deadbyte-mcp 0.15.47 → 0.15.49

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/MANIFEST.SHA256 +111 -48
  2. package/PROVENANCE.json +1 -1
  3. package/README.txt +18 -18
  4. package/RELEASE.json +3 -3
  5. package/SBOM.cdx.json +2 -2
  6. package/TEST-MATRIX.json +2 -2
  7. package/bench/r121-epoch-readhead.mjs +54 -0
  8. package/bench/r124-epoch-predecessor.mjs +53 -0
  9. package/bin/deadbyte-epoch-lock-r85.node +0 -0
  10. package/controller/deadbyte-process-policy.json +4 -4
  11. package/docs/HARNESS2-ADAPTATION-20261020.md +24 -0
  12. package/docs/R128-RELEASE-CANDIDATE.md +26 -0
  13. package/docs/R75-RECOVERY-HANDOFF.md +16 -0
  14. package/docs/R85-EPOCH-LOCK-BINARY.json +13 -0
  15. package/native/deadbyte-epoch-lock-r85.cpp +241 -0
  16. package/package.json +1 -1
  17. package/scripts/build-epoch-lock-r85.ps1 +34 -0
  18. package/scripts/deploy-r48.mjs +3 -1
  19. package/scripts/npm-release-r48.mjs +18 -4
  20. package/scripts/r75-npm-bin-integrity-gate.mjs +33 -0
  21. package/scripts/release-manifest.mjs +1 -1
  22. package/scripts/verify-authority-epoch-history.mjs +38 -0
  23. package/scripts/verify-r70-full-regression.ps1 +12 -2
  24. package/src/activation-authority-proof-r75.mjs +67 -0
  25. package/src/activation-continuity-engine-r65.mjs +17 -10
  26. package/src/activation-preflight-r65.mjs +13 -0
  27. package/src/agent-eval-r42.mjs +30 -0
  28. package/src/authority-continuity-r65.mjs +442 -29
  29. package/src/autonomous-local-supervisor.mjs +77 -47
  30. package/src/deadbyte-cli.mjs +73 -46
  31. package/src/epoch-lock-r85.mjs +48 -0
  32. package/src/foreground-marker-cas-win32.mjs +284 -0
  33. package/src/foreground-process-identity.mjs +3 -3
  34. package/src/privileged-install-trust-r89.mjs +196 -0
  35. package/src/remote-live-log.mjs +15 -8
  36. package/src/remote-log-decode-r60.mjs +65 -0
  37. package/src/remote-session-guardian.mjs +13 -12
  38. package/src/runtime-update.mjs +5 -22
  39. package/src/version.mjs +1 -1
  40. package/src/windows-powershell-environment.mjs +24 -0
  41. package/test/autonomous-local-supervisor.test.mjs +1 -1
  42. package/test/cli-entrypoint.test.mjs +1 -1
  43. package/test/harness2-eval-context-isolation.test.mjs +90 -0
  44. package/test/helpers/r75-authority-proof.mjs +9 -0
  45. package/test/r100-posix-owned-lock-race.test.mjs +72 -0
  46. package/test/r101-acl-probe-environment.test.mjs +51 -0
  47. package/test/r106-trusted-powershell-spawn.test.mjs +53 -0
  48. package/test/r107-updater-powershell-path.test.mjs +77 -0
  49. package/test/r108-remote-powershell-path.test.mjs +59 -0
  50. package/test/r110-supervisor-powershell-path.test.mjs +28 -0
  51. package/test/r111-supervisor-marker-cas.test.mjs +116 -0
  52. package/test/r112-supervisor-claim-cas.test.mjs +63 -0
  53. package/test/r113-supervisor-cleanup-terminality.test.mjs +100 -0
  54. package/test/r114-supervisor-absence-proof.test.mjs +45 -0
  55. package/test/r115-posix-continuity-entrypoint.test.mjs +17 -0
  56. package/test/r116-authority-crash-matrix.test.mjs +83 -0
  57. package/test/r117-concurrent-orphan-recovery.test.mjs +54 -0
  58. package/test/r118-cross-process-orphan-recovery.test.mjs +99 -0
  59. package/test/r119-recovery-crash-after-commit.test.mjs +91 -0
  60. package/test/r120-authority-history-audit.test.mjs +103 -0
  61. package/test/r121-signed-transition-invariant.test.mjs +132 -0
  62. package/test/r122-epoch-bounded-read.test.mjs +58 -0
  63. package/test/r123-recovery-bounded-read.test.mjs +64 -0
  64. package/test/r124-live-log-integrity.test.mjs +66 -0
  65. package/test/r124-live-log-tail-boundaries.test.mjs +57 -0
  66. package/test/r124-signed-predecessor-shape.test.mjs +66 -0
  67. package/test/r125-restore-binding-types.test.mjs +63 -0
  68. package/test/r126-restore-policy-envelope.test.mjs +89 -0
  69. package/test/r127-restore-append-policy-boundary.test.mjs +25 -0
  70. package/test/r33-closeout-regression.test.mjs +2 -1
  71. package/test/r54-offline-predecessor-self-exclusion.test.mjs +2 -2
  72. package/test/r55-offline-predecessor-guardian-family.test.mjs +2 -2
  73. package/test/r56-rollback-launcher-restore.test.mjs +2 -2
  74. package/test/r57-core-startup-race.test.mjs +2 -2
  75. package/test/r58-concurrent-recovery.test.mjs +10 -7
  76. package/test/r58-stale-owner.test.mjs +5 -6
  77. package/test/r59-tool-surface-compat.test.mjs +1 -1
  78. package/test/r65-activation-orchestration.test.mjs +2 -1
  79. package/test/r65-activation-preflight.test.mjs +7 -6
  80. package/test/r65-authority-epoch-recovery.test.mjs +3 -1
  81. package/test/r65-authority-epoch-store.test.mjs +9 -6
  82. package/test/r65-authority-restored-safe.test.mjs +2 -1
  83. package/test/r65-deploy-preflight.test.mjs +13 -14
  84. package/test/r65-epoch-native-activation-integration.test.mjs +5 -4
  85. package/test/r65-legacy-authority-migration.test.mjs +2 -1
  86. package/test/r67-npm-cross-volume.test.mjs +9 -2
  87. package/test/r71-full-verifier-artifact.test.mjs +21 -15
  88. package/test/r71-isolated-slot-rollback-restore.test.mjs +3 -1
  89. package/test/r71-release-identity.test.mjs +3 -1
  90. package/test/r74-kernel-lock.test.mjs +12 -6
  91. package/test/r74-prepublish-contract.test.mjs +3 -1
  92. package/test/r75-activation-authority-proof.test.mjs +46 -0
  93. package/test/r75-authority-evidence.test.mjs +77 -0
  94. package/test/r76-pinned-marker-cas.test.mjs +78 -0
  95. package/test/r77-epoch-grant-cas.test.mjs +51 -0
  96. package/test/r78-parent-junction-cas.test.mjs +62 -0
  97. package/test/r79-orphan-revocation-reconcile.test.mjs +88 -0
  98. package/test/r79-windows-fsync-regression.test.mjs +45 -0
  99. package/test/r80-durable-epoch-commit.test.mjs +76 -0
  100. package/test/r82-epoch-lock-owned-cas.test.mjs +93 -0
  101. package/test/r83-epoch-lock-fileid-cas.test.mjs +92 -0
  102. package/test/r84-epoch-lock-created-fileid.test.mjs +112 -0
  103. package/test/r85-epoch-lock-release-lifetime.test.mjs +25 -0
  104. package/test/r85-epoch-repeat-append.test.mjs +30 -0
  105. package/test/r86-epoch-cleanup-and-loader.test.mjs +126 -0
  106. package/test/r87-crashed-orphan-revoke-reconcile.test.mjs +110 -0
  107. package/test/r88-native-disposition-failure.test.mjs +35 -0
  108. package/test/r89-privileged-install-boundary.test.mjs +123 -0
  109. package/test/r92-acl-ace-applicability.test.mjs +45 -0
  110. package/test/r94-win32-propagation-enum.test.mjs +51 -0
  111. package/test/r99-privileged-import-closure.test.mjs +45 -0
  112. package/test/remote-session-cli.test.mjs +69 -9
@@ -0,0 +1,91 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {generateKeyPairSync, randomBytes} from 'node:crypto';
4
+ import {spawn} from 'node:child_process';
5
+ import {mkdtemp, mkdir, readFile, writeFile, access, rm} from 'node:fs/promises';
6
+ import os from 'node:os';
7
+ import path from 'node:path';
8
+ import {pathToFileURL} from 'node:url';
9
+
10
+ const moduleUrl = process.env.DEADBYTE_TEST_PROTECTED_EPOCH_MODULE
11
+ ? pathToFileURL(process.env.DEADBYTE_TEST_PROTECTED_EPOCH_MODULE).href
12
+ : new URL('../src/authority-continuity-r65.mjs', import.meta.url).href;
13
+ const {createAuthorityEpochStore} = await import(moduleUrl);
14
+ const exists = file => access(file).then(() => true, () => false);
15
+
16
+ function crashRecovery(runtimeDir, expectedHeadSha256, expectedOrphanSha256) {
17
+ const code = `
18
+ import {createAuthorityEpochStore} from ${JSON.stringify(moduleUrl)};
19
+ const store = createAuthorityEpochStore({runtimeDir:${JSON.stringify(runtimeDir)},
20
+ onDurabilityStage:stage => {
21
+ if (stage === 'head_renamed') process.kill(process.pid, 'SIGKILL');
22
+ }});
23
+ await store.reconcileOrphanedRevocation({plane:'process',
24
+ expectedHeadSha256:${JSON.stringify(expectedHeadSha256)},
25
+ expectedOrphanSha256:${JSON.stringify(expectedOrphanSha256)},
26
+ operatorConfirmed:true});
27
+ `;
28
+ const child = spawn(process.execPath, ['--input-type=module', '-e', code], {
29
+ env:{...process.env, NODE_TEST_CONTEXT:'child-v8'},
30
+ stdio:['ignore', 'ignore', 'pipe'], windowsHide:true
31
+ });
32
+ let stderr = '';
33
+ child.stderr.on('data', bytes => {stderr = (stderr + bytes.toString()).slice(-1000);});
34
+ const done = new Promise((resolve, reject) => {
35
+ const timer = setTimeout(() => {child.kill(); reject(new Error('R119 recovery crash child timeout'));}, 12000);
36
+ child.once('error', error => {clearTimeout(timer); reject(error);});
37
+ child.once('exit', (code, signal) => {clearTimeout(timer); resolve({code, signal, stderr});});
38
+ });
39
+ return {child, done};
40
+ }
41
+
42
+ test('R119 crash after recovered head rename leaves revoke committed and dead lock recoverable',
43
+ {skip:process.platform !== 'win32', timeout:30000}, async () => {
44
+ const root = await mkdtemp(path.join(os.tmpdir(), 'deadbyte-r119-recovery-crash-'));
45
+ let worker;
46
+ try {
47
+ const runtimeDir = path.join(root, 'runtime');
48
+ const trust = path.join(root, 'trust');
49
+ await mkdir(runtimeDir); await mkdir(trust);
50
+ const keys = generateKeyPairSync('ed25519');
51
+ await writeFile(path.join(trust, 'ed25519-private.pem'),
52
+ keys.privateKey.export({type:'pkcs8', format:'pem'}), {flag:'wx'});
53
+ await writeFile(path.join(trust, 'ed25519-public.pem'),
54
+ keys.publicKey.export({type:'spki', format:'pem'}), {flag:'wx'});
55
+ const nonce = randomBytes(32).toString('hex');
56
+ const grantBytes = Buffer.from(JSON.stringify({instance_nonce:nonce, fixture:'R119 isolated'}));
57
+ const store = createAuthorityEpochStore({runtimeDir});
58
+ const prior = await store.append({plane:'process', action:'grant',
59
+ grantBytes, grantInstanceNonce:nonce});
60
+ const epochDir = path.join(runtimeDir, 'authority-epochs', 'process');
61
+ const headPath = path.join(epochDir, 'head.json');
62
+ const priorHeadBytes = await readFile(headPath);
63
+ const orphan = await store.append({plane:'process', action:'user_revoke',
64
+ grantBytes, grantInstanceNonce:nonce});
65
+ await writeFile(headPath, priorHeadBytes);
66
+ assert.equal((await store.verifyGrant({plane:'process', grantBytes,
67
+ grantInstanceNonce:nonce})).trusted, false);
68
+
69
+ worker = crashRecovery(runtimeDir, prior.event_sha256, orphan.event_sha256);
70
+ const outcome = await worker.done;
71
+ assert.notEqual(outcome.code, 0, `recovery worker must die at head rename: ${outcome.stderr}`);
72
+ assert.equal((await store.readHead({plane:'process'})).event_sha256, orphan.event_sha256);
73
+ assert.equal((await store.verifyGrant({plane:'process', grantBytes,
74
+ grantInstanceNonce:nonce})).trusted, false);
75
+ const lockPath = path.join(epochDir, '.lock');
76
+ assert.equal(await exists(lockPath), true, 'dead recovery owner leaves a lock identity');
77
+ await assert.rejects(store.reconcileOrphanedRevocation({plane:'process',
78
+ expectedHeadSha256:prior.event_sha256,
79
+ expectedOrphanSha256:orphan.event_sha256, operatorConfirmed:true}),
80
+ /exactly one orphan event/);
81
+ const next = await store.append({plane:'process', action:'user_revoke',
82
+ grantBytes, grantInstanceNonce:nonce});
83
+ assert.equal(next.seq, orphan.seq + 1);
84
+ assert.equal((await store.verifyGrant({plane:'process', grantBytes,
85
+ grantInstanceNonce:nonce})).trusted, false);
86
+ assert.equal(await exists(lockPath), false, 'verified dead lock is reclaimed and released');
87
+ } finally {
88
+ if (worker?.child.exitCode === null) worker.child.kill();
89
+ await rm(root, {recursive:true, force:true, maxRetries:8, retryDelay:100});
90
+ }
91
+ });
@@ -0,0 +1,103 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {generateKeyPairSync, randomBytes} from 'node:crypto';
4
+ import {spawnSync} from 'node:child_process';
5
+ import {mkdtemp, mkdir, readdir, readFile, writeFile, unlink, rm} from 'node:fs/promises';
6
+ import os from 'node:os';
7
+ import path from 'node:path';
8
+ import {fileURLToPath, pathToFileURL} from 'node:url';
9
+ import {createAuthorityEpochStore as createCandidateStore} from '../src/authority-continuity-r65.mjs';
10
+
11
+ const protectedModule = process.env.DEADBYTE_TEST_PROTECTED_EPOCH_MODULE;
12
+ const createWriterStore = protectedModule
13
+ ? (await import(pathToFileURL(protectedModule).href)).createAuthorityEpochStore
14
+ : createCandidateStore;
15
+ const repo = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
16
+ const auditScript = path.join(repo, 'scripts', 'verify-authority-epoch-history.mjs');
17
+
18
+ async function threeEvents(run) {
19
+ const root = await mkdtemp(path.join(os.tmpdir(), 'deadbyte-r120-history-'));
20
+ try {
21
+ const runtimeDir = path.join(root, 'runtime');
22
+ const trust = path.join(root, 'trust');
23
+ await mkdir(runtimeDir); await mkdir(trust);
24
+ const keys = generateKeyPairSync('ed25519');
25
+ await writeFile(path.join(trust, 'ed25519-private.pem'),
26
+ keys.privateKey.export({type:'pkcs8', format:'pem'}), {flag:'wx'});
27
+ await writeFile(path.join(trust, 'ed25519-public.pem'),
28
+ keys.publicKey.export({type:'spki', format:'pem'}), {flag:'wx'});
29
+ const writer = createWriterStore({runtimeDir});
30
+ const firstNonce = randomBytes(32).toString('hex');
31
+ const latestNonce = randomBytes(32).toString('hex');
32
+ const firstBytes = Buffer.from(JSON.stringify({instance_nonce:firstNonce, fixture:'R120 first'}));
33
+ const latestBytes = Buffer.from(JSON.stringify({instance_nonce:latestNonce, fixture:'R120 latest'}));
34
+ const first = await writer.append({plane:'process', action:'grant',
35
+ grantBytes:firstBytes, grantInstanceNonce:firstNonce});
36
+ await writer.append({plane:'process', action:'user_revoke',
37
+ grantBytes:firstBytes, grantInstanceNonce:firstNonce});
38
+ const latest = await writer.append({plane:'process', action:'grant',
39
+ grantBytes:latestBytes, grantInstanceNonce:latestNonce});
40
+ const auditor = createCandidateStore({runtimeDir});
41
+ await run({runtimeDir, auditor, first, latest, latestBytes, latestNonce});
42
+ } finally {
43
+ await rm(root, {recursive:true, force:true, maxRetries:8, retryDelay:100});
44
+ }
45
+ }
46
+
47
+ test('R120 independent audit verifies the complete signed history, not just newest two events',
48
+ () => threeEvents(async ({auditor, latest}) => {
49
+ const result = await auditor.auditHistory({plane:'process'});
50
+ assert.equal(result.status, 'PASS');
51
+ assert.equal(result.seq, 3);
52
+ assert.equal(result.event_sha256, latest.event_sha256);
53
+ assert.equal(result.verified_events, 3);
54
+ }));
55
+
56
+ test('R120 oldest event corruption is rejected even when current grant head still verifies',
57
+ () => threeEvents(async ({runtimeDir, auditor, first, latestBytes, latestNonce}) => {
58
+ const events = path.join(runtimeDir, 'authority-epochs', 'process', 'events');
59
+ const names = await readdir(events);
60
+ const firstName = names.find(name => name.startsWith('0000000000000001-'));
61
+ assert.ok(firstName);
62
+ const firstPath = path.join(events, firstName);
63
+ const original = await readFile(firstPath);
64
+ await writeFile(firstPath, Buffer.from('{}'));
65
+ assert.equal((await auditor.verifyGrant({plane:'process', grantBytes:latestBytes,
66
+ grantInstanceNonce:latestNonce})).trusted, true,
67
+ 'hot-path head and immediate predecessor do not inspect oldest event');
68
+ await assert.rejects(auditor.auditHistory({plane:'process'}), /history.*hash mismatch/);
69
+ assert.notEqual(first.event_sha256, latestNonce);
70
+ assert.notDeepEqual(await readFile(firstPath), original);
71
+ }));
72
+
73
+ test('R120 CLI audit binds the exact expected head and rejects a wrong identity',
74
+ () => threeEvents(async ({runtimeDir, latest}) => {
75
+ const base = [auditScript, '--runtime-dir', runtimeDir, '--plane', 'process',
76
+ '--expected-head', latest.event_sha256];
77
+ const correct = spawnSync(process.execPath, base, {cwd:repo, encoding:'utf8', timeout:10000});
78
+ assert.equal(correct.status, 0, correct.stderr);
79
+ const result = JSON.parse(correct.stdout);
80
+ assert.equal(result.status, 'PASS');
81
+ assert.equal(result.event_sha256, latest.event_sha256);
82
+ const wrong = spawnSync(process.execPath,
83
+ [auditScript, '--runtime-dir', runtimeDir, '--plane', 'process',
84
+ '--expected-head', 'f'.repeat(64)], {cwd:repo, encoding:'utf8', timeout:10000});
85
+ assert.notEqual(wrong.status, 0);
86
+ assert.match(wrong.stderr, /expected head mismatch/);
87
+ }));
88
+
89
+ test('R120 missing oldest event and duplicate signed-looking history fail the audit',
90
+ () => threeEvents(async ({runtimeDir, auditor, latestBytes, latestNonce}) => {
91
+ const events = path.join(runtimeDir, 'authority-epochs', 'process', 'events');
92
+ const names = await readdir(events);
93
+ const oldest = names.find(name => name.startsWith('0000000000000001-'));
94
+ assert.ok(oldest);
95
+ const original = await readFile(path.join(events, oldest));
96
+ await unlink(path.join(events, oldest));
97
+ assert.equal((await auditor.verifyGrant({plane:'process', grantBytes:latestBytes,
98
+ grantInstanceNonce:latestNonce})).trusted, true);
99
+ await assert.rejects(auditor.auditHistory({plane:'process'}), /history event count mismatch/);
100
+ await writeFile(path.join(events, oldest), original, {flag:'wx'});
101
+ await writeFile(path.join(events, `0000000000000001-${'f'.repeat(64)}.json`), original, {flag:'wx'});
102
+ await assert.rejects(auditor.auditHistory({plane:'process'}), /history event count mismatch/);
103
+ }));
@@ -0,0 +1,132 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {createHash, generateKeyPairSync, randomBytes, sign as cryptoSign} from 'node:crypto';
4
+ import {mkdtemp, mkdir, readFile, writeFile, rm} from 'node:fs/promises';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import {pathToFileURL} from 'node:url';
8
+ import {canonicalJson} from '../src/canonical-json.mjs';
9
+ import {createAuthorityEpochStore as createCandidateStore} from '../src/authority-continuity-r65.mjs';
10
+
11
+ const protectedModule = process.env.DEADBYTE_TEST_PROTECTED_EPOCH_MODULE;
12
+ const createWriterStore = protectedModule
13
+ ? (await import(pathToFileURL(protectedModule).href)).createAuthorityEpochStore
14
+ : createCandidateStore;
15
+ const sha256 = bytes => createHash('sha256').update(bytes).digest('hex');
16
+ const domain = Buffer.from('DEADBYTE-AUTHORITY-DURABLE-EPOCH-R65-V1\0', 'utf8');
17
+
18
+ test('R121 correctly signed restore after user revoke is still an invalid authority transition',
19
+ async () => {
20
+ const root = await mkdtemp(path.join(os.tmpdir(), 'deadbyte-r121-transition-'));
21
+ try {
22
+ const runtimeDir = path.join(root, 'runtime');
23
+ const trust = path.join(root, 'trust');
24
+ await mkdir(runtimeDir); await mkdir(trust);
25
+ const keys = generateKeyPairSync('ed25519');
26
+ await writeFile(path.join(trust, 'ed25519-private.pem'),
27
+ keys.privateKey.export({type:'pkcs8', format:'pem'}), {flag:'wx'});
28
+ await writeFile(path.join(trust, 'ed25519-public.pem'),
29
+ keys.publicKey.export({type:'spki', format:'pem'}), {flag:'wx'});
30
+ const firstNonce = randomBytes(32).toString('hex');
31
+ const nextNonce = randomBytes(32).toString('hex');
32
+ const oldGrant = Buffer.from(JSON.stringify({instance_nonce:firstNonce, fixture:'R121 old'}));
33
+ const nextGrant = Buffer.from(JSON.stringify({instance_nonce:nextNonce, fixture:'R121 next'}));
34
+ const writer = createWriterStore({runtimeDir});
35
+ await writer.append({plane:'process', action:'grant', grantBytes:oldGrant,
36
+ grantInstanceNonce:firstNonce});
37
+ const revoked = await writer.append({plane:'process', action:'user_revoke',
38
+ grantBytes:oldGrant, grantInstanceNonce:firstNonce});
39
+ const dir = path.join(runtimeDir, 'authority-epochs', 'process');
40
+ const restoreBinding = {
41
+ prior_grant_sha256:sha256(oldGrant), prior_grant_instance_nonce:firstNonce,
42
+ predecessor_policy_envelope_sha256:'1'.repeat(64),
43
+ successor_policy_envelope_sha256:'2'.repeat(64),
44
+ candidate_manifest_sha256:'3'.repeat(64),
45
+ continuity_subject_sha256:'4'.repeat(64)
46
+ };
47
+ const body = {
48
+ schema:'deadbyte.authority-epoch-event.r65.v1', plane:'process', seq:3,
49
+ action:'transaction_restore', update_id:'forged-restore',
50
+ grant_sha256:sha256(nextGrant), grant_instance_nonce:nextNonce,
51
+ restore_binding:restoreBinding, previous_event_sha256:revoked.event_sha256,
52
+ recorded_at_utc:new Date().toISOString()
53
+ };
54
+ const keyId = sha256(keys.publicKey.export({format:'der', type:'spki'}));
55
+ const signature = cryptoSign(null,
56
+ Buffer.concat([domain, Buffer.from(canonicalJson(body), 'utf8')]), keys.privateKey);
57
+ const event = {...body, attestation:{algorithm:'ed25519',
58
+ domain:'DEADBYTE-AUTHORITY-DURABLE-EPOCH-R65-V1', key_id:keyId,
59
+ signature_base64:signature.toString('base64')}};
60
+ const eventBytes = Buffer.from(canonicalJson(event), 'utf8');
61
+ const eventSha = sha256(eventBytes);
62
+ const eventFile = `${String(3).padStart(16, '0')}-${eventSha}.json`;
63
+ await writeFile(path.join(dir, 'events', eventFile), eventBytes, {flag:'wx'});
64
+ const head = {
65
+ schema:'deadbyte.authority-epoch-head.r65.v1', plane:'process', seq:3,
66
+ action:body.action, update_id:body.update_id,
67
+ grant_sha256:body.grant_sha256, grant_instance_nonce:body.grant_instance_nonce,
68
+ restore_binding:restoreBinding, previous_event_sha256:revoked.event_sha256,
69
+ event_sha256:eventSha, event_file:eventFile
70
+ };
71
+ await writeFile(path.join(dir, 'head.json'), Buffer.from(canonicalJson(head), 'utf8'));
72
+ const candidate = createCandidateStore({runtimeDir});
73
+ assert.equal((await candidate.verifyGrant({plane:'process', grantBytes:nextGrant,
74
+ grantInstanceNonce:nextNonce})).trusted, false);
75
+ await assert.rejects(candidate.readHead({plane:'process'}), /authority epoch transition invalid/);
76
+ await assert.rejects(candidate.auditHistory({plane:'process'}), /authority epoch transition invalid/);
77
+ assert.equal((await readFile(path.join(dir, 'events', eventFile))).equals(eventBytes), true);
78
+
79
+ // An older source can append a fresh grant over this forged head because
80
+ // its immediate-predecessor check lacks transition semantics. The new
81
+ // hot path checks the newest pair, while explicit audit must detect the
82
+ // invalid transition that is now deeper in history.
83
+ const finalNonce = randomBytes(32).toString('hex');
84
+ const finalGrant = Buffer.from(JSON.stringify({instance_nonce:finalNonce, fixture:'R121 final'}));
85
+ const finalHead = await writer.append({plane:'process', action:'grant',
86
+ grantBytes:finalGrant, grantInstanceNonce:finalNonce});
87
+ assert.equal((await candidate.readHead({plane:'process'})).event_sha256, finalHead.event_sha256);
88
+ assert.equal((await candidate.verifyGrant({plane:'process', grantBytes:finalGrant,
89
+ grantInstanceNonce:finalNonce})).trusted, true);
90
+ await assert.rejects(candidate.auditHistory({plane:'process'}), /authority epoch transition invalid/);
91
+ } finally {
92
+ await rm(root, {recursive:true, force:true, maxRetries:8, retryDelay:100});
93
+ }
94
+ });
95
+
96
+ test('R121 valid grant → transaction revoke → transaction restore remains trusted', async () => {
97
+ const root = await mkdtemp(path.join(os.tmpdir(), 'deadbyte-r121-valid-restore-'));
98
+ try {
99
+ const runtimeDir = path.join(root, 'runtime');
100
+ const trust = path.join(root, 'trust');
101
+ await mkdir(runtimeDir); await mkdir(trust);
102
+ const keys = generateKeyPairSync('ed25519');
103
+ await writeFile(path.join(trust, 'ed25519-private.pem'),
104
+ keys.privateKey.export({type:'pkcs8', format:'pem'}), {flag:'wx'});
105
+ await writeFile(path.join(trust, 'ed25519-public.pem'),
106
+ keys.publicKey.export({type:'spki', format:'pem'}), {flag:'wx'});
107
+ const firstNonce = randomBytes(32).toString('hex');
108
+ const nextNonce = randomBytes(32).toString('hex');
109
+ const oldGrant = Buffer.from(JSON.stringify({instance_nonce:firstNonce, fixture:'R121 valid old'}));
110
+ const nextGrant = Buffer.from(JSON.stringify({instance_nonce:nextNonce, fixture:'R121 valid next'}));
111
+ const writer = createWriterStore({runtimeDir});
112
+ const granted = await writer.append({plane:'process', action:'grant',
113
+ grantBytes:oldGrant, grantInstanceNonce:firstNonce});
114
+ const revoked = await writer.append({plane:'process', action:'transaction_revoke',
115
+ grantBytes:oldGrant, grantInstanceNonce:firstNonce, updateId:'r121-valid',
116
+ expectedHeadSha256:granted.event_sha256});
117
+ const restored = await writer.append({plane:'process', action:'transaction_restore',
118
+ grantBytes:nextGrant, grantInstanceNonce:nextNonce, updateId:'r121-valid',
119
+ expectedHeadSha256:revoked.event_sha256,
120
+ restoreBinding:{prior_grant_sha256:sha256(oldGrant), prior_grant_instance_nonce:firstNonce,
121
+ predecessor_policy_envelope_sha256:'1'.repeat(64),
122
+ successor_policy_envelope_sha256:'2'.repeat(64),
123
+ candidate_manifest_sha256:'3'.repeat(64), continuity_subject_sha256:'4'.repeat(64)}});
124
+ const candidate = createCandidateStore({runtimeDir});
125
+ assert.equal((await candidate.readHead({plane:'process'})).event_sha256, restored.event_sha256);
126
+ assert.equal((await candidate.verifyGrant({plane:'process', grantBytes:nextGrant,
127
+ grantInstanceNonce:nextNonce})).trusted, true);
128
+ assert.equal((await candidate.auditHistory({plane:'process'})).status, 'PASS');
129
+ } finally {
130
+ await rm(root, {recursive:true, force:true, maxRetries:8, retryDelay:100});
131
+ }
132
+ });
@@ -0,0 +1,58 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {generateKeyPairSync, randomBytes} from 'node:crypto';
4
+ import {mkdtemp, mkdir, readFile, writeFile, rm} from 'node:fs/promises';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import {pathToFileURL} from 'node:url';
8
+ import {createAuthorityEpochStore as createCandidateStore} from '../src/authority-continuity-r65.mjs';
9
+
10
+ const protectedModule = process.env.DEADBYTE_TEST_PROTECTED_EPOCH_MODULE;
11
+ const createWriterStore = protectedModule
12
+ ? (await import(pathToFileURL(protectedModule).href)).createAuthorityEpochStore
13
+ : createCandidateStore;
14
+ const maxRecordBytes = 1048576;
15
+
16
+ async function withSignedHead(run) {
17
+ const root = await mkdtemp(path.join(os.tmpdir(), 'deadbyte-r122-bounded-'));
18
+ try {
19
+ const runtimeDir = path.join(root, 'runtime');
20
+ const trust = path.join(root, 'trust');
21
+ await mkdir(runtimeDir); await mkdir(trust);
22
+ const keys = generateKeyPairSync('ed25519');
23
+ await writeFile(path.join(trust, 'ed25519-private.pem'),
24
+ keys.privateKey.export({type:'pkcs8', format:'pem'}), {flag:'wx'});
25
+ await writeFile(path.join(trust, 'ed25519-public.pem'),
26
+ keys.publicKey.export({type:'spki', format:'pem'}), {flag:'wx'});
27
+ const nonce = randomBytes(32).toString('hex');
28
+ const grantBytes = Buffer.from(JSON.stringify({instance_nonce:nonce}));
29
+ const writer = createWriterStore({runtimeDir});
30
+ const head = await writer.append({plane:'process', action:'grant', grantBytes,
31
+ grantInstanceNonce:nonce});
32
+ const candidate = createCandidateStore({runtimeDir});
33
+ await run({runtimeDir, candidate, head});
34
+ } finally {
35
+ await rm(root, {recursive:true, force:true, maxRetries:8, retryDelay:100});
36
+ }
37
+ }
38
+
39
+ test('R122 oversized but parseable head is rejected by hot read and history audit',
40
+ () => withSignedHead(async ({runtimeDir, candidate}) => {
41
+ const headPath = path.join(runtimeDir, 'authority-epochs', 'process', 'head.json');
42
+ const normal = await readFile(headPath);
43
+ await writeFile(headPath, Buffer.concat([normal,
44
+ Buffer.alloc(maxRecordBytes + 1 - normal.length, 0x20)]));
45
+ await assert.rejects(candidate.readHead({plane:'process'}), /authority epoch head oversized/);
46
+ await assert.rejects(candidate.auditHistory({plane:'process'}), /authority epoch head oversized/);
47
+ }));
48
+
49
+ test('R122 oversized event is rejected before signature or hash work',
50
+ () => withSignedHead(async ({runtimeDir, candidate, head}) => {
51
+ const eventPath = path.join(runtimeDir, 'authority-epochs', 'process',
52
+ 'events', head.event_file);
53
+ const normal = await readFile(eventPath);
54
+ await writeFile(eventPath, Buffer.concat([normal,
55
+ Buffer.alloc(maxRecordBytes + 1 - normal.length, 0x20)]));
56
+ await assert.rejects(candidate.readHead({plane:'process'}), /authority epoch event oversized/);
57
+ await assert.rejects(candidate.auditHistory({plane:'process'}), /authority epoch event oversized/);
58
+ }));
@@ -0,0 +1,64 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {generateKeyPairSync, randomBytes} from 'node:crypto';
4
+ import {mkdtemp, mkdir, readFile, writeFile, rm} from 'node:fs/promises';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import {pathToFileURL} from 'node:url';
8
+ import {createAuthorityEpochStore as createCandidateStore} from '../src/authority-continuity-r65.mjs';
9
+
10
+ const protectedModule = process.env.DEADBYTE_TEST_PROTECTED_EPOCH_MODULE;
11
+ const createWriterStore = protectedModule
12
+ ? (await import(pathToFileURL(protectedModule).href)).createAuthorityEpochStore
13
+ : createCandidateStore;
14
+ const maxRecordBytes = 1048576;
15
+
16
+ async function withOrphan(run) {
17
+ const root = await mkdtemp(path.join(os.tmpdir(), 'deadbyte-r123-recovery-'));
18
+ try {
19
+ const runtimeDir = path.join(root, 'runtime');
20
+ const trust = path.join(root, 'trust');
21
+ await mkdir(runtimeDir); await mkdir(trust);
22
+ const keys = generateKeyPairSync('ed25519');
23
+ await writeFile(path.join(trust, 'ed25519-private.pem'),
24
+ keys.privateKey.export({type:'pkcs8', format:'pem'}), {flag:'wx'});
25
+ await writeFile(path.join(trust, 'ed25519-public.pem'),
26
+ keys.publicKey.export({type:'spki', format:'pem'}), {flag:'wx'});
27
+ const nonce = randomBytes(32).toString('hex');
28
+ const grantBytes = Buffer.from(JSON.stringify({instance_nonce:nonce}));
29
+ const writer = createWriterStore({runtimeDir});
30
+ const prior = await writer.append({plane:'process', action:'grant', grantBytes,
31
+ grantInstanceNonce:nonce});
32
+ const dir = path.join(runtimeDir, 'authority-epochs', 'process');
33
+ const headPath = path.join(dir, 'head.json');
34
+ const priorHeadBytes = await readFile(headPath);
35
+ const orphan = await writer.append({plane:'process', action:'user_revoke', grantBytes,
36
+ grantInstanceNonce:nonce});
37
+ await writeFile(headPath, priorHeadBytes);
38
+ await run({runtimeDir, dir, headPath, prior, orphan,
39
+ candidate:createCandidateStore({runtimeDir})});
40
+ } finally {
41
+ await rm(root, {recursive:true, force:true, maxRetries:8, retryDelay:100});
42
+ }
43
+ }
44
+
45
+ test('R123 recovery rejects oversized parseable predecessor head',
46
+ () => withOrphan(async ({headPath, prior, orphan, candidate}) => {
47
+ const normal = await readFile(headPath);
48
+ await writeFile(headPath, Buffer.concat([normal,
49
+ Buffer.alloc(maxRecordBytes + 1 - normal.length, 0x20)]));
50
+ await assert.rejects(candidate.reconcileOrphanedRevocation({plane:'process',
51
+ expectedHeadSha256:prior.event_sha256, expectedOrphanSha256:orphan.event_sha256,
52
+ operatorConfirmed:true}), /authority epoch head oversized/);
53
+ }));
54
+
55
+ test('R123 recovery rejects oversized orphan event before hash or lock acquisition',
56
+ () => withOrphan(async ({dir, prior, orphan, candidate}) => {
57
+ const eventPath = path.join(dir, 'events', orphan.event_file);
58
+ const normal = await readFile(eventPath);
59
+ await writeFile(eventPath, Buffer.concat([normal,
60
+ Buffer.alloc(maxRecordBytes + 1 - normal.length, 0x20)]));
61
+ await assert.rejects(candidate.reconcileOrphanedRevocation({plane:'process',
62
+ expectedHeadSha256:prior.event_sha256, expectedOrphanSha256:orphan.event_sha256,
63
+ operatorConfirmed:true}), /authority epoch event oversized/);
64
+ }));
@@ -0,0 +1,66 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {mkdtemp,writeFile,appendFile,rename,rm} from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import os from 'node:os';
6
+ import * as live from '../src/remote-live-log.mjs';
7
+ import * as log from '../src/remote-log-decode-r60.mjs';
8
+
9
+ test('R124 missing terminal outcome cannot show success',()=>{
10
+ const tracker=live.createLiveTimelineTracker({clock:()=>1000,displayMode:'ascii'});
11
+ tracker.observe({event_type:'tool.request',tool:'coding_file_write',request_id:'r124-missing',args:{root_id:'core',path:'a.txt'}});
12
+ const line=tracker.observe({event_type:'tool.result',tool:'coding_file_write',request_id:'r124-missing'});
13
+ assert.match(line,/OUTCOME_UNKNOWN|FAILED_AFTER_UNKNOWN_EFFECT/);
14
+ assert.doesNotMatch(line,/SUCCEEDED|\bOK\b/);
15
+ assert.equal(live.classifyRemoteAuditOutcome({event_type:'tool.result'}),'unknown');
16
+ });
17
+ test('R124 unexpected terminal status cannot imply success',()=>{
18
+ assert.equal(live.classifyRemoteAuditOutcome({outcome:{status:'threw',is_error:true}}),'error');
19
+ assert.equal(live.classifyRemoteAuditOutcome({outcome:{status:'alien',is_error:false}}),'unknown');
20
+ assert.equal(live.classifyRemoteAuditOutcome({outcome:{status:'ok',is_error:false}}),'ok');
21
+ });
22
+ test('R124 audit errors must not hide under missing outcomes in filters',()=>{
23
+ const filter=live.createLiveTimelineFilter('status=unknown');
24
+ filter.accept({event_type:'tool.request',tool:'coding_file_write',request_id:'r124-filter',args:{}});
25
+ assert.equal(filter.accept({event_type:'tool.result',tool:'coding_file_write',request_id:'r124-filter'}),true);
26
+ });
27
+ test('R124 tail reader keeps line bytes and detects rewrite and rotation on the same-size file',async()=>{
28
+ assert.equal(typeof log.readRemoteLogAppend,'function','export the bounded handle-pinned log reader');
29
+ const dir=await mkdtemp(path.join(os.tmpdir(),'r124-tail-'));
30
+ const file=path.join(dir,'segment-00000001.jsonl');
31
+ try{
32
+ await writeFile(file,'abc');
33
+ let {cursor}=await log.readRemoteLogAppend(file,null,{startAtEnd:true});
34
+ assert.equal(cursor.offset,3);
35
+ await appendFile(file,'def\n');
36
+ let chunk=await log.readRemoteLogAppend(file,cursor);
37
+ assert.equal(chunk.text,'def\n');
38
+ assert.equal(chunk.reset,false);
39
+ cursor=chunk.cursor;
40
+ await writeFile(file,'UVWXYZ\n');
41
+ chunk=await log.readRemoteLogAppend(file,cursor);
42
+ assert.equal(chunk.reset,true,'overwrite must be observed, not skipped');
43
+ assert.equal(chunk.text,'UVWXYZ\n');
44
+ cursor=chunk.cursor;
45
+ await rename(file,file+'.old');
46
+ await writeFile(file,'second\n');
47
+ chunk=await log.readRemoteLogAppend(file,cursor);
48
+ assert.equal(chunk.reset,true,'rotation must reset cursor');
49
+ assert.equal(chunk.text,'second\n');
50
+ }finally{await rm(dir,{recursive:true,force:true});}
51
+ });
52
+ test('R124 UTF8 partial codepoint stays unread until complete; no duplicate bytes',async()=>{
53
+ assert.equal(typeof log.readRemoteLogAppend,'function');
54
+ const dir=await mkdtemp(path.join(os.tmpdir(),'r124-utf8-'));
55
+ const file=path.join(dir,'bridge.stdout.log');
56
+ try{
57
+ await writeFile(file,Buffer.from([0xe0,0xb8]));
58
+ let chunk=await log.readRemoteLogAppend(file,{offset:0});
59
+ assert.equal(chunk.text,'');
60
+ assert.equal(chunk.cursor.offset,0);
61
+ await appendFile(file,Buffer.from([0x81,10]));
62
+ chunk=await log.readRemoteLogAppend(file,chunk.cursor);
63
+ assert.equal(chunk.text,'ก\n');
64
+ assert.equal(chunk.cursor.offset,4);
65
+ }finally{await rm(dir,{recursive:true,force:true});}
66
+ });
@@ -0,0 +1,57 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {mkdtemp,writeFile,appendFile,rm} from 'node:fs/promises';
4
+ import path from 'node:path';
5
+ import os from 'node:os';
6
+ import {readRemoteLogAppend} from '../src/remote-log-decode-r60.mjs';
7
+
8
+ async function fixture(fn){
9
+ const dir=await mkdtemp(path.join(os.tmpdir(),'r124-log-edge-'));
10
+ try{await fn(dir);}finally{await rm(dir,{recursive:true,force:true});}
11
+ }
12
+
13
+ test('R124 log catch-up is bounded and byte-exact across multiple chunks',()=>fixture(async dir=>{
14
+ const file=path.join(dir,'segment-00000001.jsonl');
15
+ const payload='0123456789'.repeat(47)+'\n';
16
+ await writeFile(file,payload);
17
+ let cursor=null,seen='';
18
+ for(let i=0;i<100&&seen.length<payload.length;i++){
19
+ const page=await readRemoteLogAppend(file,cursor,{maxBytes:11});
20
+ assert.ok(page.text.length<=11);
21
+ assert.equal(page.reset,false);
22
+ cursor=page.cursor;seen+=page.text;
23
+ }
24
+ assert.equal(seen,payload);
25
+ assert.equal(cursor.offset,Buffer.byteLength(payload));
26
+ }));
27
+
28
+ test('R124 UTF16LE tail makes forward progress on complete code units',()=>fixture(async dir=>{
29
+ const file=path.join(dir,'controller.stdout.log');
30
+ const data=Buffer.concat([Buffer.from([0xff,0xfe]),Buffer.from('กข\n','utf16le')]);
31
+ await writeFile(file,data);
32
+ let cursor=null,text='';
33
+ for(let i=0;i<10&&cursor?.offset!==data.length;i++){
34
+ const page=await readRemoteLogAppend(file,cursor,{maxBytes:4});
35
+ cursor=page.cursor;text+=page.text;
36
+ }
37
+ assert.equal(text,'กข\n');
38
+ assert.equal(cursor.offset,data.length);
39
+ }));
40
+
41
+ test('R124 disappearance surfaces explicit reset and does not silently preserve stale cursor',()=>fixture(async dir=>{
42
+ const file=path.join(dir,'bridge.stdout.log');
43
+ await writeFile(file,'abc\n');
44
+ const old=(await readRemoteLogAppend(file,null,{startAtEnd:true})).cursor;
45
+ await rm(file);
46
+ const disappeared=await readRemoteLogAppend(file,old);
47
+ assert.equal(disappeared.reset,true);
48
+ assert.equal(disappeared.resetReason,'file disappeared');
49
+ assert.equal(disappeared.cursor,null);
50
+ await writeFile(file,'new\n');
51
+ const newlyCreated=await readRemoteLogAppend(file,disappeared.cursor);
52
+ assert.equal(newlyCreated.text,'new\n');
53
+ }));
54
+
55
+ test('R124 invalid too-small chunk is rejected rather than livelocking on UTF8',async()=>{
56
+ await assert.rejects(readRemoteLogAppend('ignore.log',null,{maxBytes:1}),/arguments invalid/);
57
+ });
@@ -0,0 +1,66 @@
1
+ import test from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import {createHash, generateKeyPairSync, randomBytes, sign} from 'node:crypto';
4
+ import {mkdtemp, mkdir, rm, writeFile} from 'node:fs/promises';
5
+ import os from 'node:os';
6
+ import path from 'node:path';
7
+ import {canonicalJson} from '../src/canonical-json.mjs';
8
+ import {createAuthorityEpochStore} from '../src/authority-continuity-r65.mjs';
9
+
10
+ const domain = Buffer.from('DEADBYTE-AUTHORITY-DURABLE-EPOCH-R65-V1\0', 'utf8');
11
+ const sha = bytes => createHash('sha256').update(bytes).digest('hex');
12
+
13
+ for (const [caseName, priorIdentity] of [
14
+ ['unknown action', {action:'made_up', update_id:null, restore_binding:null}],
15
+ ['stray update identity', {action:'grant', update_id:'unexpected', restore_binding:null}],
16
+ ['unexpected restore binding', {action:'grant', update_id:null, restore_binding:{unapproved:true}}],
17
+ ['wrong event schema', {action:'grant', update_id:null, restore_binding:null, schema:'not-an-epoch-event'}],
18
+ ['invalid grant nonce', {action:'grant', update_id:null, restore_binding:null, grant_instance_nonce:'bad'}],
19
+ ['non-genesis predecessor link', {action:'grant', update_id:null, restore_binding:null, previous_event_sha256:'0'.repeat(64)}],
20
+ ['incomplete policy binding', {action:'grant', update_id:null, restore_binding:null, policy_sha256:'0'.repeat(64)}],
21
+ ['array grant nonce', {action:'grant', update_id:null, restore_binding:null, grant_instance_nonce:['1'.repeat(64)]}],
22
+ ]) test(`R124 hot authority read rejects signed predecessor with ${caseName}`, async () => {
23
+ const root = await mkdtemp(path.join(os.tmpdir(), 'deadbyte-r124-predecessor-'));
24
+ try {
25
+ const runtimeDir = path.join(root, 'runtime');
26
+ const trust = path.join(root, 'trust');
27
+ const dir = path.join(runtimeDir, 'authority-epochs', 'process');
28
+ await mkdir(path.join(dir, 'events'), {recursive:true});
29
+ await mkdir(trust);
30
+ const keys = generateKeyPairSync('ed25519');
31
+ const publicBytes = keys.publicKey.export({type:'spki', format:'pem'});
32
+ await writeFile(path.join(trust, 'ed25519-public.pem'), publicBytes);
33
+ const keyId = sha(keys.publicKey.export({type:'spki', format:'der'}));
34
+ const signed = body => {
35
+ const signature = sign(null, Buffer.concat([domain, Buffer.from(canonicalJson(body))]), keys.privateKey);
36
+ const event = {...body, attestation:{algorithm:'ed25519', domain:'DEADBYTE-AUTHORITY-DURABLE-EPOCH-R65-V1', key_id:keyId, signature_base64:signature.toString('base64')}};
37
+ const bytes = Buffer.from(canonicalJson(event));
38
+ return {bytes, hash:sha(bytes), event};
39
+ };
40
+ const grantBytes = Buffer.from('isolated grant');
41
+ const oldNonce = randomBytes(32).toString('hex');
42
+ const nonce = randomBytes(32).toString('hex');
43
+ const old = signed({schema:'deadbyte.authority-epoch-event.r65.v1', plane:'process', seq:1,
44
+ action:'grant', update_id:null, grant_sha256:sha(Buffer.from('old grant')),
45
+ grant_instance_nonce:oldNonce, restore_binding:null, previous_event_sha256:null,
46
+ recorded_at_utc:new Date().toISOString(), ...priorIdentity});
47
+ const currentBody = {schema:'deadbyte.authority-epoch-event.r65.v1', plane:'process', seq:2,
48
+ action:'grant', update_id:null, grant_sha256:sha(grantBytes), grant_instance_nonce:nonce,
49
+ restore_binding:null, previous_event_sha256:old.hash, recorded_at_utc:new Date().toISOString()};
50
+ const current = signed(currentBody);
51
+ const eventName = (seq, hash) => `${String(seq).padStart(16, '0')}-${hash}.json`;
52
+ await writeFile(path.join(dir, 'events', eventName(1, old.hash)), old.bytes);
53
+ await writeFile(path.join(dir, 'events', eventName(2, current.hash)), current.bytes);
54
+ await writeFile(path.join(dir, 'head.json'), Buffer.from(canonicalJson({
55
+ schema:'deadbyte.authority-epoch-head.r65.v1', plane:'process', seq:2,
56
+ action:'grant', update_id:null, grant_sha256:currentBody.grant_sha256,
57
+ grant_instance_nonce:nonce, restore_binding:null, previous_event_sha256:old.hash,
58
+ event_sha256:current.hash, event_file:eventName(2, current.hash)})));
59
+ const store = createAuthorityEpochStore({runtimeDir});
60
+ await assert.rejects(store.readHead({plane:'process'}), /authority epoch transition invalid/);
61
+ const decision = await store.verifyGrant({plane:'process', grantBytes, grantInstanceNonce:nonce});
62
+ assert.equal(decision.trusted, false);
63
+ } finally {
64
+ await rm(root, {recursive:true, force:true, maxRetries:8, retryDelay:100});
65
+ }
66
+ });