deadbyte-mcp 0.15.52 → 0.15.54

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 (33) hide show
  1. package/MANIFEST.SHA256 +32 -25
  2. package/PROVENANCE.json +27 -8
  3. package/README.txt +18 -18
  4. package/RELEASE.json +33 -6
  5. package/SBOM.cdx.json +2 -2
  6. package/TEST-MATRIX.json +32 -2
  7. package/bridge/src/remote-call-claims-r60.mjs +5 -0
  8. package/controller/deadbyte-process-policy.json +4 -4
  9. package/package.json +1 -1
  10. package/scripts/deploy-r48.mjs +51 -5
  11. package/src/activation-preflight-r65.mjs +35 -5
  12. package/src/commander-remote-bridge-r47.mjs +20 -2
  13. package/src/commander-remote-relay-r47.mjs +13 -2
  14. package/src/deadbyte-cli.mjs +10 -2
  15. package/src/legacy-offline-policy-proof-r146.mjs +204 -0
  16. package/src/release-generation.mjs +20 -1
  17. package/src/remote-device-r44.mjs +1 -1
  18. package/src/remote-live-log.mjs +43 -7
  19. package/src/version.mjs +1 -1
  20. package/test/commander-remote-bridge-r47.test.mjs +20 -1
  21. package/test/commander-remote-plane-r47.test.mjs +11 -0
  22. package/test/r130-release-security-contract.test.mjs +8 -8
  23. package/test/r132-release-identity.test.mjs +9 -9
  24. package/test/r144-claim-terminal-history.test.mjs +55 -0
  25. package/test/r144-controller-broad-roots-cleanup.test.mjs +54 -0
  26. package/test/r146-generation-scope-narrowing.test.mjs +64 -0
  27. package/test/r146-legacy-offline-policy-proof.test.mjs +199 -0
  28. package/test/r146-livelog-backpressure.test.mjs +105 -0
  29. package/test/r147-remote-request-hash.test.mjs +54 -0
  30. package/test/r59-tool-surface-compat.test.mjs +1 -1
  31. package/test/r65-activation-orchestration.test.mjs +53 -0
  32. package/test/r74-prepublish-contract.test.mjs +2 -2
  33. package/test/release-generation.test.mjs +8 -1
@@ -11,12 +11,35 @@ import {classifyActivationPreflight,classifyAuthorityContinuity,orchestrateActiv
11
11
  import {createEpochNativeContinuityEngine} from '../src/activation-continuity-engine-r65.mjs';
12
12
  import {createActivationClaimManager} from '../src/activation-lock-r65.mjs';
13
13
  import {observeAuthorityEvidence} from '../src/activation-authority-proof-r75.mjs';
14
+ import {proveLegacyOfflineScopeRepair} from '../src/legacy-offline-policy-proof-r146.mjs';
14
15
 
15
16
  const root=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');
16
17
  const state=path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(os.homedir(),'.deadbyte-mcp'));
17
18
  const sha=async f=>createHash('sha256').update(await readFile(f)).digest('hex');
18
19
  const sleep=ms=>new Promise(resolve=>setTimeout(resolve,ms));
19
20
 
21
+ function parseDeployArgs(argv){
22
+ const out={preflight:false,approvalPath:null,approvalSha256:null};
23
+ for(let index=0;index<argv.length;index++){
24
+ const arg=argv[index];
25
+ if(arg==='--preflight'){if(out.preflight)throw new Error('duplicate --preflight');out.preflight=true;continue;}
26
+ if(arg==='--policy-migration-approval'){
27
+ if(out.approvalPath)throw new Error('duplicate --policy-migration-approval');
28
+ const value=argv[++index];if(!value)throw new Error('--policy-migration-approval requires a path');
29
+ out.approvalPath=path.resolve(value);continue;
30
+ }
31
+ if(arg==='--policy-migration-approval-sha256'){
32
+ if(out.approvalSha256)throw new Error('duplicate --policy-migration-approval-sha256');
33
+ const value=String(argv[++index]??'').toLowerCase();if(!/^[0-9a-f]{64}$/.test(value))throw new Error('--policy-migration-approval-sha256 requires lowercase SHA-256');
34
+ out.approvalSha256=value;continue;
35
+ }
36
+ throw new Error(`unknown deploy argument '${arg}'`);
37
+ }
38
+ if(Boolean(out.approvalPath)!==Boolean(out.approvalSha256))throw new Error('policy migration approval path and SHA-256 are required together');
39
+ return out;
40
+ }
41
+ const deployArgs=parseDeployArgs(process.argv.slice(2));
42
+
20
43
  async function writeExclusive(file,value){
21
44
  const handle=await open(file,'wx',0o600);
22
45
  try{await handle.writeFile(JSON.stringify(value)+'\n');await handle.sync();}
@@ -72,13 +95,35 @@ function controllerCommand(command){
72
95
  if(run.error||run.status!==0)throw new Error(`activation controller ${command} failed exit=${run.status} code=${run.error?.code??'none'}`);
73
96
  }
74
97
 
75
- async function activationPreflight(manifest,pkg,pointer){
98
+ function legacyOfflineRecovery(approvalPath,approvalSha256){
99
+ if(!approvalPath)return null;
100
+ return async({observation,preflight_subject_sha256,pointer,expectedParent,candidate,expectedCandidate})=>
101
+ proveLegacyOfflineScopeRepair({stateRoot:state,pointer,expectedParent,candidate,expectedCandidate,
102
+ preflightSubjectSha256:preflight_subject_sha256,observation,policyPaths:observation.policy_paths,
103
+ policySha256:observation.policy_sha256,approvalPath,approvalSha256,
104
+ ownerPublicKeyPath:path.join(state,'trust','r70-owner-public.pem')});
105
+ }
106
+
107
+ async function activationPreflight(manifest,pkg,pointer,recovery){
76
108
  const release=JSON.parse(await readFile(path.join(root,'RELEASE.json'),'utf8'));
77
109
  const observed=await observeController();
78
110
  const result=classifyActivationPreflight({pointer,expectedParent:release.parent,
79
111
  candidate:{version:pkg.version,manifest_sha256:manifest},
80
112
  expectedCandidate:{version:release.version,manifest_sha256:manifest},observation:observed});
81
113
  const continuity=classifyAuthorityContinuity(observed);
114
+ if(result.state==='AUTHORITY_UNVERIFIED'&&recovery&&observed.state==='OFFLINE'){
115
+ try{
116
+ const proof=await recovery({phase:'preflight',observation:observed,preflight_subject_sha256:result.subject_sha256,
117
+ pointer,expectedParent:release.parent,candidate:{version:pkg.version,manifest_sha256:manifest},
118
+ expectedCandidate:{version:release.version,manifest_sha256:manifest}});
119
+ if(proof?.state==='VERIFIED_OFFLINE_LEGACY_SCOPE_REPAIR'&&proof.owner_approval_verified===true)
120
+ return {...result,state:'LEGACY_SCOPE_REPAIR_READY',safe_to_activate:true,authority_continuity:'LEGACY_SCOPE_REPAIRED_DISARMED',
121
+ legacy_scope_proof_sha256:proof.subject_sha256,owner_approval_subject_sha256:proof.owner_approval_subject_sha256,reauth_planes:[]};
122
+ }catch(error){
123
+ return {...result,state:'LEGACY_SCOPE_REPAIR_UNVERIFIED',safe_to_activate:false,
124
+ reason:String(error?.message??error).slice(0,256),authority_continuity:'UNVERIFIED',reauth_planes:[]};
125
+ }
126
+ }
82
127
  if(continuity.state==='UNVERIFIED')return {...result,state:'AUTHORITY_CONTINUITY_UNVERIFIED',safe_to_activate:false,
83
128
  reason:continuity.reason??'authority continuity observation unverified',authority_continuity:'UNVERIFIED',reauth_planes:continuity.reauth_planes??[]};
84
129
  if(continuity.state==='EPOCH_NATIVE')return {...result,authority_continuity:'EPOCH_NATIVE',
@@ -139,13 +184,13 @@ const bridgeScript=path.join(bridgeRoot,'src','bridge.mjs');
139
184
  const pkg=JSON.parse(await readFile(path.join(root,'package.json'),'utf8'));
140
185
  const expectedVersion=pkg.version;
141
186
  const release=JSON.parse(await readFile(path.join(root,'RELEASE.json'),'utf8'));
142
- const preflight=await activationPreflight(manifest,pkg,pointer);
143
- if(process.argv.includes('--preflight')){
187
+ const recovery=legacyOfflineRecovery(deployArgs.approvalPath,deployArgs.approvalSha256);
188
+ const preflight=await activationPreflight(manifest,pkg,pointer,recovery);
189
+ if(deployArgs.preflight){
144
190
  console.log(JSON.stringify(preflight));
145
191
  process.exit(0);
146
192
  }
147
- if(process.argv.length>2)throw new Error('deploy accepts only --preflight');
148
- if(!['ONLINE_READY','ONLINE_HOST_ARMED','PARKED_DIRTY','OFFLINE_CLEAN'].includes(preflight.state))
193
+ if(!['ONLINE_READY','ONLINE_HOST_ARMED','PARKED_DIRTY','OFFLINE_CLEAN','LEGACY_SCOPE_REPAIR_READY'].includes(preflight.state))
149
194
  throw new Error(`activation preflight rejected: ${preflight.state} subject=${preflight.subject_sha256??'<incomplete>'}`);
150
195
  const args=[
151
196
  '--stage',root,'--development-root',root,'--expected-version',expectedVersion,'--expected-manifest',manifest,
@@ -178,6 +223,7 @@ if(preflight.authority_continuity==='EPOCH_NATIVE'){
178
223
  const outcome=await orchestrateActivation({pointer,expectedParent:release.parent,
179
224
  candidate:{version:expectedVersion,manifest_sha256:manifest},expectedCandidate:{version:release.version,manifest_sha256:manifest},
180
225
  observe:async()=>observeController(),command:async command=>controllerCommand(command),claim:claimActivation,continuityEngine,
226
+ legacyOfflineRecovery:recovery,
181
227
  recordOutcome:recordActivationOutcome,
182
228
  activate:async()=>{
183
229
  result=await runSlotUpdateCli(args,{stateRoot:state});
@@ -85,12 +85,25 @@ export function classifyActivationPreflight({pointer,expectedParent,candidate,ex
85
85
  return Object.freeze({state:'ONLINE_READY',safe_to_activate:true,...bound});
86
86
  }
87
87
 
88
- export async function orchestrateActivation({pointer,expectedParent,candidate,expectedCandidate,observe,command,activate,claim=null,continuityEngine=null,recordOutcome=null}){
88
+ function validateLegacyOfflineRecoveryProof(proof,{preflightSubjectSha256,candidate}){
89
+ if(!proof||proof.state!=='VERIFIED_OFFLINE_LEGACY_SCOPE_REPAIR'||
90
+ proof.preflight_subject_sha256!==preflightSubjectSha256||
91
+ proof.candidate_manifest_sha256!==candidate.manifest_sha256||
92
+ proof.owner_approval_verified!==true||!HEX64.test(String(proof.subject_sha256??'')))
93
+ throw new Error('legacy offline scope recovery proof invalid or unbound');
94
+ return proof;
95
+ }
96
+
97
+ export async function orchestrateActivation({pointer,expectedParent,candidate,expectedCandidate,observe,command,activate,claim=null,continuityEngine=null,recordOutcome=null,legacyOfflineRecovery=null}){
89
98
  if([observe,command,activate].some(fn=>typeof fn!=='function'))throw new Error('activation orchestrator callbacks invalid');
90
99
  const input={pointer,expectedParent,candidate,expectedCandidate};
91
100
  const firstObservation=await observe();
92
101
  const initial=classifyActivationPreflight({...input,observation:firstObservation});
93
- if(!['ONLINE_READY','ONLINE_HOST_ARMED','PARKED_DIRTY','OFFLINE_CLEAN'].includes(initial.state))
102
+ const legacyScopeRepair=initial.state==='AUTHORITY_UNVERIFIED'&&typeof legacyOfflineRecovery==='function'&&
103
+ firstObservation?.state==='OFFLINE'&&firstObservation?.local_ready===false&&
104
+ !firstObservation?.bridge?.alive&&!firstObservation?.core?.alive&&!firstObservation?.cloud?.alive&&
105
+ firstObservation?.supervisor?.running===false&&PLANES.every(plane=>initial.authority?.[plane]===false);
106
+ if(!legacyScopeRepair&&!['ONLINE_READY','ONLINE_HOST_ARMED','PARKED_DIRTY','OFFLINE_CLEAN'].includes(initial.state))
94
107
  return Object.freeze({state:'ACTIVATION_REJECTED',preflight:initial.state,subject_sha256:initial.subject_sha256??null});
95
108
  const continuity=classifyAuthorityContinuity(firstObservation);
96
109
  if(continuity.state==='UNVERIFIED')
@@ -112,6 +125,12 @@ export async function orchestrateActivation({pointer,expectedParent,candidate,ex
112
125
  let continuityCapture=null;
113
126
  const outcome=await (async()=>{
114
127
  try{
128
+ let legacyProof=null;
129
+ if(legacyScopeRepair){
130
+ legacyProof=validateLegacyOfflineRecoveryProof(await legacyOfflineRecovery({phase:'claimed',observation:firstObservation,
131
+ preflight_subject_sha256:initial.subject_sha256,pointer,expectedParent,candidate,expectedCandidate}),
132
+ {preflightSubjectSha256:initial.subject_sha256,candidate});
133
+ }
115
134
  const requiresReauth=continuity.state==='LEGACY_REAUTH_REQUIRED'||continuity.state==='MIXED_REAUTH_REQUIRED';
116
135
  const epochNative=continuity.state==='EPOCH_NATIVE';
117
136
  if(epochNative){
@@ -134,8 +153,18 @@ export async function orchestrateActivation({pointer,expectedParent,candidate,ex
134
153
  await command('supervisorstop');
135
154
  await command('stop');
136
155
  }
137
- const ready=classifyActivationPreflight({...input,observation:await observe()});
138
- if(!ready.safe_to_activate)
156
+ const readyObservation=await observe();
157
+ const ready=classifyActivationPreflight({...input,observation:readyObservation});
158
+ let readySafe=ready.safe_to_activate;
159
+ if(legacyScopeRepair){
160
+ if(ready.subject_sha256!==initial.subject_sha256)throw new Error('legacy offline scope recovery observation drift');
161
+ const second=validateLegacyOfflineRecoveryProof(await legacyOfflineRecovery({phase:'preactivate',observation:readyObservation,
162
+ preflight_subject_sha256:initial.subject_sha256,pointer,expectedParent,candidate,expectedCandidate,prior_proof:legacyProof}),
163
+ {preflightSubjectSha256:initial.subject_sha256,candidate});
164
+ if(second.subject_sha256!==legacyProof.subject_sha256)throw new Error('legacy offline scope recovery proof drift');
165
+ readySafe=true;
166
+ }
167
+ if(!readySafe)
139
168
  return Object.freeze({state:transitionAttempted?'ACTIVATION_OUTCOME_UNKNOWN':'ACTIVATION_REJECTED',
140
169
  preflight:ready.state,subject_sha256:initial.subject_sha256});
141
170
  if(PLANES.some(plane=>ready.authority[plane]!==false))
@@ -180,7 +209,8 @@ export async function orchestrateActivation({pointer,expectedParent,candidate,ex
180
209
  reauth_planes:Object.freeze([...(restored.reauth_planes??[])])});
181
210
  }
182
211
  const final=beforeRestore;
183
- const authorityOutcome=continuity.state==='NOT_APPLICABLE'?'NOT_APPLICABLE':'REAUTH_REQUIRED';
212
+ const authorityOutcome=legacyScopeRepair?'LEGACY_SCOPE_REPAIRED_DISARMED':
213
+ continuity.state==='NOT_APPLICABLE'?'NOT_APPLICABLE':'REAUTH_REQUIRED';
184
214
  return Object.freeze({
185
215
  state:'ACTIVATED',runtime_activation:'ACTIVATED',authority_continuity:authorityOutcome,
186
216
  subject_sha256:initial.subject_sha256,release_id:activation.release_id,
@@ -1,4 +1,5 @@
1
1
  import * as z from 'zod/v4';
2
+ import { payloadHash } from './remote-device-r44.mjs';
2
3
 
3
4
  const HEX64=/^[0-9a-f]{64}$/;
4
5
  const CAP=/^[A-Za-z0-9._-]{1,128}$/;
@@ -9,12 +10,25 @@ function requirePlane(plane,name){
9
10
  if(!plane) return {status:'unavailable',tool:name,error:'remote-device-r44 plane is not configured'};
10
11
  return null;
11
12
  }
13
+ async function probeBoundConnection(plane,{device_id,epoch,connection_id,nonce}){
14
+ if(!HEX64.test(device_id??''))throw new Error('remote readiness device identity invalid');
15
+ if(!Number.isInteger(epoch)||epoch<1)throw new Error('remote readiness epoch invalid');
16
+ if(typeof connection_id!=='string'||connection_id.length<1||connection_id.length>256)throw new Error('remote readiness connection identity invalid');
17
+ if(!HEX64.test(nonce??''))throw new Error('remote readiness nonce invalid');
18
+ const result=await plane.probe({deviceId:device_id,epoch});
19
+ if(!result||result.device_id!==device_id)throw new Error('remote readiness device identity mismatch');
20
+ if(result.epoch!==epoch)throw new Error('remote readiness epoch mismatch');
21
+ if(result.connection_id!==connection_id)throw new Error('remote readiness connection identity mismatch');
22
+ if(result.reachable!==true)throw new Error('remote readiness probe not reachable');
23
+ return {...result,nonce,ready:true};
24
+ }
12
25
  export function registerCommanderRemoteBridge(server,{remoteDevicePlane=null}={}){
13
26
  register(server,'remote_device',z.strictObject({
14
27
  op:z.enum(['pair','status','report_online','probe','dispatch','revoke','rotate_credential','list']),
15
28
  device_id:z.string().regex(HEX64).optional(),epoch:z.number().int().min(1).optional(),
16
29
  public_key_spki_base64:z.string().min(1).max(4096).optional(),display_name:z.string().min(1).max(128).optional(),
17
30
  connection_id:z.string().min(1).max(256).optional(),capability_id:z.string().regex(CAP).optional(),
31
+ nonce:z.string().regex(HEX64).optional(),
18
32
  payload:z.record(z.string(),z.unknown()).optional(),request_id:z.string().regex(HEX64).optional(),
19
33
  encrypted_bundle_base64:z.string().min(1).max(1024*1024).optional(),expected_previous_sha256:z.string().regex(HEX64).nullable().optional()
20
34
  }),async a=>{
@@ -23,7 +37,7 @@ export function registerCommanderRemoteBridge(server,{remoteDevicePlane=null}={}
23
37
  case 'pair': return remoteDevicePlane.pair({publicKeySpkiBase64:a.public_key_spki_base64,displayName:a.display_name});
24
38
  case 'status': return remoteDevicePlane.status({deviceId:a.device_id});
25
39
  case 'report_online': return remoteDevicePlane.reportOnline({deviceId:a.device_id,epoch:a.epoch,connectionId:a.connection_id});
26
- case 'probe': return remoteDevicePlane.probe({deviceId:a.device_id,epoch:a.epoch});
40
+ case 'probe': return probeBoundConnection(remoteDevicePlane,a);
27
41
  case 'dispatch': return remoteDevicePlane.dispatch({deviceId:a.device_id,epoch:a.epoch,capabilityId:a.capability_id,payload:a.payload??{},requestId:a.request_id});
28
42
  case 'revoke': return remoteDevicePlane.revoke({deviceId:a.device_id,epoch:a.epoch});
29
43
  case 'rotate_credential': return remoteDevicePlane.rotateCredential({deviceId:a.device_id,epoch:a.epoch,encryptedBundleBase64:a.encrypted_bundle_base64,expectedPreviousSha256:a.expected_previous_sha256??null});
@@ -33,9 +47,13 @@ export function registerCommanderRemoteBridge(server,{remoteDevicePlane=null}={}
33
47
  });
34
48
  register(server,'remote_mcp',z.strictObject({
35
49
  device_id:z.string().regex(HEX64),epoch:z.number().int().min(1),capability_id:z.string().regex(CAP),
36
- payload:z.record(z.string(),z.unknown()).default({}),request_id:z.string().regex(HEX64)
50
+ payload:z.record(z.string(),z.unknown()).default({}),request_id:z.string().regex(HEX64),
51
+ request_sha256:z.string().regex(HEX64).optional()
37
52
  }),async a=>{
38
53
  const unavailable=requirePlane(remoteDevicePlane,'remote_mcp');if(unavailable)return unavailable;
54
+ if(a.request_sha256&&payloadHash({deviceId:a.device_id,epoch:a.epoch,capabilityId:a.capability_id,payload:a.payload})!==a.request_sha256){
55
+ throw new Error('remote request hash mismatch');
56
+ }
39
57
  return remoteDevicePlane.dispatch({deviceId:a.device_id,epoch:a.epoch,capabilityId:a.capability_id,payload:a.payload,requestId:a.request_id});
40
58
  });
41
59
  }
@@ -1,4 +1,5 @@
1
1
  import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
2
+ import { DEADBYTE_VERSION } from './version.mjs';
2
3
 
3
4
  function insist(ok,message){if(!ok)throw new Error(message);}
4
5
  function structured(result,label){
@@ -8,11 +9,21 @@ function structured(result,label){
8
9
  return value;
9
10
  }
10
11
 
12
+ export function validateRemoteProbeResponseR47(result,{deviceId,epoch,connectionId,nonce}){
13
+ insist(result&&typeof result==='object','remote readiness response missing');
14
+ insist(result.device_id===deviceId,'remote readiness device identity mismatch');
15
+ insist(result.epoch===epoch,'remote readiness epoch mismatch');
16
+ insist(result.connection_id===connectionId,'remote readiness connection identity mismatch');
17
+ insist(result.nonce===nonce,'remote readiness nonce mismatch');
18
+ insist(result.ready===true,'remote readiness response not ready');
19
+ return result;
20
+ }
21
+
11
22
  export async function createCommanderRemoteRelayR47({endpoint,protocolVersion='2026-07-28',timeoutMs=15000}={}){
12
23
  const url=new URL(String(endpoint??''));
13
24
  insist(url.protocol==='https:','remote relay endpoint must use HTTPS');
14
25
  insist(Number.isInteger(timeoutMs)&&timeoutMs>=100&&timeoutMs<=120000,'remote relay timeout invalid');
15
- const client=new Client({name:'deadbyte-r47-remote-relay',version:'0.15.15'},{versionNegotiation:{mode:{pin:protocolVersion}}});
26
+ const client=new Client({name:'deadbyte-r47-remote-relay',version:DEADBYTE_VERSION},{versionNegotiation:{mode:{pin:protocolVersion}}});
16
27
  const transport=new StreamableHTTPClientTransport(url);
17
28
  let connected=false;
18
29
  let connecting=null;
@@ -25,7 +36,7 @@ export async function createCommanderRemoteRelayR47({endpoint,protocolVersion='2
25
36
  async function probe({device_id,epoch,connection_id,nonce}){
26
37
  const value=await call('remote_device',{op:'probe',device_id,epoch,connection_id,nonce},'remote readiness probe');
27
38
  const result=value.result??value;
28
- return {device_id,epoch,nonce,ready:result.ready===true||result.reachable===true};
39
+ return validateRemoteProbeResponseR47(result,{deviceId:device_id,epoch,connectionId:connection_id,nonce});
29
40
  }
30
41
  async function request({device_id,epoch,capability_id,payload,request_id,request_sha256}){
31
42
  insist(typeof request_sha256==='string'&&/^[0-9a-f]{64}$/.test(request_sha256),'remote request hash invalid');
@@ -400,7 +400,11 @@ async function cleanupRemoteSession(ctx,{displayMode='unicode'}={}){
400
400
  for(const command of SESSION_CLEANUP){
401
401
  try{
402
402
  const result=invokeController(ctx,command,{allowFailure:true,capture:true,echoCaptured:false,timeoutMs:REMOTE_STATUS_TIMEOUT_MS});
403
- if(result.status!==0){failed.push(command);writeRemote(process.stderr,`${remoteTag('warn',displayMode)} cleanup ${command} exit=${result.status}\n`,displayMode);}
403
+ if(result.status!==0){
404
+ failed.push(command);
405
+ const detail=boundedSingleLine(result.stderr||result.stdout||'controller returned no diagnostic',512);
406
+ writeRemote(process.stderr,`${remoteTag('warn',displayMode)} cleanup ${command} exit=${result.status}: ${detail}\n`,displayMode);
407
+ }
404
408
  }
405
409
  catch(error){failed.push(command);writeRemote(process.stderr,`${remoteTag('warn',displayMode)} cleanup ${command}: ${String(error?.message||error)}\n`,displayMode);}
406
410
  }
@@ -631,7 +635,11 @@ async function runRemoteSession({resumeCursor=null,verbose=false,trace=false,fil
631
635
  const wasAborted=aborter.signal.aborted;
632
636
  aborter.abort();
633
637
  if(logTask) await logTask.catch(()=>{});
634
- timelineWriter.flush();
638
+ timelineWriter.flush();
639
+ const liveLogDelivery=timelineWriter.retention();
640
+ if(liveLogDelivery.suppressed_since_drain>0){
641
+ process.stderr.write(`[WARN] LIVELOG ${liveLogDelivery.suppressed_since_drain} display lines still suppressed at disconnect due to stdout backpressure; inspect signed audit and runtime logs.\n`);
642
+ }
635
643
  for(const line of activityTracker.reconcileInterrupted('session_end')) process.stdout.write(`${line}\n`);
636
644
  const filterRetention=timelineFilter?.retention();
637
645
  if(filterRetention?.evicted>0) process.stdout.write(`[WARN] LIVELOG filter evicted ${filterRetention.evicted} presentation identities; full audit remains on disk.\n`);
@@ -0,0 +1,204 @@
1
+ import {createHash,createPublicKey,verify as verifySignature} from 'node:crypto';
2
+ import {lstat,open,readFile} from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import {verifyGenerationBundle} from './release-generation.mjs';
5
+ import {canonicalJson} from './canonical-json.mjs';
6
+
7
+ const PLANES=['process','coding','autonomous','desktop','host'];
8
+ const GRANT_FIELDS={process:'grant_file',coding:'lease_file',autonomous:'lease_file',desktop:'grant_file',host:'arm_file'};
9
+ const HEX=/^[0-9a-f]{64}$/;
10
+ const APPROVAL_DOMAIN=Buffer.from('DEADBYTE-R146-POLICY-MIGRATION-APPROVAL\0','utf8');
11
+ const APPROVAL_SCHEMA='deadbyte.r146-policy-migration-approval.v1';
12
+ const SUBJECT_SCHEMA='deadbyte.r146-policy-migration-subject.v1';
13
+ const APPROVAL_FIELDS=['schema','subject_sha256','owner_key_id','subject','signature_base64'];
14
+ const SUBJECT_FIELDS=['schema','action','predecessor_release_id','predecessor_version','predecessor_manifest_sha256',
15
+ 'predecessor_generation_manifest_sha256','candidate_version','candidate_manifest_sha256','predecessor_policy_sha256',
16
+ 'preflight_subject_sha256','issued_at_utc','expires_at_utc'];
17
+ const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
18
+
19
+ function sameFile(a,b){
20
+ return a.isFile()&&b.isFile()&&!a.isSymbolicLink()&&!b.isSymbolicLink()&&
21
+ a.dev===b.dev&&a.ino===b.ino&&a.size===b.size&&a.mtimeMs===b.mtimeMs&&a.ctimeMs===b.ctimeMs;
22
+ }
23
+ async function readPinned(file,expected,label){
24
+ if(!path.isAbsolute(file)||!HEX.test(expected??''))throw new Error(label+' identity incomplete');
25
+ const before=await lstat(file);
26
+ if(!before.isFile()||before.isSymbolicLink())throw new Error(label+' file invalid');
27
+ const handle=await open(file,'r');
28
+ try{
29
+ const opened=await handle.stat();
30
+ if(!sameFile(before,opened))throw new Error(label+' identity moved');
31
+ const bytes=await handle.readFile();
32
+ const after=await handle.stat(),onPath=await lstat(file);
33
+ if(!sameFile(opened,after)||!sameFile(after,onPath))throw new Error(label+' changed');
34
+ if(sha(bytes)!==expected)throw new Error(label+' hash mismatch');
35
+ return JSON.parse(bytes.toString('utf8').replace(/^\uFEFF/,''));
36
+ }finally{await handle.close();}
37
+ }
38
+ async function readPinnedBytes(file,expected,label){
39
+ if(!path.isAbsolute(file)||!HEX.test(expected??''))throw new Error(label+' identity incomplete');
40
+ const before=await lstat(file);
41
+ if(!before.isFile()||before.isSymbolicLink())throw new Error(label+' file invalid');
42
+ const handle=await open(file,'r');
43
+ try{
44
+ const opened=await handle.stat();
45
+ if(!sameFile(before,opened))throw new Error(label+' identity moved');
46
+ const bytes=await handle.readFile();
47
+ const after=await handle.stat(),onPath=await lstat(file);
48
+ if(!sameFile(opened,after)||!sameFile(after,onPath))throw new Error(label+' changed');
49
+ if(sha(bytes)!==expected)throw new Error(label+' hash mismatch');
50
+ return bytes;
51
+ }finally{await handle.close();}
52
+ }
53
+ async function readRegularBytes(file,label){
54
+ if(!path.isAbsolute(file))throw new Error(label+' path invalid');
55
+ const before=await lstat(file);
56
+ if(!before.isFile()||before.isSymbolicLink())throw new Error(label+' file invalid');
57
+ const handle=await open(file,'r');
58
+ try{
59
+ const opened=await handle.stat();
60
+ if(!sameFile(before,opened))throw new Error(label+' identity moved');
61
+ const bytes=await handle.readFile();
62
+ const after=await handle.stat(),onPath=await lstat(file);
63
+ if(!sameFile(opened,after)||!sameFile(after,onPath))throw new Error(label+' changed');
64
+ return bytes;
65
+ }finally{await handle.close();}
66
+ }
67
+ async function mustBeAbsent(file,label){
68
+ try{await lstat(file);throw new Error(label+' present');}
69
+ catch(error){if(error?.code!=='ENOENT')throw error;}
70
+ }
71
+ function exactDrive(root,drive){
72
+ return typeof root?.path==='string'&&path.win32.normalize(root.path).toLowerCase()===drive.toLowerCase();
73
+ }
74
+ function exactObject(value,fields,label){
75
+ if(!value||typeof value!=='object'||Array.isArray(value))throw new Error(label+' invalid');
76
+ const keys=Object.keys(value);
77
+ if(keys.some(key=>!fields.includes(key))||fields.some(key=>!Object.hasOwn(value,key)))throw new Error(label+' shape invalid');
78
+ return value;
79
+ }
80
+ function instant(value,label){
81
+ if(typeof value!=='string'||!/^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z$/.test(value))throw new Error(label+' invalid');
82
+ const parsed=Date.parse(value);
83
+ if(!Number.isFinite(parsed)||new Date(parsed).toISOString()!==value)throw new Error(label+' invalid');
84
+ return parsed;
85
+ }
86
+ async function verifyMigrationApproval({approvalPath,approvalSha256,ownerPublicKeyPath,expected,now}){
87
+ const approval=JSON.parse((await readPinnedBytes(approvalPath,approvalSha256,'policy migration approval')).toString('utf8').replace(/^\uFEFF/,''));
88
+ exactObject(approval,APPROVAL_FIELDS,'policy migration approval');
89
+ const subject=exactObject(approval.subject,SUBJECT_FIELDS,'policy migration approval subject');
90
+ if(approval.schema!==APPROVAL_SCHEMA||subject.schema!==SUBJECT_SCHEMA||subject.action!=='legacy_offline_scope_repair')throw new Error('policy migration approval scope invalid');
91
+ for(const [key,value] of Object.entries(expected)){
92
+ if(key==='predecessor_policy_sha256'){
93
+ if(canonicalJson(subject[key])!==canonicalJson(value))throw new Error('policy migration approval policy binding mismatch');
94
+ }else if(subject[key]!==value)throw new Error('policy migration approval subject mismatch: '+key.replaceAll('_',' '));
95
+ }
96
+ const issued=instant(subject.issued_at_utc,'policy migration approval issued time');
97
+ const expires=instant(subject.expires_at_utc,'policy migration approval expiry');
98
+ const observed=now instanceof Date?now.getTime():NaN;
99
+ if(!Number.isFinite(observed)||expires<=issued||expires-issued>3_600_000||observed<issued||observed>=expires)throw new Error('policy migration approval expired or invalid');
100
+ const subjectSha256=sha(Buffer.from(canonicalJson(subject),'utf8'));
101
+ if(approval.subject_sha256!==subjectSha256)throw new Error('policy migration approval subject hash mismatch');
102
+ const keyBytes=await readRegularBytes(ownerPublicKeyPath,'policy migration owner key');
103
+ let publicKey;
104
+ try{publicKey=createPublicKey(keyBytes);}catch{throw new Error('policy migration owner key invalid');}
105
+ if(publicKey.asymmetricKeyType!=='ed25519')throw new Error('policy migration owner key must be Ed25519');
106
+ const ownerKeyId=sha(publicKey.export({type:'spki',format:'der'}));
107
+ if(approval.owner_key_id!==ownerKeyId)throw new Error('policy migration owner key identity mismatch');
108
+ if(typeof approval.signature_base64!=='string')throw new Error('policy migration approval signature invalid');
109
+ const signature=Buffer.from(approval.signature_base64,'base64');
110
+ const signed={schema:APPROVAL_SCHEMA,subject_sha256:subjectSha256,owner_key_id:ownerKeyId};
111
+ if(signature.length!==64||!verifySignature(null,Buffer.concat([APPROVAL_DOMAIN,Buffer.from(canonicalJson(signed),'utf8')]),publicKey,signature))throw new Error('policy migration approval signature invalid');
112
+ return {subject_sha256:subjectSha256,owner_key_id:ownerKeyId,expires_at_utc:subject.expires_at_utc};
113
+ }
114
+ export async function proveLegacyOfflineScopeRepair({
115
+ stateRoot,pointer,expectedParent,candidate,expectedCandidate,preflightSubjectSha256,
116
+ observation,policyPaths,policySha256,approvalPath,approvalSha256,ownerPublicKeyPath,
117
+ now=new Date()
118
+ }){
119
+ if(!stateRoot||!path.isAbsolute(stateRoot))throw new Error('state root invalid');
120
+ if(!pointer||!expectedParent||!observation)throw new Error('legacy scope proof subject missing');
121
+ if(expectedParent.version!=='0.15.45'||pointer.version!==expectedParent.version||
122
+ pointer.release_id!==expectedParent.release_id||
123
+ pointer.manifest_sha256!==expectedParent.manifest_sha256||
124
+ pointer.generation_manifest_sha256!==expectedParent.generation_manifest_sha256)
125
+ throw new Error('legacy scope proof predecessor mismatch');
126
+ if(!candidate||!expectedCandidate||candidate.version!==expectedCandidate.version||
127
+ candidate.manifest_sha256!==expectedCandidate.manifest_sha256||!HEX.test(candidate.manifest_sha256??''))
128
+ throw new Error('legacy scope proof candidate mismatch');
129
+ if(!HEX.test(preflightSubjectSha256??''))throw new Error('legacy scope proof preflight subject invalid');
130
+ if(observation.state!=='OFFLINE'||observation.local_ready!==false||
131
+ ['bridge','core','cloud'].some(key=>observation[key]?.alive!==false)||
132
+ observation.supervisor?.running!==false)
133
+ throw new Error('legacy scope proof runtime is not cleanly offline');
134
+ if(observation.active_release_id!==pointer.release_id||
135
+ observation.active_manifest_sha256!==pointer.manifest_sha256||
136
+ observation.active_generation_manifest_sha256!==pointer.generation_manifest_sha256)
137
+ throw new Error('legacy scope proof observation generation drift');
138
+ if(!observation.authority||!observation.authority_proof||!observation.policy_sha256)
139
+ throw new Error('legacy scope proof missing authority evidence');
140
+ for(const plane of PLANES){
141
+ const entry=observation.authority[plane],proof=observation.authority_proof[plane];
142
+ const reason=plane==='autonomous'?'unverified':'disarmed';
143
+ const state=plane==='autonomous'?'UNVERIFIED':'VERIFIED_DISARMED';
144
+ if(entry?.configured!==true||entry.armed!==false||entry.reason!==reason||
145
+ proof?.state!==state||proof.grant_absent!==true||
146
+ proof.policy_sha256!==observation.policy_sha256[plane]||
147
+ policySha256?.[plane]!==proof.policy_sha256)
148
+ throw new Error('legacy scope proof authority not physically inactive: '+plane);
149
+ }
150
+ const storedPointer=JSON.parse(await readFile(path.join(stateRoot,'active-release.json'),'utf8'));
151
+ if(storedPointer.release_id!==pointer.release_id||
152
+ storedPointer.generation_manifest_sha256!==pointer.generation_manifest_sha256||
153
+ storedPointer.manifest_sha256!==pointer.manifest_sha256)
154
+ throw new Error('legacy scope proof pointer changed');
155
+ await verifyGenerationBundle(pointer.generation_root,pointer.generation_manifest_sha256);
156
+ await mustBeAbsent(path.join(stateRoot,'runtime','remote-session.json'),'foreground ownership marker');
157
+ for(const plane of PLANES)await mustBeAbsent(path.join(stateRoot,'runtime','authority-epochs',plane,'.lock'),plane+' authority epoch lock');
158
+ const policies={};
159
+ for(const plane of PLANES){
160
+ const file=policyPaths?.[plane],digest=policySha256?.[plane];
161
+ if(path.resolve(path.dirname(file??''))!==path.resolve(pointer.generation_root))throw new Error('legacy scope proof policy outside predecessor generation: '+plane);
162
+ policies[plane]=await readPinned(file,digest,plane+' policy');
163
+ const grant=policies[plane][GRANT_FIELDS[plane]];
164
+ if(typeof grant!=='string'||!path.isAbsolute(grant)||
165
+ path.win32.resolve(path.win32.dirname(grant)).toLowerCase()!==
166
+ path.win32.resolve(path.join(stateRoot,'runtime')).toLowerCase())
167
+ throw new Error('legacy scope proof grant path invalid: '+plane);
168
+ await mustBeAbsent(grant,plane+' grant');
169
+ }
170
+ const coding=policies.coding,autonomous=policies.autonomous;
171
+ if(coding.schema!=='deadbyte.coding-policy.v1'||autonomous.schema!=='deadbyte.autonomous-policy.v1'||
172
+ coding.enabled!==true||autonomous.enabled!==true||
173
+ autonomous.coding_policy_sha256!==policySha256.coding)
174
+ throw new Error('legacy scope proof Coding/Autonomous binding invalid');
175
+ if(!exactDrive(coding.roots?.local_c,'C:\\')||!exactDrive(coding.roots?.local_d,'D:\\'))
176
+ throw new Error('legacy scope proof expected drive roots missing');
177
+ for(const [id,root] of Object.entries(coding.roots)){
178
+ const normalized=path.win32.normalize(String(root?.path??''));
179
+ if(/^[a-z]:\\$/i.test(normalized)&&!['local_c','local_d'].includes(id))
180
+ throw new Error('legacy scope proof unexpected drive root: '+id);
181
+ }
182
+ if(!Array.isArray(autonomous.root_ids)||autonomous.root_ids.length===0||
183
+ autonomous.root_ids.some(id=>id==='local_c'||id==='local_d'||!Object.hasOwn(coding.roots,id)))
184
+ throw new Error('legacy scope proof autonomous roots unsafe');
185
+ const approval=await verifyMigrationApproval({approvalPath,approvalSha256,ownerPublicKeyPath,now,expected:{
186
+ predecessor_release_id:pointer.release_id,predecessor_version:pointer.version,
187
+ predecessor_manifest_sha256:pointer.manifest_sha256,
188
+ predecessor_generation_manifest_sha256:pointer.generation_manifest_sha256,
189
+ candidate_version:candidate.version,candidate_manifest_sha256:candidate.manifest_sha256,
190
+ predecessor_policy_sha256:policySha256,preflight_subject_sha256:preflightSubjectSha256
191
+ }});
192
+ const subject={release_id:pointer.release_id,version:pointer.version,
193
+ generation_manifest_sha256:pointer.generation_manifest_sha256,
194
+ coding_policy_sha256:policySha256.coding,autonomous_policy_sha256:policySha256.autonomous,
195
+ candidate_version:candidate.version,candidate_manifest_sha256:candidate.manifest_sha256,
196
+ preflight_subject_sha256:preflightSubjectSha256,owner_approval_subject_sha256:approval.subject_sha256,
197
+ grant_absent_planes:PLANES};
198
+ return Object.freeze({
199
+ state:'VERIFIED_OFFLINE_LEGACY_SCOPE_REPAIR',
200
+ ...subject,owner_approval_verified:true,owner_key_id:approval.owner_key_id,
201
+ approval_expires_at_utc:approval.expires_at_utc,
202
+ subject_sha256:sha(Buffer.from(canonicalJson(subject),'utf8'))
203
+ });
204
+ }
@@ -51,11 +51,30 @@ export async function verifyGenerationBundle(root,expectedManifestSha256){
51
51
  return {files,generation_manifest_sha256:manifestSha256};
52
52
  }
53
53
 
54
- function rebindCodingPolicy(raw,{developmentRoot,stateRoot,parityScratchRoot}){
54
+ export function rebindCodingPolicy(raw,{developmentRoot,stateRoot,parityScratchRoot}){
55
55
  const policy=clone(raw);
56
56
  policy.lease_file=path.join(stateRoot,'runtime','coding-grant.json');
57
57
  policy.evidence_dir=path.join(stateRoot,'evidence','coding');
58
58
  if(!policy.roots?.core) throw new Error('coding policy requires core root for R27');
59
+ // A prior generation may contain legacy whole-drive roots. Never let those
60
+ // capabilities propagate into a new signed release generation.
61
+ const removed=new Set();
62
+ for(const [id,root] of Object.entries(policy.roots)){
63
+ if(typeof root?.path!=='string') throw new Error('coding root path invalid: '+id);
64
+ const full=path.win32.normalize(root.path),drive=path.win32.parse(full).root;
65
+ if(!/^[a-z]:\\$/i.test(drive)||full.toLowerCase()!==drive.toLowerCase()) continue;
66
+ if(!((id==='local_c'&&drive.toLowerCase()==='c:\\')||
67
+ (id==='local_d'&&drive.toLowerCase()==='d:\\')))
68
+ throw new Error('unrecognized whole-drive Coding root: '+id);
69
+ removed.add(id);
70
+ delete policy.roots[id];
71
+ }
72
+ for(const [id,command] of Object.entries(policy.commands??{})){
73
+ if(!Array.isArray(command.root_ids)) continue;
74
+ const next=command.root_ids.filter(rootId=>!removed.has(rootId));
75
+ if(next.length===0) throw new Error('Coding command would lose every scoped root: '+id);
76
+ command.root_ids=next;
77
+ }
59
78
  policy.roots.core.path=developmentRoot;
60
79
  policy.roots.parity_scratch={path:parityScratchRoot,read:true,write:true,exec:false,deny_paths:[]};
61
80
  return policy;
@@ -73,7 +73,7 @@ async function withTimeout(operation,timeoutMs,label){
73
73
  ]);
74
74
  }finally{if(timer)clearTimeout(timer);}
75
75
  }
76
- function payloadHash({deviceId,epoch,capabilityId,payload}){
76
+ export function payloadHash({deviceId,epoch,capabilityId,payload}){
77
77
  return sha(Buffer.from(canonicalJson({device_id:deviceId,epoch,capability_id:capabilityId,payload}),'utf8'));
78
78
  }
79
79
 
@@ -325,9 +325,40 @@ export function createLiveTimelineWriter({stream=process.stdout,displayMode='uni
325
325
  if(!stream||typeof stream.write!=='function') throw new Error('timeline writer stream invalid');
326
326
  if(!REMOTE_DISPLAY_MODES.includes(displayMode)) throw new Error('timeline writer display mode invalid');
327
327
  if(typeof rewrite!=='boolean') throw new Error('timeline writer rewrite invalid');
328
- let active=null;
328
+ let active=null,congested=false,needsNewline=false,suppressedSinceDrain=0,totalSuppressed=0;
329
+ // Display output is lossy under a stalled consumer; the on-disk signed audit
330
+ // and runtime logs remain authoritative. Do not enqueue unbounded text in
331
+ // Node's Writable internal buffer after write() returns false.
332
+ function emit(chunk){
333
+ const ok=stream.write(chunk);
334
+ if(ok===false){
335
+ if(typeof stream.once!=='function') throw new Error('timeline writer requires drain-capable stream under backpressure');
336
+ congested=true;
337
+ stream.once('drain',onDrain);
338
+ }
339
+ }
340
+ function onDrain(){
341
+ congested=false;
342
+ if(active||needsNewline){
343
+ active=null;needsNewline=false;
344
+ emit('\n');
345
+ if(congested) return;
346
+ }
347
+ if(suppressedSinceDrain){
348
+ const skipped=suppressedSinceDrain;
349
+ suppressedSinceDrain=0;
350
+ emit('[WARN] LIVELOG '+skipped+' presentation lines suppressed by stdout backpressure; inspect signed audit and runtime logs.\n');
351
+ }
352
+ }
353
+ function noteSuppressed(){
354
+ suppressedSinceDrain++;
355
+ totalSuppressed++;
356
+ }
329
357
  function flush(){
330
- if(active){stream.write('\n');active=null;}
358
+ if(!active) return;
359
+ active=null;
360
+ if(congested){needsNewline=true;return;}
361
+ emit('\n');
331
362
  }
332
363
  // Inspect terminal width for every emission: resize must not erase a wrapped prior line.
333
364
  // Unknown Unicode cell width falls back to append-only output, never truncation.
@@ -337,20 +368,24 @@ export function createLiveTimelineWriter({stream=process.stdout,displayMode='uni
337
368
  }
338
369
  function write(line,{event_type=null,request_id=null}={}){
339
370
  if(typeof line!=='string'||!line) return;
371
+ if(congested){noteSuppressed();return;}
340
372
  const tty=stream.isTTY===true&&rewrite;
341
373
  if(!tty||!fitsOneRow(line)){
342
374
  if(active) flush();
343
- stream.write(line+'\n');
375
+ if(congested){noteSuppressed();return;}
376
+ emit(line+'\n');
344
377
  return;
345
378
  }
346
379
  if(active&&event_type==='tool.result'&&request_id&&request_id===active.request_id){
347
- stream.write('\r\x1b[2K'+line+'\n');active=null;return;
380
+ active=null;emit('\r\x1b[2K'+line+'\n');return;
348
381
  }
349
382
  if(active) flush();
350
- if(event_type==='tool.call'&&request_id){stream.write(line);active={request_id};return;}
351
- stream.write(line+'\n');
383
+ if(congested){noteSuppressed();return;}
384
+ if(event_type==='tool.call'&&request_id){emit(line);active={request_id};return;}
385
+ emit(line+'\n');
352
386
  }
353
- return Object.freeze({write,flush,pending(){return active?{...active}:null;}});
387
+ return Object.freeze({write,flush,pending(){return active?{...active}:null;},
388
+ retention(){return Object.freeze({congested,suppressed_since_drain:suppressedSinceDrain,total_suppressed:totalSuppressed});}});
354
389
  }
355
390
 
356
391
  export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,foldHeartbeats=false,heartbeatBurstLimit=10,heartbeatWindowMs=3000,displayMode='unicode',rootPaths={},trace=false,slowCallMs=1000}={}){
@@ -549,6 +584,7 @@ export function createLiveTimelineTracker({clock=()=>Date.now(),verbose=false,fo
549
584
  (p50!==null?' | p50 '+formatDuration(p50)+' | p95 '+formatDuration(p95)+' | p99 '+formatDuration(p99)+' | sample '+durationSamples.length+'/'+durationObservedCount:'')+
550
585
  (slowest?' | slowest '+slowest.tool+' '+formatDuration(slowest.duration_ms):'')+
551
586
  ' | peak in-flight '+peakInFlight+
587
+ (pendingOverflow?' | '+pendingOverflow+' presentation spans untracked (overflow); inspect signed audit':'')+
552
588
  (foldedHeartbeatLines?' | folded '+foldedHeartbeatLines+' heartbeat lines':'')+
553
589
  (top?' | top: '+top:''),displayMode);
554
590
  }
package/src/version.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export const DEADBYTE_VERSION = '0.15.52';
1
+ export const DEADBYTE_VERSION = '0.15.54';
2
2
  export const MCP_PROTOCOL_VERSION = '2026-07-28';
3
3
  export const RECEIPT_SCHEMA_V1 = 'deadbyte.receipt.v1';
4
4
  export const RECEIPT_SCHEMA_V2 = 'deadbyte.receipt.v2';