deadbyte-mcp 0.15.39 → 0.15.42

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 (100) hide show
  1. package/DEADBYTE-MCP.cmd +8 -4
  2. package/MANIFEST.SHA256 +99 -45
  3. package/PROVENANCE.json +90 -20
  4. package/README.txt +39 -18
  5. package/RELEASE.json +85 -20
  6. package/SBOM.cdx.json +2 -2
  7. package/TEST-MATRIX.json +134 -34
  8. package/bridge/package.json +1 -1
  9. package/controller/deadbyte-controller.ps1 +132 -12
  10. package/controller/deadbyte-process-policy.json +4 -4
  11. package/docs/R64-EVIDENCE.json +95 -0
  12. package/docs/R64-REFERENCE-GAP-MATRIX.json +98 -0
  13. package/docs/R64-SCOPE.json +41 -0
  14. package/docs/R65-AUTHORITY-CONTINUITY-DESIGN.json +27 -0
  15. package/docs/R65-REFERENCE-GAP-MATRIX.json +22 -0
  16. package/package.json +7 -4
  17. package/scripts/deploy-r48.mjs +123 -7
  18. package/scripts/gate-windows-r63-prepublish.ps1 +45 -0
  19. package/scripts/gate-windows-r64-prepublish.ps1 +68 -0
  20. package/scripts/gate-windows-r65-prepublish.ps1 +65 -0
  21. package/scripts/npm-publication-r65.mjs +139 -0
  22. package/scripts/npm-release-r48.mjs +62 -37
  23. package/scripts/r64-predecessor-reproducer.mjs +32 -0
  24. package/scripts/r64-read-benchmark.mjs +84 -0
  25. package/scripts/r65-canonical-full.mjs +45 -0
  26. package/scripts/r65-focused.mjs +37 -0
  27. package/scripts/verify-r65-parent-source.mjs +75 -0
  28. package/src/activation-continuity-engine-r65.mjs +225 -0
  29. package/src/activation-lock-r65.mjs +155 -0
  30. package/src/activation-preflight-r65.mjs +197 -0
  31. package/src/authority-continuity-r65.mjs +541 -0
  32. package/src/authority-epoch-cli-r65.mjs +10 -0
  33. package/src/autonomous-policy.mjs +7 -3
  34. package/src/coding-plane.mjs +12 -3
  35. package/src/compact-gateway-mcp-tools.mjs +1 -1
  36. package/src/deadbyte-cli.mjs +16 -2
  37. package/src/desktop-policy-r40.mjs +6 -3
  38. package/src/doctor-transactions-r58.mjs +1 -1
  39. package/src/host-plane.mjs +9 -3
  40. package/src/machine-fs-mcp-tools.mjs +1 -1
  41. package/src/machine-fs.mjs +7 -4
  42. package/src/process-policy.mjs +7 -4
  43. package/src/remote-authority.mjs +20 -1
  44. package/src/runtime-update.mjs +15 -5
  45. package/src/version.mjs +1 -1
  46. package/test/audit-context.test.mjs +15 -4
  47. package/test/autonomous-local-supervisor.test.mjs +7 -6
  48. package/test/autonomous-supervisor-integration.test.mjs +8 -7
  49. package/test/capability-resolver.test.mjs +11 -5
  50. package/test/codex-autonomous-r44.test.mjs +7 -6
  51. package/test/coding-observation.test.mjs +4 -3
  52. package/test/commander-real-parity-r47.test.mjs +5 -4
  53. package/test/commander-torture-r47.test.mjs +5 -4
  54. package/test/desktop-policy-r40.test.mjs +6 -4
  55. package/test/desktop-runtime-r40.test.mjs +8 -3
  56. package/test/host-inline-r45.test.mjs +4 -3
  57. package/test/machine-fs-runtime.test.mjs +4 -3
  58. package/test/process-longrun-r36.test.mjs +3 -2
  59. package/test/process-policy.test.mjs +10 -6
  60. package/test/process-runtime.test.mjs +9 -6
  61. package/test/r52-host-revocation.test.mjs +1 -0
  62. package/test/r59-tool-surface-compat.test.mjs +14 -4
  63. package/test/r62-release-contract.test.mjs +2 -1
  64. package/test/r63-one-shot-coherence.test.mjs +38 -0
  65. package/test/r63-release-contract.test.mjs +59 -0
  66. package/test/r64-machine-fs-large-read.test.mjs +136 -0
  67. package/test/r64-release-contract.test.mjs +52 -0
  68. package/test/r65-activation-lock-recovery.test.mjs +72 -0
  69. package/test/r65-activation-lock.test.mjs +59 -0
  70. package/test/r65-activation-orchestration.test.mjs +179 -0
  71. package/test/r65-activation-preflight.test.mjs +35 -0
  72. package/test/r65-authority-continuity-decision.test.mjs +58 -0
  73. package/test/r65-authority-durable-policy-binding.test.mjs +34 -0
  74. package/test/r65-authority-epoch-cli.test.mjs +125 -0
  75. package/test/r65-authority-epoch-races.test.mjs +87 -0
  76. package/test/r65-authority-epoch-recovery.test.mjs +82 -0
  77. package/test/r65-authority-epoch-store.test.mjs +94 -0
  78. package/test/r65-authority-fixture.mjs +41 -0
  79. package/test/r65-authority-grant-capture.test.mjs +37 -0
  80. package/test/r65-authority-observation-epoch.test.mjs +25 -0
  81. package/test/r65-authority-policy-envelope.test.mjs +46 -0
  82. package/test/r65-authority-restored-safe.test.mjs +75 -0
  83. package/test/r65-authority-runtime-epoch.test.mjs +115 -0
  84. package/test/r65-authority-runtime-policy-binding.test.mjs +29 -0
  85. package/test/r65-authority-test-trust.mjs +20 -0
  86. package/test/r65-cli-invocation.test.mjs +38 -0
  87. package/test/r65-controller-authority-epoch-live.test.mjs +100 -0
  88. package/test/r65-controller-authority-epoch.test.mjs +37 -0
  89. package/test/r65-deploy-preflight.test.mjs +55 -0
  90. package/test/r65-epoch-native-activation-integration.test.mjs +141 -0
  91. package/test/r65-epoch-native-continuity-engine.test.mjs +112 -0
  92. package/test/r65-epoch-native-cross-plane.test.mjs +95 -0
  93. package/test/r65-legacy-authority-migration.test.mjs +63 -0
  94. package/test/r65-legacy-auto-restore-rejection.test.mjs +17 -0
  95. package/test/r65-npm-publication.test.mjs +114 -0
  96. package/test/r65-parent-source-verifier.test.mjs +63 -0
  97. package/test/r65-release-contract.test.mjs +74 -0
  98. package/test/r65-transaction-precondition.test.mjs +36 -0
  99. package/test/remote-session-cli.test.mjs +13 -11
  100. package/test/standing-grants.test.mjs +20 -4
@@ -20,7 +20,7 @@ import { windowsPowerShellEnvironment } from './runtime-update.mjs';
20
20
  import { resolveStateRoot } from './state-root.mjs';
21
21
  import {foregroundIdentityMatches,queryForegroundIdentity,validForegroundIdentity} from './foreground-process-identity.mjs';
22
22
  import {acquireForegroundSessionLock} from './foreground-session-lock.mjs';
23
- import { AUTHORITY_PLANES, authorityVector, parseAuthorityObservation, assertAuthorityVector, assertRuntimeReadyObservation, authorityBanner } from './remote-authority.mjs';
23
+ import { AUTHORITY_PLANES, authorityVector, authorityRestorePlan, parseAuthorityObservation, assertAuthorityVector, assertRuntimeReadyObservation, authorityBanner } from './remote-authority.mjs';
24
24
  import { decodeRemoteLogBytes, boundedRemoteDiagnostic, safeRemoteLogPrefixLength } from './remote-log-decode-r60.mjs';
25
25
 
26
26
  const REMOTE_COMMANDS=new Set([
@@ -137,6 +137,20 @@ async function synchronizeForegroundRuntime(ctx,{displayMode='unicode'}={}){
137
137
  }
138
138
  });
139
139
  }
140
+ async function synchronizeOneShotRuntime(ctx,{displayMode='ascii'}={}){
141
+ const active=await readActiveRuntimeBinding(ctx.root);
142
+ if(!active||active.version===DEADBYTE_VERSION) return synchronizeForegroundRuntime(ctx,{displayMode});
143
+ const before=observedAuthority(ctx);
144
+ const expected=Object.fromEntries(AUTHORITY_PLANES.map(plane=>[plane,before.authority[plane].armed]));
145
+ const result=await synchronizeForegroundRuntime(ctx,{displayMode});
146
+ if(result.status!=='upgraded') return result;
147
+ for(const command of authorityRestorePlan(before)){
148
+ const restored=invokeController(ctx,command,{capture:true,allowFailure:true,echoCaptured:false,timeoutMs:REMOTE_STATUS_TIMEOUT_MS});
149
+ if(restored.status!==0) throw new Error(`runtime coherence authority restore '${command}' failed exit=${restored.status}`);
150
+ }
151
+ assertAuthorityVector(observedAuthority(ctx),expected);
152
+ return result;
153
+ }
140
154
  async function readControllerStartFailure(root,command,{maxChars=2048}={}){
141
155
  const file=path.join(root,'runtime','controller-start-failure.json');
142
156
  let raw;
@@ -629,7 +643,7 @@ async function runRemote(args){
629
643
  if(positional.length>1) throw new Error('remote accepts one controller command');
630
644
  const command=positional[0];if(!REMOTE_COMMANDS.has(command)) throw new Error(`unsupported remote command '${command}'`);
631
645
  const ctx=await remoteContext();
632
- await reportVersionCoherence(ctx.root,{displayMode:'ascii'});
646
+ await synchronizeOneShotRuntime(ctx,{displayMode:'ascii'});
633
647
  const result=invokeController(ctx,command,{allowFailure:true});
634
648
  process.exitCode=result.status;
635
649
  }
@@ -2,6 +2,7 @@ import { createHash } from 'node:crypto';
2
2
  import { mkdir, readFile } from 'node:fs/promises';
3
3
  import path from 'node:path';
4
4
  import { decodeUtf8Strict } from './text-policy.mjs';
5
+ import { verifyStandingGrantEpoch } from './authority-continuity-r65.mjs';
5
6
 
6
7
  export const DESKTOP_POLICY_SCHEMA='deadbyte.desktop-policy.v1';
7
8
  export const DESKTOP_GRANT_SCHEMA='deadbyte.desktop-grant.v1';
@@ -84,8 +85,8 @@ export async function loadDesktopPolicy(policyPath){
84
85
 
85
86
  export async function desktopSessionStatus(policy){
86
87
  if(!policy||policy.schema!==DESKTOP_POLICY_SCHEMA) throw new Error('desktop policy invalid');
87
- let grant;
88
- try{grant=JSON.parse(decodeUtf8Strict(await readFile(policy.grant_file)));}
88
+ let grant,grantBytes;
89
+ try{grantBytes=await readFile(policy.grant_file);grant=JSON.parse(decodeUtf8Strict(grantBytes));}
89
90
  catch(error){
90
91
  return Object.freeze({configured:true,armed:false,reason:error?.code==='ENOENT'?'disarmed':'invalid-grant',standing:false,action_kinds:[]});
91
92
  }
@@ -96,8 +97,10 @@ export async function desktopSessionStatus(policy){
96
97
  try{granted=actions(grant.action_kinds,'grant.action_kinds');}
97
98
  catch{return Object.freeze({configured:true,armed:false,reason:'actions-invalid',standing:false,action_kinds:[]});}
98
99
  if(granted.some(item=>!policy.allowed_actions.includes(item))) return Object.freeze({configured:true,armed:false,reason:'actions-outside-policy',standing:false,action_kinds:[]});
100
+ const epoch=await verifyStandingGrantEpoch({plane:'desktop',grantFile:policy.grant_file,grantBytes,grantInstanceNonce:grant.instance_nonce,policyPath:policy.policy_path});
101
+ if(!epoch.trusted)return Object.freeze({configured:true,armed:false,reason:epoch.reason,standing:false,instance_nonce:grant.instance_nonce,action_kinds:granted});
99
102
  return Object.freeze({
100
- configured:true,armed:true,reason:'standing-grant',standing:true,instance_nonce:grant.instance_nonce,action_kinds:granted
103
+ configured:true,armed:true,reason:'standing-grant',standing:true,instance_nonce:grant.instance_nonce,action_kinds:granted,authority_epoch:epoch
101
104
  });
102
105
  }
103
106
 
@@ -87,7 +87,7 @@ export async function inspectDoctorTransactions(explicitRoot,{maxEntries=1024}={
87
87
  }
88
88
 
89
89
  if(body.phase==='superseded'){terminal++;continue;}
90
- if(body.phase==='failed_receipted'){
90
+ if(body.phase==='failed_receipted'||body.phase==='precondition_rejected'){
91
91
  add('FAIL','Transaction journal claims a signed failure receipt but the receipt is missing.',{state_sha256:digest,check:'missing_failure_receipt',recorded_phase:body.phase});
92
92
  continue;
93
93
  }
@@ -11,6 +11,7 @@ import { spawn } from 'node:child_process';
11
11
  import { decodeUtf8Strict } from './text-policy.mjs';
12
12
  import { isInside, validatePortableRelativePath } from './path-policy.mjs';
13
13
  import { atomicMutation } from './atomic-mutation.mjs';
14
+ import { verifyStandingGrantEpoch } from './authority-continuity-r65.mjs';
14
15
 
15
16
  export const HOST_POLICY_SCHEMA = 'deadbyte.host-policy.v1';
16
17
  export const HOST_POLICY_SCHEMA_V2 = 'deadbyte.host-policy.v2';
@@ -164,9 +165,10 @@ export async function loadHostPolicy(policyPath) {
164
165
  }
165
166
 
166
167
  export async function assertHostArmed(policy, now = new Date()) {
167
- let arm;
168
+ let arm,armBytes;
168
169
  try {
169
- arm = JSON.parse(decodeUtf8Strict(await readFile(policy.arm_file)));
170
+ armBytes=await readFile(policy.arm_file);
171
+ arm = JSON.parse(decodeUtf8Strict(armBytes));
170
172
  } catch (error) {
171
173
  throw new Error(`privileged host plane is disarmed: ${error?.code === 'ENOENT' ? 'arm file missing' : 'invalid arm file'}`);
172
174
  }
@@ -175,7 +177,11 @@ export async function assertHostArmed(policy, now = new Date()) {
175
177
  if (typeof arm.instance_nonce !== 'string' || !SHA256_RE.test(arm.instance_nonce)) {
176
178
  throw new Error('privileged host plane is disarmed: nonce invalid');
177
179
  }
178
- if (arm.schema === HOST_GRANT_SCHEMA) return { arm, standing:true, remaining_ms:null };
180
+ if (arm.schema === HOST_GRANT_SCHEMA) {
181
+ const epoch=await verifyStandingGrantEpoch({plane:'host',grantFile:policy.arm_file,grantBytes:armBytes,grantInstanceNonce:arm.instance_nonce,policyPath:policy.policy_path});
182
+ if(!epoch.trusted)throw new Error(`privileged host plane is disarmed: ${epoch.reason}`);
183
+ return { arm, standing:true, remaining_ms:null, authority_epoch:epoch };
184
+ }
179
185
  const expires = Date.parse(arm.expires_at_utc);
180
186
  if (!Number.isFinite(expires) || expires <= now.getTime()) throw new Error('privileged host plane is disarmed: arm expired');
181
187
  return { arm, standing:false, remaining_ms: expires - now.getTime() };
@@ -38,7 +38,7 @@ export function registerMachineFsTools(server, { runtime }) {
38
38
  });
39
39
  server.registerTool('machine_fs_read', {
40
40
  description:'Read one bounded byte range from a root-relative regular file.',
41
- inputSchema:z.strictObject({ root_id:id, path:rel, offset:z.number().int().min(-4*1024*1024).max(4*1024*1024).default(0), max_bytes:z.number().int().min(1).max(4*1024*1024).default(65536), hash_file:z.boolean().default(false), timeout_ms:z.number().int().min(100).max(180000).default(30000) }),
41
+ inputSchema:z.strictObject({ root_id:id, path:rel, offset:z.number().int().min(-4*1024*1024).max(Number.MAX_SAFE_INTEGER).default(0), max_bytes:z.number().int().min(1).max(4*1024*1024).default(65536), hash_file:z.boolean().default(false), timeout_ms:z.number().int().min(100).max(180000).default(30000) }),
42
42
  outputSchema:machineFsReadOutputSchema
43
43
  }, async ({root_id,path,offset,max_bytes,hash_file,timeout_ms}) => {
44
44
  try { return ok(machineFsReadOutputSchema, await runtime.read({rootId:root_id,relativePath:path,offset,maxBytes:max_bytes,hashFile:hash_file,timeoutMs:timeout_ms})); }
@@ -21,10 +21,10 @@ const MAX_DIRECTORY_IDENTITY_ENTRIES = 4096;
21
21
  const sha256Bytes = bytes => createHash('sha256').update(bytes).digest('hex');
22
22
  const sha256Object = value => sha256Bytes(Buffer.from(canonicalJson(value), 'utf8'));
23
23
  const nowUtc = () => new Date().toISOString();
24
- async function sha256File(file) {
24
+ async function sha256File(file, signal = undefined) {
25
25
  const hash = createHash('sha256');
26
26
  await new Promise((resolve, reject) => {
27
- const stream = createReadStream(file);
27
+ const stream = createReadStream(file, { signal });
28
28
  stream.on('data', chunk => hash.update(chunk));
29
29
  stream.once('error', reject);
30
30
  stream.once('end', resolve);
@@ -35,11 +35,13 @@ async function sha256File(file) {
35
35
  const machineFsReadWorkerPath = fileURLToPath(new URL('./machine-fs-read-worker.mjs', import.meta.url));
36
36
  async function boundedReadInProcess({ file, offset, length, hashFile, timeoutMs }) {
37
37
  const handle = await open(file, 'r');
38
+ const abortController = new AbortController();
38
39
  let timer;
39
40
  let settled = false;
40
41
  const abort = new Promise((_, reject) => {
41
42
  timer = setTimeout(() => {
42
43
  settled = true;
44
+ abortController.abort();
43
45
  void handle.close().catch(() => {});
44
46
  reject(new Error(`machine fs read timed out after ${timeoutMs}ms`));
45
47
  }, timeoutMs);
@@ -50,7 +52,7 @@ async function boundedReadInProcess({ file, offset, length, hashFile, timeoutMs
50
52
  const buffer = Buffer.alloc(length);
51
53
  const result = length ? await handle.read(buffer, 0, length, offset) : { bytesRead: 0 };
52
54
  let fileSha256 = null;
53
- if (hashFile) fileSha256 = await sha256File(file);
55
+ if (hashFile) fileSha256 = await sha256File(file, abortController.signal);
54
56
  return { schema: 'deadbyte.machine-fs-read-worker.v1', bytes_read: result.bytesRead,
55
57
  data_base64: buffer.subarray(0, result.bytesRead).toString('base64'), file_sha256: fileSha256 };
56
58
  })();
@@ -408,13 +410,14 @@ export function createMachineFsRuntime({ policy, signingKeyPath, verifyKeyPath,
408
410
  return { status: 'ok', root_id: rootId, path: found.relative, entries, next_cursor: nextCursor };
409
411
  }
410
412
  async function readRange({ rootId, relativePath, offset = 0, maxBytes = 65536, hashFile = false, timeoutMs = 30000 }) {
411
- if (!Number.isInteger(offset) || offset < -policy.max_read_bytes || offset > policy.max_read_bytes) throw new Error(`machine fs read offset must be in [-${policy.max_read_bytes}, ${policy.max_read_bytes}]`);
413
+ if (!Number.isSafeInteger(offset) || offset < -policy.max_read_bytes) throw new Error(`machine fs read offset must be a safe integer >= -${policy.max_read_bytes}`);
412
414
  if (!Number.isInteger(maxBytes) || maxBytes < 1 || maxBytes > policy.max_read_bytes) throw new Error(`machine fs max_bytes must be in [1, ${policy.max_read_bytes}]`);
413
415
  if (!Number.isInteger(timeoutMs) || timeoutMs < 100 || timeoutMs > 180000) throw new Error('machine fs read timeout_ms must be in [100, 180000]');
414
416
  const grant = await assertCodingReadAllowed(policy, rootId);
415
417
  const found = await resolveExisting(policy, grant, rootId, relativePath, 'read', false);
416
418
  const info = await lstat(found.resolved);
417
419
  if (!info.isFile()) throw new Error('machine fs read target must be a regular file');
420
+ if (!Number.isSafeInteger(info.size)) throw new Error('machine fs read file size exceeds safe integer range');
418
421
  const effectiveOffset = offset < 0 ? Math.max(0, info.size + offset) : offset;
419
422
  if (effectiveOffset > info.size) throw new Error('machine fs read offset exceeds file size');
420
423
  const length = Math.min(maxBytes, info.size - effectiveOffset);
@@ -2,6 +2,7 @@ import { createHash } from 'node:crypto';
2
2
  import { lstat, mkdir, readFile, realpath, stat } from 'node:fs/promises';
3
3
  import path from 'node:path';
4
4
  import { decodeUtf8Strict } from './text-policy.mjs';
5
+ import { verifyStandingGrantEpoch } from './authority-continuity-r65.mjs';
5
6
 
6
7
  export const PROCESS_POLICY_SCHEMA = 'deadbyte.process-policy.v1';
7
8
  export const PROCESS_GRANT_SCHEMA = 'deadbyte.process-grant.v1';
@@ -124,8 +125,8 @@ function configuredStatus(policy, reason) {
124
125
  policy_sha256: policy.policy_sha256
125
126
  };
126
127
  }export async function processSessionStatus(policy) {
127
- let grant;
128
- try { grant = JSON.parse(decodeUtf8Strict(await readFile(policy.grant_file))); }
128
+ let grant,grantBytes;
129
+ try { grantBytes=await readFile(policy.grant_file); grant = JSON.parse(decodeUtf8Strict(grantBytes)); }
129
130
  catch (error) { return configuredStatus(policy, error?.code === 'ENOENT' ? 'disarmed' : 'invalid-grant'); }
130
131
  if (!grant || grant.schema !== PROCESS_GRANT_SCHEMA) return configuredStatus(policy, 'grant-schema');
131
132
  if (grant.policy_sha256 !== policy.policy_sha256) return configuredStatus(policy, 'policy-digest');
@@ -136,10 +137,12 @@ function configuredStatus(policy, reason) {
136
137
  grant.root_ids.some(id => !ID_RE.test(id) || !policy.roots[id])) return configuredStatus(policy, 'roots-invalid');
137
138
  const profileIds = [...new Set(grant.profile_ids)].sort();
138
139
  const rootIds = [...new Set(grant.root_ids)].sort();
140
+ const epoch=await verifyStandingGrantEpoch({plane:'process',grantFile:policy.grant_file,grantBytes,grantInstanceNonce:grant.instance_nonce,policyPath:policy.policy_path});
141
+ if(!epoch.trusted)return configuredStatus(policy,epoch.reason);
139
142
  return {
140
143
  configured: true, armed: true, reason: 'standing-grant', expires_at_utc: null,
141
144
  root_ids: rootIds, profile_ids: profileIds, policy_sha256: policy.policy_sha256,
142
- instance_nonce: grant.instance_nonce, standing: true
145
+ instance_nonce: grant.instance_nonce, standing: true, authority_epoch:epoch
143
146
  };
144
147
  }
145
148
 
@@ -168,4 +171,4 @@ export async function recheckProcessAuthority(policy, initial, profileId = null,
168
171
  current.root_ids.some((id, index) => id !== initial.root_ids[index]);
169
172
  if (changed) throw new Error('process authority changed during operation');
170
173
  return current;
171
- }
174
+ }
@@ -11,6 +11,16 @@ export function authorityVector(mode='default'){
11
11
  });
12
12
  }
13
13
 
14
+ export function authorityRestorePlan(snapshot){
15
+ const disarm=[],arm=[];
16
+ for(const plane of AUTHORITY_PLANES){
17
+ const entry=snapshot?.authority?.[plane];
18
+ if(!entry||typeof entry.armed!=='boolean') throw new Error(`authority ${plane} observation invalid`);
19
+ (entry.armed?arm:disarm).push(`${entry.armed?'arm':'disarm'}${plane}`);
20
+ }
21
+ return Object.freeze([...disarm,...arm]);
22
+ }
23
+
14
24
  export function parseAuthorityObservation(raw){
15
25
  let value;
16
26
  try{value=typeof raw==='string'?JSON.parse(raw.replace(/^\uFEFF/,'')):raw;}
@@ -22,7 +32,16 @@ export function parseAuthorityObservation(raw){
22
32
  const entry=value.authority[plane];
23
33
  if(!entry||typeof entry!=='object'||typeof entry.armed!=='boolean'||typeof entry.configured!=='boolean'||typeof entry.reason!=='string')
24
34
  throw new Error(`controller authority ${plane} observation invalid`);
25
- authority[plane]=Object.freeze({configured:entry.configured,armed:entry.armed,reason:entry.reason.slice(0,128)});
35
+ const hasEpochSeq=Object.hasOwn(entry,'epoch_seq'),hasEpochSha=Object.hasOwn(entry,'epoch_sha256');
36
+ if(hasEpochSeq!==hasEpochSha)throw new Error(`controller authority ${plane} epoch observation incomplete`);
37
+ let epochSeq=null,epochSha256=null;
38
+ if(hasEpochSeq){
39
+ if(!Number.isSafeInteger(entry.epoch_seq)||entry.epoch_seq<1||typeof entry.epoch_sha256!=='string'||!/^[0-9a-f]{64}$/.test(entry.epoch_sha256))
40
+ throw new Error(`controller authority ${plane} epoch observation invalid`);
41
+ epochSeq=entry.epoch_seq;epochSha256=entry.epoch_sha256;
42
+ }
43
+ authority[plane]=Object.freeze({configured:entry.configured,armed:entry.armed,reason:entry.reason.slice(0,128),
44
+ epoch_seq:epochSeq,epoch_sha256:epochSha256});
26
45
  }
27
46
  return Object.freeze({...value,authority:Object.freeze(authority)});
28
47
  }
@@ -859,7 +859,7 @@ async function captureRuntime(ctx){
859
859
  const bridge=await readLedger(ctx,'bridge'); const cloud=await readLedger(ctx,'cloud'); const core=coreProcess(ctx.core_root,bridge);
860
860
  return {status,status_text:statusText.trim(),bridge,cloud,core};
861
861
  }
862
- export async function capturePredecessor(ctx,recoverOfflinePredecessor){
862
+ export async function capturePredecessor(ctx,recoverOfflinePredecessor,{allowHostDisarm=true}={}){
863
863
  let statusText,status;
864
864
  try{
865
865
  statusText=controllerCaptured(ctx,'status',15000);
@@ -894,6 +894,7 @@ export async function capturePredecessor(ctx,recoverOfflinePredecessor){
894
894
  if(!recoverOfflinePredecessor) throw new Error(`slot update requires ONLINE runtime: ${statusText}`);
895
895
  if(!status.offline||status.bridge_pid||status.cloud_pid||status.local_ready) throw new Error(`offline predecessor recovery requires clean OFFLINE controller state: ${statusText}`);
896
896
  if(status.host_armed){
897
+ if(!allowHostDisarm) throw new Error('offline predecessor recovery requires host plane DISARMED');
897
898
  controllerCaptured(ctx,'disarmhost',15000);
898
899
  statusText=controllerCaptured(ctx,'status',15000);
899
900
  status=parseControllerStatus(statusText);
@@ -1235,7 +1236,7 @@ async function restoreRollbackLaunchers(plan){
1235
1236
  const UPDATE_PHASE_RANK=Object.freeze({
1236
1237
  initial:0,candidate_prepared:10,old_runtime_verified:20,shutdown_handoff_armed:25,supervisor_spawned:30,
1237
1238
  authority_revoked:40,old_runtime_stopped:50,workspace_ready:60,launcher_installed:70,pointer_committed:80,
1238
- fresh_runtime_verified:90,authority_verified:100,completed:110,superseded:120,failed:1000,failed_receipted:1001,failed_unreceipted:1002
1239
+ fresh_runtime_verified:90,authority_verified:100,completed:110,superseded:120,failed:1000,failed_receipted:1001,failed_unreceipted:1002,precondition_rejected:1003
1239
1240
  });
1240
1241
  function phaseRank(phase){ return UPDATE_PHASE_RANK[String(phase)]??-1; }
1241
1242
  function activeTransitionPhase(phase){
@@ -1657,6 +1658,9 @@ export async function recoverInterruptedSlotUpdates({stateRoot=DEFAULT_STATE_ROO
1657
1658
  }
1658
1659
 
1659
1660
  export async function performSlotUpdate({stage,developmentRoot,expectedVersion,expectedManifest,sourceRoot=null,stateRoot=DEFAULT_STATE_ROOT,restoreStandingGrants=false,recoverOfflinePredecessor=false,cloudTransport=null,bridgeBootstrap=null,processPolicyBootstrap=null,hostPolicyBootstrap=null,codingPolicyBootstrap=null,desktopPolicyBootstrap=null,semanticLspBootstrap=null,enableMachineFs=false,enableObservation=false,enableDesktop=false,enableAutonomousSupervisor=false,hostCommandPins=[]}){
1661
+ // R64 grants have no signed revocation epoch. The historical boolean restore
1662
+ // cannot prove continuity, and must never materialize a successor grant.
1663
+ if(restoreStandingGrants)throw new Error('R65 standing grant auto-restore disabled: explicit reauthorization required');
1660
1664
  stateRoot=resolveStateRoot(stateRoot);
1661
1665
  if(process.platform!=='win32') throw new Error('R27 slot update is Windows-only');
1662
1666
  const state=path.resolve(stateRoot),stageRoot=path.resolve(stage),development=assertDevelopmentRootSafe(developmentRoot,state);
@@ -1669,12 +1673,17 @@ export async function performSlotUpdate({stage,developmentRoot,expectedVersion,e
1669
1673
  await writeState(statePath,journal,'initial');
1670
1674
  let pointerInfo=null,control=null,predecessor=null,oldRuntime=null,priorStatus=null,authorityPreparation=null,workspace=null,launchers=null,newPointer=null;
1671
1675
  let stableMcpUrl=null,mcpUrlSnapshot=null;
1676
+ let mutationBegan=false;
1672
1677
  try{
1673
1678
  pointerInfo=await loadPointerIfPresent(state);
1674
1679
  const stageVerified=await verifyPackageRoot(stageRoot,expectedVersion,expectedManifest,'stage');
1675
1680
  const developmentVerified=await verifyPackageRoot(development,expectedVersion,expectedManifest,'development');
1676
- const slot=await materializeReleaseSlot({source:stageRoot,stateRoot:state,version:expectedVersion,manifestSha256:expectedManifest});
1677
1681
  control=await loadControlInputs({stateRoot:state,sourceRoot,pointerInfo,bridgeBootstrap});
1682
+ // Read-only predecessor authority and process identity must be safe before
1683
+ // materializing a slot or changing any stable runtime file.
1684
+ await capturePredecessor(control.ctx,recoverOfflinePredecessor,{allowHostDisarm:false});
1685
+ mutationBegan=true;
1686
+ const slot=await materializeReleaseSlot({source:stageRoot,stateRoot:state,version:expectedVersion,manifestSha256:expectedManifest});
1678
1687
  const hostBootstrap=await prepareHostPolicyBootstrap(hostPolicyBootstrap);if(hostBootstrap) control.hostPolicy=hostBootstrap.policy;
1679
1688
  const codingBootstrap=await prepareCodingPolicyBootstrap(codingPolicyBootstrap);if(codingBootstrap) control.codingPolicy=codingBootstrap.policy;
1680
1689
  const processBootstrap=await prepareProcessPolicyBootstrap(processPolicyBootstrap);
@@ -1722,7 +1731,7 @@ export async function performSlotUpdate({stage,developmentRoot,expectedVersion,e
1722
1731
  bridge:{working_directory:control.config.Bridge.WorkingDirectory,script_path:control.config.Bridge.ScriptPath,script_sha256:control.config.Bridge.ScriptSha256},
1723
1732
  host_repin:{requested:hostCommandPins.length>0,repinned:authorityPreparation.hostRepinned},coding_repin:{requested:hostCommandPins.length>0,repinned:authorityPreparation.codingRepinned},
1724
1733
  new_pointer:publicPointer(newPointer)});
1725
- predecessor=await capturePredecessor(control.ctx,recoverOfflinePredecessor);oldRuntime=predecessor.runtime;priorStatus=predecessor.status;
1734
+ predecessor=await capturePredecessor(control.ctx,recoverOfflinePredecessor,{allowHostDisarm:false});oldRuntime=predecessor.runtime;priorStatus=predecessor.status;
1726
1735
  await writeState(statePath,journal,'old_runtime_verified',{predecessor_mode:predecessor.mode,old_runtime:generationTuple(oldRuntime),
1727
1736
  prior_authority:{process_armed:priorStatus.process_armed,coding_armed:priorStatus.coding_armed,autonomous_armed:priorStatus.autonomous_armed,desktop_armed:priorStatus.desktop_armed,host_armed:priorStatus.host_armed}});
1728
1737
  const handoff=await writeHandoff({stateRoot:state,state:journal,journal,statePath,evidenceDir,key,pointerInfo,newPointer,predecessor,priorStatus,workspace,launchers,stableMcpUrl,mcpUrlSnapshot,controlCtx:control.ctx});
@@ -1730,12 +1739,13 @@ export async function performSlotUpdate({stage,developmentRoot,expectedVersion,e
1730
1739
  return {ok:true,handed_off:true,completed:false,update_id:updateId,state_path:statePath,handoff_path:handoff.path,handoff_receipt_sha256:handoff.receipt.receipt_sha256,supervisor_pid:supervisor.pid,pointer:newPointer};
1731
1740
  }catch(error){
1732
1741
  const failure={schema:FAILURE_SCHEMA,update_id:updateId,release_version:expectedVersion,candidate_manifest_sha256:expectedManifest,failed_at_utc:new Date().toISOString(),
1742
+ result:mutationBegan?'MUTATION_OUTCOME_REQUIRES_RECONCILIATION':'PRECONDITION_REJECTED',
1733
1743
  phase:journal.phase??'unknown',error:{name:error?.name??'Error',code:error?.code??null,message:String(error?.message??error)},
1734
1744
  predecessor_mode:predecessor?.mode??null,prior_pointer:publicPointer(pointerInfo?.pointer??null),attempted_pointer:publicPointer(newPointer),old_runtime:generationTuple(oldRuntime),
1735
1745
  rollback:{attempted:false,new_runtime_stopped:false,pointer_restored:false,launchers_restored:false,workspace_restored:false,mcp_url_restored:false,old_runtime_restarted:false,authority_restored:false,errors:[]}};
1736
1746
  await writeState(statePath,journal,'failed',{failure}).catch(()=>{});
1737
1747
  let failureReceipt=null;
1738
- try{ failureReceipt=await writeEnvelope(failurePath,failure,key);await writeState(statePath,journal,'failed_receipted',{failure_receipt_path:failurePath,failure_receipt_sha256:failureReceipt.receipt_sha256});}
1748
+ try{ failureReceipt=await writeEnvelope(failurePath,failure,key);await writeState(statePath,journal,mutationBegan?'failed_receipted':'precondition_rejected',{failure_receipt_path:failurePath,failure_receipt_sha256:failureReceipt.receipt_sha256});}
1739
1749
  catch(receiptError){ await writeState(statePath,journal,'failed_unreceipted',{failure_receipt_error:String(receiptError?.message??receiptError)}).catch(()=>{}); }
1740
1750
  const wrapped=new Error('R27 slot update handoff failed: '+(error?.message??error));wrapped.cause=error;wrapped.statePath=statePath;wrapped.failureReceiptPath=failureReceipt?failurePath:null;throw wrapped;
1741
1751
  }
package/src/version.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export const DEADBYTE_VERSION = '0.15.39';
1
+ export const DEADBYTE_VERSION = '0.15.42';
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';
@@ -6,6 +6,7 @@ import os from 'node:os';
6
6
  import path from 'node:path';
7
7
  import { releaseId } from '../src/release-slot.mjs';
8
8
  import { createAuditContextProvider } from '../src/audit-context.mjs';
9
+ import { writeBoundStandingGrant } from './r65-authority-fixture.mjs';
9
10
 
10
11
  const H=x=>x.repeat(64);
11
12
  const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
@@ -19,10 +20,20 @@ test('audit context binds verified active release, generation, machine and all a
19
20
  const manifestSha=sha(manifest);const generationRoot=path.join(root,'generations','g-test');await mkdir(generationRoot,{recursive:true});
20
21
  const generationSha=H('2');const rid=releaseId('0.8.7',manifestSha);
21
22
  await writeFile(path.join(root,'active-release.json'),JSON.stringify({schema:'deadbyte.active-release.v1',release_id:rid,version:'0.8.7',manifest_sha256:manifestSha,release_root:pkg,generation_root:generationRoot,generation_manifest_sha256:generationSha,launcher_sha256:H('3'),config_path:path.join(generationRoot,'controller-config.psd1')}));
22
- const processPolicy={grant_file:path.join(root,'process-grant.json'),policy_sha256:H('4'),roots:{core:{}},profiles:{dev:{}}};
23
- await writeFile(processPolicy.grant_file,JSON.stringify({schema:'deadbyte.process-grant.v1',policy_sha256:processPolicy.policy_sha256,instance_nonce:H('5'),profile_ids:['dev'],root_ids:['core']}));
24
- const hostPolicy={arm_file:path.join(root,'host-grant.json'),policy_sha256:H('6')};
25
- await writeFile(hostPolicy.arm_file,JSON.stringify({schema:'deadbyte.host-grant.v2',policy_sha256:hostPolicy.policy_sha256,instance_nonce:H('7')}));
23
+ const processPolicyFile=path.join(root,'process-policy.json');
24
+ const processPolicyBytes=Buffer.from(JSON.stringify({schema:'deadbyte.process-policy.v1',enabled:true,
25
+ grant_file:path.join(root,'runtime','process-grant.json'),state_dir:path.join(root,'process-state'),
26
+ native_host:{path:process.execPath,sha256:H('a')},roots:{core:{path:root,deny_paths:[]}},
27
+ profiles:{dev:{executable:process.execPath,executable_sha256:H('a'),root_ids:['core'],prefix_args:[],transport:'pipe',allow_raw_input:false,max_args:8,max_arg_bytes:1024,max_sessions:1,max_input_bytes:4096,max_retained_output_bytes:4096}}}));
28
+ await writeFile(processPolicyFile,processPolicyBytes);
29
+ const processPolicy={grant_file:path.join(root,'runtime','process-grant.json'),policy_path:processPolicyFile,policy_sha256:sha(processPolicyBytes),roots:{core:{}},profiles:{dev:{}}};
30
+ await writeBoundStandingGrant('process',processPolicy.grant_file,{schema:'deadbyte.process-grant.v1',policy_sha256:processPolicy.policy_sha256,instance_nonce:H('5'),profile_ids:['dev'],root_ids:['core']});
31
+ const hostPolicyFile=path.join(root,'host-policy.json');
32
+ const hostPolicyBytes=Buffer.from(JSON.stringify({schema:'deadbyte.host-policy.v2',enabled:true,
33
+ arm_file:path.join(root,'runtime','host-grant.json'),roots:{core:{path:root,read:true,write:false,exec:false,deny_paths:[]}},commands:{},max_read_bytes:4096,max_write_bytes:4096}));
34
+ await writeFile(hostPolicyFile,hostPolicyBytes);
35
+ const hostPolicy={arm_file:path.join(root,'runtime','host-grant.json'),policy_path:hostPolicyFile,policy_sha256:sha(hostPolicyBytes)};
36
+ await writeBoundStandingGrant('host',hostPolicy.arm_file,{schema:'deadbyte.host-grant.v2',policy_sha256:hostPolicy.policy_sha256,instance_nonce:H('7')});
26
37
  const machineRuntime={status:async()=>({status:'ok',release_id:rid,release_manifest_sha256:manifestSha,generation_manifest_sha256:generationSha,machine_id:H('8'),identity_generation:9,online_epoch:H('9')})};
27
38
  const codingRuntime={sessionStatus:async()=>({status:'ok',configured:true,armed:true,reason:'standing-grant'})};
28
39
  const autonomousRuntime={sessionStatus:async()=>({status:'ok',configured:true,armed:true,autonomous_armed:true,autonomous_reason:'standing-grant'})};
@@ -18,6 +18,7 @@ import {
18
18
  startLocalSupervisorHost, recoverManagedRuntime, runPinnedControllerOperation
19
19
  } from '../src/autonomous-local-supervisor.mjs';
20
20
  import { localSupervisorFixture } from './helpers/autonomous-r34-fixture.mjs';
21
+ import { writeBoundStandingGrant } from './r65-authority-fixture.mjs';
21
22
 
22
23
  const here=path.dirname(fileURLToPath(import.meta.url));
23
24
  const repoRoot=path.resolve(here,'..');
@@ -32,11 +33,11 @@ async function createProviderHostFixture(){
32
33
  const keys=await generateTrustKeypair({privateKeyPath:path.join(trust,'private.pem'),publicKeyPath:path.join(trust,'public.pem')});
33
34
  const codingPath=path.join(root,'coding.json');
34
35
  await writeFile(codingPath,JSON.stringify({schema:'deadbyte.coding-policy.v1',enabled:true,
35
- lease_file:path.join(root,'coding-grant.json'),evidence_dir:path.join(root,'coding-evidence'),
36
+ lease_file:path.join(root,'runtime','coding-grant.json'),evidence_dir:path.join(root,'coding-evidence'),
36
37
  roots:{project:{path:project,read:true,write:true,exec:false,deny_paths:[]}},commands:{}}));
37
38
  const codingPolicy=await loadCodingPolicy(codingPath);
38
- await writeFile(codingPolicy.lease_file,JSON.stringify({schema:'deadbyte.coding-grant.v2',policy_sha256:codingPolicy.policy_sha256,
39
- instance_nonce:nonce(),root_ids:['project'],granted_at_utc:new Date().toISOString()}));
39
+ await writeBoundStandingGrant('coding', codingPolicy.lease_file, {schema:'deadbyte.coding-grant.v2',policy_sha256:codingPolicy.policy_sha256,
40
+ instance_nonce:nonce(),root_ids:['project'],granted_at_utc:new Date().toISOString()});
40
41
  let round=0;
41
42
  const planner=createServer(async(req,res)=>{ let body=''; for await(const chunk of req) body+=chunk; JSON.parse(body); round+=1;
42
43
  const decision=round===1
@@ -47,13 +48,13 @@ async function createProviderHostFixture(){
47
48
  await new Promise(resolve=>planner.listen(0,'127.0.0.1',resolve));
48
49
  const autonomousPath=path.join(root,'autonomous.json');
49
50
  await writeFile(autonomousPath,JSON.stringify({schema:'deadbyte.autonomous-policy.v1',enabled:true,
50
- lease_file:path.join(root,'autonomous-grant.json'),evidence_dir:path.join(root,'autonomous-evidence'),
51
+ lease_file:path.join(root,'runtime','autonomous-grant.json'),evidence_dir:path.join(root,'autonomous-evidence'),
51
52
  coding_policy_sha256:codingPolicy.policy_sha256,root_ids:['project'],
52
53
  planner:{backend:'openai_compatible',endpoint:`http://127.0.0.1:${planner.address().port}/v1/chat/completions`,model:'r34-host-test',max_tokens:2048,timeout_ms:30000,max_response_bytes:262144},
53
54
  limits:{max_iterations:8,max_mutations:8,max_wall_ms:null,max_context_bytes:262144,max_diff_bytes:262144,max_files_per_diff:4},profiles:{}}));
54
55
  const autonomousPolicy=await loadAutonomousPolicy(autonomousPath,codingPolicy);
55
- await writeFile(autonomousPolicy.lease_file,JSON.stringify({schema:'deadbyte.autonomous-grant.v2',policy_sha256:autonomousPolicy.policy_sha256,
56
- instance_nonce:nonce(),root_ids:['project'],granted_at_utc:new Date().toISOString()}));
56
+ await writeBoundStandingGrant('autonomous', autonomousPolicy.lease_file, {schema:'deadbyte.autonomous-grant.v2',policy_sha256:autonomousPolicy.policy_sha256,
57
+ instance_nonce:nonce(),root_ids:['project'],granted_at_utc:new Date().toISOString()});
57
58
  const codingRuntime=createCodingRuntime({policy:codingPolicy,signingKeyPath:keys.privateKeyPath,verifyKeyPath:keys.publicKeyPath});
58
59
  const nativeDiffRuntime=createNativeDiffRuntime({policy:autonomousPolicy,codingRuntime,signingKeyPath:keys.privateKeyPath,verifyKeyPath:keys.publicKeyPath});
59
60
  const runtime=createAutonomousRuntime({policy:autonomousPolicy,codingRuntime,nativeDiffRuntime,signingKeyPath:keys.privateKeyPath,verifyKeyPath:keys.publicKeyPath});
@@ -10,6 +10,7 @@ import { fileURLToPath } from 'node:url';
10
10
  import { Client } from '@modelcontextprotocol/client';
11
11
  import { StdioClientTransport, getDefaultEnvironment } from '@modelcontextprotocol/client/stdio';
12
12
  import { generateTrustKeypair } from '../src/trust.mjs';
13
+ import { writeBoundStandingGrant } from './r65-authority-fixture.mjs';
13
14
 
14
15
  const here = path.dirname(fileURLToPath(import.meta.url));
15
16
  const repoRoot = path.resolve(here, '..');
@@ -52,18 +53,18 @@ async function startPlanner() {
52
53
  const codingPath = path.join(root, 'coding.json');
53
54
  await writeFile(codingPath, JSON.stringify({
54
55
  schema:'deadbyte.coding-policy.v1', enabled:true,
55
- lease_file:path.join(root, 'coding-grant.json'), evidence_dir:path.join(root, 'coding-evidence'),
56
+ lease_file:path.join(root, 'runtime', 'coding-grant.json'), evidence_dir:path.join(root, 'coding-evidence'),
56
57
  roots:{ project:{ path:project, read:true, write:true, exec:false, deny_paths:[] } }, commands:{}
57
58
  }));
58
59
  const codingSha = sha(await readFile(codingPath));
59
- await writeFile(path.join(root, 'coding-grant.json'), JSON.stringify({
60
+ await writeBoundStandingGrant('coding', path.join(root, 'runtime', 'coding-grant.json'), {
60
61
  schema:'deadbyte.coding-grant.v2', policy_sha256:codingSha,
61
62
  instance_nonce:nonce(), root_ids:['project'], granted_at_utc:new Date().toISOString()
62
- }));
63
+ });
63
64
  const planner = await startPlanner();
64
65
  const autonomousPath = path.join(root, 'autonomous.json'); await writeFile(autonomousPath, JSON.stringify({
65
66
  schema:'deadbyte.autonomous-policy.v1', enabled:true,
66
- lease_file:path.join(root, 'autonomous-grant.json'), evidence_dir:path.join(root, 'autonomous-evidence'),
67
+ lease_file:path.join(root, 'runtime', 'autonomous-grant.json'), evidence_dir:path.join(root, 'autonomous-evidence'),
67
68
  coding_policy_sha256:codingSha, root_ids:['project'],
68
69
  planner:{ backend:'openai_compatible', endpoint:planner.endpoint, model:'fake-supervisor', max_tokens:2048,
69
70
  timeout_ms:30000, max_response_bytes:262144 },
@@ -71,10 +72,10 @@ async function startPlanner() {
71
72
  max_context_bytes:262144, max_diff_bytes:262144, max_files_per_diff:4 }, profiles:{}
72
73
  }));
73
74
  const autonomousSha = sha(await readFile(autonomousPath));
74
- await writeFile(path.join(root, 'autonomous-grant.json'), JSON.stringify({
75
+ await writeBoundStandingGrant('autonomous', path.join(root, 'runtime', 'autonomous-grant.json'), {
75
76
  schema:'deadbyte.autonomous-grant.v2', policy_sha256:autonomousSha,
76
77
  instance_nonce:nonce(), root_ids:['project'], granted_at_utc:new Date().toISOString()
77
- }));
78
+ });
78
79
  const client = new Client({ name:'deadbyte-supervisor-integration', version:'0.8.0' },
79
80
  { versionNegotiation:{ mode:{ pin:'2026-07-28' } } });
80
81
  const transport = new StdioClientTransport({
@@ -148,4 +149,4 @@ test('external supervisor mode leaves provider goal durable until standalone hos
148
149
  await new Promise(resolve=>f.planner.server.close(resolve));
149
150
  await rm(f.root,{recursive:true,force:true});
150
151
  }
151
- });
152
+ });
@@ -2,6 +2,7 @@ import test from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
3
  import os from 'node:os';
4
4
  import path from 'node:path';
5
+ import { createHash } from 'node:crypto';
5
6
  import { mkdtemp, rm, writeFile } from 'node:fs/promises';
6
7
  import {
7
8
  CAPABILITY_STATES,
@@ -9,6 +10,7 @@ import {
9
10
  resolveCapabilityStates
10
11
  } from '../src/capability-resolver.mjs';
11
12
  import { R36_CAPABILITY_LEDGER } from '../src/capability-ledger.mjs';
13
+ import { writeBoundStandingGrant } from './r65-authority-fixture.mjs';
12
14
 
13
15
  test('capability resolver enforces 5 canonical states', () => {
14
16
  assert.equal(CAPABILITY_STATES.AVAILABLE, 'AVAILABLE');
@@ -187,14 +189,18 @@ test('capability resolver reports actual disarmed authority across coding proces
187
189
  test('capability resolver reports armed Host capability when the standing grant is valid', async () => {
188
190
  const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-capability-host-'));
189
191
  try{
190
- const armFile=path.join(root,'host-grant.json');
191
- const policySha='b'.repeat(64);
192
- await writeFile(armFile,JSON.stringify({
192
+ const armFile=path.join(root,'runtime','host-grant.json');
193
+ const policyFile=path.join(root,'host-policy.json');
194
+ const policyBytes=Buffer.from(JSON.stringify({schema:'deadbyte.host-policy.v2',enabled:true,arm_file:armFile,
195
+ roots:{core:{path:root,read:true,write:false,exec:false,deny_paths:[]}},commands:{},max_read_bytes:4096,max_write_bytes:4096}));
196
+ await writeFile(policyFile,policyBytes);
197
+ const policySha=createHash('sha256').update(policyBytes).digest('hex');
198
+ await writeBoundStandingGrant('host',armFile,{
193
199
  schema:'deadbyte.host-grant.v2',
194
200
  policy_sha256:policySha,
195
201
  instance_nonce:'c'.repeat(64),
196
202
  armed_at_utc:new Date().toISOString()
197
- }));
203
+ });
198
204
  const result=await resolveCapabilityStates({
199
205
  ledger:{entries:[{
200
206
  id:'host.file_read',compact_tool:'host_file_read',full_tool:'host_file_read',compact_op:'direct',
@@ -203,7 +209,7 @@ test('capability resolver reports armed Host capability when the standing grant
203
209
  toolCatalog:['host_file_read'],
204
210
  profile:'compact',
205
211
  env:{},
206
- hostPolicy:{arm_file:armFile,policy_sha256:policySha}
212
+ hostPolicy:{arm_file:armFile,policy_path:policyFile,policy_sha256:policySha}
207
213
  });
208
214
  const hostRead=result.capabilities[0];
209
215
  assert.equal(hostRead.state,CAPABILITY_STATES.AVAILABLE);
@@ -10,6 +10,7 @@ import { loadAutonomousPolicy } from '../src/autonomous-policy.mjs';
10
10
  import { createNativeDiffRuntime } from '../src/native-diff.mjs';
11
11
  import { createAutonomousRuntime } from '../src/autonomous-runtime.mjs';
12
12
  import { providerCircuitId } from '../src/provider-circuit.mjs';
13
+ import { writeBoundStandingGrant } from './r65-authority-fixture.mjs';
13
14
 
14
15
  const sha=b=>createHash('sha256').update(b).digest('hex');
15
16
  const nonce=()=>randomBytes(32).toString('hex');
@@ -21,13 +22,13 @@ async function fixture(){
21
22
  const keys=await generateTrustKeypair({privateKeyPath:path.join(trust,'private.pem'),publicKeyPath:path.join(trust,'public.pem')});
22
23
  const codingPath=path.join(root,'coding.json');
23
24
  await writeFile(codingPath,JSON.stringify({schema:'deadbyte.coding-policy.v1',enabled:true,
24
- lease_file:path.join(root,'coding-grant.json'),evidence_dir:path.join(root,'coding-evidence'),
25
+ lease_file:path.join(root,'runtime','coding-grant.json'),evidence_dir:path.join(root,'coding-evidence'),
25
26
  roots:{project:{path:project,read:true,write:true,exec:false,deny_paths:[]}},commands:{}},null,2));
26
27
  const codingPolicy=await loadCodingPolicy(codingPath);
27
28
  const nodeSha=sha(await readFile(process.execPath));
28
29
  const autonomousPath=path.join(root,'autonomous.json');
29
30
  await writeFile(autonomousPath,JSON.stringify({schema:'deadbyte.autonomous-policy.v1',enabled:true,
30
- lease_file:path.join(root,'autonomous-grant.json'),evidence_dir:path.join(root,'autonomous-evidence'),
31
+ lease_file:path.join(root,'runtime','autonomous-grant.json'),evidence_dir:path.join(root,'autonomous-evidence'),
31
32
  coding_policy_sha256:codingPolicy.policy_sha256,root_ids:['project'],
32
33
  planner:{backend:'codex_app_server',model:'gpt-test',root_id:'project',executable:process.execPath,
33
34
  executable_sha256:nodeSha,args:['app-server'],max_tokens:4096,timeout_ms:30000,
@@ -35,10 +36,10 @@ async function fixture(){
35
36
  profiles:{},limits:{max_iterations:8,max_mutations:4,max_wall_ms:600000,max_context_bytes:262144,
36
37
  max_diff_bytes:262144,max_files_per_diff:4,max_stalled_cycles:2,max_post_replan_stalls:2,max_replans:4}},null,2));
37
38
  const policy=await loadAutonomousPolicy(autonomousPath,codingPolicy);
38
- await writeFile(codingPolicy.lease_file,JSON.stringify({schema:'deadbyte.coding-grant.v2',policy_sha256:codingPolicy.policy_sha256,
39
- instance_nonce:nonce(),root_ids:['project'],armed_at_utc:new Date().toISOString()}));
40
- await writeFile(policy.lease_file,JSON.stringify({schema:'deadbyte.autonomous-grant.v2',policy_sha256:policy.policy_sha256,
41
- instance_nonce:nonce(),root_ids:['project'],armed_at_utc:new Date().toISOString()}));
39
+ await writeBoundStandingGrant('coding', codingPolicy.lease_file, {schema:'deadbyte.coding-grant.v2',policy_sha256:codingPolicy.policy_sha256,
40
+ instance_nonce:nonce(),root_ids:['project'],armed_at_utc:new Date().toISOString()});
41
+ await writeBoundStandingGrant('autonomous', policy.lease_file, {schema:'deadbyte.autonomous-grant.v2',policy_sha256:policy.policy_sha256,
42
+ instance_nonce:nonce(),root_ids:['project'],armed_at_utc:new Date().toISOString()});
42
43
  const codingRuntime=createCodingRuntime({policy:codingPolicy,signingKeyPath:keys.privateKeyPath,verifyKeyPath:keys.publicKeyPath});
43
44
  const nativeDiffRuntime=createNativeDiffRuntime({policy,codingRuntime,signingKeyPath:keys.privateKeyPath,verifyKeyPath:keys.publicKeyPath});
44
45
  return {root,project,keys,codingPolicy,policy,codingRuntime,nativeDiffRuntime,cleanup:()=>rm(root,{recursive:true,force:true})};
@@ -6,6 +6,7 @@ import os from 'node:os';
6
6
  import path from 'node:path';
7
7
  import { generateTrustKeypair } from '../src/trust.mjs';
8
8
  import { createCodingRuntime, loadCodingPolicy } from '../src/coding-plane.mjs';
9
+ import { writeBoundStandingGrant } from './r65-authority-fixture.mjs';
9
10
 
10
11
  const sha = bytes => createHash('sha256').update(bytes).digest('hex');
11
12
 
@@ -18,7 +19,7 @@ async function fixture() {
18
19
  await writeFile(path.join(project, 'src', 'b.txt'), 'beta NEEDLE two\n');
19
20
  await writeFile(path.join(project, 'node_modules', 'hidden.txt'), 'needle hidden\n');
20
21
  const policyPath = path.join(root, 'coding-policy.json');
21
- const leasePath = path.join(root, 'coding-grant.json');
22
+ const leasePath = path.join(root, 'runtime', 'coding-grant.json');
22
23
  await writeFile(policyPath, JSON.stringify({
23
24
  schema: 'deadbyte.coding-policy.v1', enabled: true,
24
25
  lease_file: leasePath, evidence_dir: path.join(root, 'evidence'),
@@ -26,10 +27,10 @@ async function fixture() {
26
27
  commands: {}
27
28
  }));
28
29
  const policy = await loadCodingPolicy(policyPath);
29
- await writeFile(leasePath, JSON.stringify({
30
+ await writeBoundStandingGrant('coding', leasePath, {
30
31
  schema: 'deadbyte.coding-grant.v2', policy_sha256: policy.policy_sha256,
31
32
  instance_nonce: 'a'.repeat(64), root_ids: ['project'], granted_at_utc: new Date().toISOString()
32
- }));
33
+ });
33
34
  const keys = await generateTrustKeypair({
34
35
  privateKeyPath: path.join(root, 'private.pem'), publicKeyPath: path.join(root, 'public.pem')
35
36
  });