deadbyte-mcp 0.15.41 → 0.15.43
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.
- package/DEADBYTE-MCP.cmd +8 -4
- package/MANIFEST.SHA256 +88 -43
- package/PROVENANCE.json +66 -15
- package/README.txt +27 -18
- package/RELEASE.json +55 -15
- package/SBOM.cdx.json +2 -2
- package/TEST-MATRIX.json +117 -14
- package/bridge/package.json +1 -1
- package/controller/deadbyte-controller.ps1 +132 -12
- package/controller/deadbyte-process-policy.json +4 -4
- package/docs/R65-AUTHORITY-CONTINUITY-DESIGN.json +27 -0
- package/docs/R65-REFERENCE-GAP-MATRIX.json +22 -0
- package/package.json +6 -4
- package/scripts/deploy-r48.mjs +123 -7
- package/scripts/gate-windows-r65-prepublish.ps1 +65 -0
- package/scripts/gate-windows-r66-prepublish.ps1 +65 -0
- package/scripts/npm-publication-r65.mjs +139 -0
- package/scripts/npm-release-r48.mjs +56 -37
- package/scripts/r65-canonical-full.mjs +45 -0
- package/scripts/r65-focused.mjs +37 -0
- package/scripts/verify-r65-parent-source.mjs +75 -0
- package/src/activation-continuity-engine-r65.mjs +225 -0
- package/src/activation-lock-r65.mjs +155 -0
- package/src/activation-preflight-r65.mjs +197 -0
- package/src/authority-continuity-r65.mjs +541 -0
- package/src/authority-epoch-cli-r65.mjs +10 -0
- package/src/autonomous-policy.mjs +7 -3
- package/src/coding-plane.mjs +12 -3
- package/src/desktop-policy-r40.mjs +6 -3
- package/src/doctor-runtime-r58.mjs +3 -1
- package/src/doctor-transactions-r58.mjs +1 -1
- package/src/host-plane.mjs +9 -3
- package/src/process-policy.mjs +7 -4
- package/src/remote-authority.mjs +10 -1
- package/src/runtime-update.mjs +15 -5
- package/src/version.mjs +1 -1
- package/test/audit-context.test.mjs +15 -4
- package/test/autonomous-local-supervisor.test.mjs +7 -6
- package/test/autonomous-supervisor-integration.test.mjs +8 -7
- package/test/capability-resolver.test.mjs +11 -5
- package/test/codex-autonomous-r44.test.mjs +7 -6
- package/test/coding-observation.test.mjs +4 -3
- package/test/commander-real-parity-r47.test.mjs +5 -4
- package/test/commander-torture-r47.test.mjs +5 -4
- package/test/desktop-policy-r40.test.mjs +6 -4
- package/test/desktop-runtime-r40.test.mjs +8 -3
- package/test/host-inline-r45.test.mjs +4 -3
- package/test/machine-fs-runtime.test.mjs +4 -3
- package/test/process-longrun-r36.test.mjs +3 -2
- package/test/process-policy.test.mjs +10 -6
- package/test/process-runtime.test.mjs +9 -6
- package/test/r52-host-revocation.test.mjs +1 -0
- package/test/r58-doctor-runtime.test.mjs +18 -0
- package/test/r59-tool-surface-compat.test.mjs +1 -1
- package/test/r64-release-contract.test.mjs +2 -1
- package/test/r65-activation-lock-recovery.test.mjs +72 -0
- package/test/r65-activation-lock.test.mjs +59 -0
- package/test/r65-activation-orchestration.test.mjs +179 -0
- package/test/r65-activation-preflight.test.mjs +35 -0
- package/test/r65-authority-continuity-decision.test.mjs +58 -0
- package/test/r65-authority-durable-policy-binding.test.mjs +34 -0
- package/test/r65-authority-epoch-cli.test.mjs +125 -0
- package/test/r65-authority-epoch-races.test.mjs +87 -0
- package/test/r65-authority-epoch-recovery.test.mjs +82 -0
- package/test/r65-authority-epoch-store.test.mjs +94 -0
- package/test/r65-authority-fixture.mjs +41 -0
- package/test/r65-authority-grant-capture.test.mjs +37 -0
- package/test/r65-authority-observation-epoch.test.mjs +25 -0
- package/test/r65-authority-policy-envelope.test.mjs +46 -0
- package/test/r65-authority-restored-safe.test.mjs +75 -0
- package/test/r65-authority-runtime-epoch.test.mjs +115 -0
- package/test/r65-authority-runtime-policy-binding.test.mjs +29 -0
- package/test/r65-authority-test-trust.mjs +20 -0
- package/test/r65-cli-invocation.test.mjs +38 -0
- package/test/r65-controller-authority-epoch-live.test.mjs +100 -0
- package/test/r65-controller-authority-epoch.test.mjs +37 -0
- package/test/r65-deploy-preflight.test.mjs +55 -0
- package/test/r65-epoch-native-activation-integration.test.mjs +141 -0
- package/test/r65-epoch-native-continuity-engine.test.mjs +112 -0
- package/test/r65-epoch-native-cross-plane.test.mjs +95 -0
- package/test/r65-legacy-authority-migration.test.mjs +63 -0
- package/test/r65-legacy-auto-restore-rejection.test.mjs +17 -0
- package/test/r65-npm-publication.test.mjs +114 -0
- package/test/r65-parent-source-verifier.test.mjs +63 -0
- package/test/r65-release-contract.test.mjs +75 -0
- package/test/r65-transaction-precondition.test.mjs +36 -0
- package/test/r66-release-contract.test.mjs +56 -0
- package/test/remote-session-cli.test.mjs +6 -6
- package/test/standing-grants.test.mjs +20 -4
|
@@ -7,6 +7,7 @@ import path from 'node:path';
|
|
|
7
7
|
import { createDesktopFrame } from '../src/desktop-contract.mjs';
|
|
8
8
|
import { DESKTOP_POLICY_SCHEMA,DESKTOP_GRANT_SCHEMA } from '../src/desktop-policy-r40.mjs';
|
|
9
9
|
import { createDesktopRuntime,verifyDesktopReceiptObject } from '../src/desktop-runtime-r40.mjs';
|
|
10
|
+
import { writeBoundStandingGrant } from './r65-authority-fixture.mjs';
|
|
10
11
|
|
|
11
12
|
const H=value=>createHash('sha256').update(String(value)).digest('hex');
|
|
12
13
|
const EPOCH=H('epoch');
|
|
@@ -21,7 +22,7 @@ function F(at,image=H('same'),window=WIN){
|
|
|
21
22
|
}
|
|
22
23
|
async function fixture({grantActions=['click_primary','text_input'],captureFrames=null,times=null,inputProvider=null,grounderProvider=null,clockMs=null,sleeper=null}={}){
|
|
23
24
|
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r40-desktop-runtime-'));
|
|
24
|
-
const grant=path.join(root,'grant.json'),evidence=path.join(root,'evidence');
|
|
25
|
+
const grant=path.join(root,'runtime','grant.json'),evidence=path.join(root,'evidence');
|
|
25
26
|
const policy={
|
|
26
27
|
schema:DESKTOP_POLICY_SCHEMA,enabled:true,policy_path:path.join(root,'policy.json'),
|
|
27
28
|
policy_sha256:H('policy'),grant_file:grant,evidence_dir:evidence,
|
|
@@ -31,9 +32,13 @@ async function fixture({grantActions=['click_primary','text_input'],captureFrame
|
|
|
31
32
|
allowed_actions:['pointer_move','click_primary','click_secondary','double_click_primary','scroll','key_chord','text_input'],
|
|
32
33
|
limits:{max_frame_age_ms:2000,max_frame_bytes:8*1024*1024,capture_timeout_ms:5000,input_timeout_ms:2000,post_observe_timeout_ms:5000,max_preview_bytes:512*1024,max_text_bytes:4096}
|
|
33
34
|
};
|
|
34
|
-
|
|
35
|
+
const {policy_path:policyPath,policy_sha256:_unusedSha,...rawPolicy}=policy;
|
|
36
|
+
const policyBytes=Buffer.from(JSON.stringify(rawPolicy));
|
|
37
|
+
await writeFile(policyPath,policyBytes);
|
|
38
|
+
policy.policy_sha256=createHash('sha256').update(policyBytes).digest('hex');
|
|
39
|
+
await writeBoundStandingGrant('desktop', grant, {
|
|
35
40
|
schema:DESKTOP_GRANT_SCHEMA,policy_sha256:policy.policy_sha256,instance_nonce:H('grant-nonce'),action_kinds:grantActions
|
|
36
|
-
})
|
|
41
|
+
});
|
|
37
42
|
const expected=F('2026-09-19T02:00:00.000Z');
|
|
38
43
|
const frames=captureFrames??[
|
|
39
44
|
{frame:F('2026-09-19T02:00:00.700Z'),backend:'fake',byte_length:4},
|
|
@@ -7,13 +7,14 @@ import path from 'node:path';
|
|
|
7
7
|
import {
|
|
8
8
|
HOST_GRANT_SCHEMA, HOST_POLICY_SCHEMA_V2, hostExecInline, hostExecNative, loadHostPolicy
|
|
9
9
|
} from '../src/host-plane.mjs';
|
|
10
|
+
import { writeBoundStandingGrant } from './r65-authority-fixture.mjs';
|
|
10
11
|
|
|
11
12
|
const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
12
13
|
|
|
13
14
|
async function fixture(){
|
|
14
15
|
const dir=await mkdtemp(path.join(os.tmpdir(),'deadbyte-host-inline-r45-'));
|
|
15
16
|
const root=path.join(dir,'root');await mkdir(root);
|
|
16
|
-
const arm=path.join(dir,'arm.json'), policyPath=path.join(dir,'policy.json');
|
|
17
|
+
const arm=path.join(dir,'runtime','arm.json'), policyPath=path.join(dir,'policy.json');
|
|
17
18
|
const exeSha=sha(await readFile(process.execPath));
|
|
18
19
|
const raw={
|
|
19
20
|
schema:HOST_POLICY_SCHEMA_V2,enabled:true,arm_file:arm,
|
|
@@ -27,8 +28,8 @@ async function fixture(){
|
|
|
27
28
|
};
|
|
28
29
|
await writeFile(policyPath,JSON.stringify(raw,null,2));
|
|
29
30
|
const policy=await loadHostPolicy(policyPath);
|
|
30
|
-
await
|
|
31
|
-
instance_nonce:randomBytes(32).toString('hex'),armed_at_utc:new Date().toISOString()},
|
|
31
|
+
await writeBoundStandingGrant('host', arm, {schema:HOST_GRANT_SCHEMA,policy_sha256:policy.policy_sha256,
|
|
32
|
+
instance_nonce:randomBytes(32).toString('hex'),armed_at_utc:new Date().toISOString()},{space:2});
|
|
32
33
|
return {dir,root,arm,policy,exeSha};
|
|
33
34
|
}
|
|
34
35
|
|
|
@@ -7,6 +7,7 @@ import path from 'node:path';
|
|
|
7
7
|
import { generateTrustKeypair } from '../src/trust.mjs';
|
|
8
8
|
import { loadCodingPolicy } from '../src/coding-plane.mjs';
|
|
9
9
|
import { createMachineFsRuntime, verifyMachineFsReceiptFile } from '../src/machine-fs.mjs';
|
|
10
|
+
import { writeBoundStandingGrant } from './r65-authority-fixture.mjs';
|
|
10
11
|
|
|
11
12
|
const sha = bytes => createHash('sha256').update(bytes).digest('hex');
|
|
12
13
|
|
|
@@ -15,17 +16,17 @@ async function fixture({ readWorkerPath = undefined, listEntryLstat = undefined
|
|
|
15
16
|
const project = path.join(root, 'project');
|
|
16
17
|
await mkdir(project);
|
|
17
18
|
const policyPath = path.join(root, 'coding-policy.json');
|
|
18
|
-
const grantPath = path.join(root, 'coding-grant.json');
|
|
19
|
+
const grantPath = path.join(root, 'runtime', 'coding-grant.json');
|
|
19
20
|
await writeFile(policyPath, JSON.stringify({
|
|
20
21
|
schema: 'deadbyte.coding-policy.v1', enabled: true,
|
|
21
22
|
lease_file: grantPath, evidence_dir: path.join(root, 'evidence'),
|
|
22
23
|
roots: { project: { path: project, read: true, write: true, exec: false, deny_paths: ['blocked'] } },
|
|
23
24
|
commands: {}, max_read_bytes: 65536, max_write_bytes: 65536
|
|
24
25
|
})); const policy = await loadCodingPolicy(policyPath);
|
|
25
|
-
await
|
|
26
|
+
await writeBoundStandingGrant('coding', grantPath, {
|
|
26
27
|
schema: 'deadbyte.coding-grant.v2', policy_sha256: policy.policy_sha256,
|
|
27
28
|
instance_nonce: 'b'.repeat(64), root_ids: ['project'], granted_at_utc: new Date().toISOString()
|
|
28
|
-
})
|
|
29
|
+
});
|
|
29
30
|
const keys = await generateTrustKeypair({
|
|
30
31
|
privateKeyPath: path.join(root, 'private.pem'), publicKeyPath: path.join(root, 'public.pem')
|
|
31
32
|
});
|
|
@@ -8,6 +8,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
8
8
|
import { PROCESS_GRANT_SCHEMA, PROCESS_POLICY_SCHEMA, loadProcessPolicy } from '../src/process-policy.mjs';
|
|
9
9
|
import { createProcessRuntime } from '../src/process-runtime.mjs';
|
|
10
10
|
import { generateTrustKeypair } from '../src/trust.mjs';
|
|
11
|
+
import { writeBoundStandingGrant } from './r65-authority-fixture.mjs';
|
|
11
12
|
|
|
12
13
|
const repo=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');
|
|
13
14
|
const host=path.join(repo,'bin','deadbyte-process-host.exe');
|
|
@@ -20,11 +21,11 @@ async function fixture(){
|
|
|
20
21
|
await mkdir(project); await mkdir(trust);
|
|
21
22
|
const script=path.join(project,'longrun.mjs');
|
|
22
23
|
await writeFile(script,`process.stdout.write('chunk-1\\n');\nsetTimeout(()=>process.stdout.write('chunk-2\\n'),180);\nsetTimeout(()=>process.stdout.write('chunk-3\\n'),360);\nsetTimeout(()=>process.exit(0),540);\n`);
|
|
23
|
-
const grantFile=path.join(root,'process-grant.json'); const stateDir=path.join(root,'state'); const policyPath=path.join(root,'process-policy.json');
|
|
24
|
+
const grantFile=path.join(root,'runtime','process-grant.json'); const stateDir=path.join(root,'state'); const policyPath=path.join(root,'process-policy.json');
|
|
24
25
|
const raw={schema:PROCESS_POLICY_SCHEMA,enabled:true,grant_file:grantFile,state_dir:stateDir,native_host:{path:host,sha256:sha(await readFile(host))},roots:{core:{path:project,deny_paths:[]}},profiles:{longrun:{executable:process.execPath,executable_sha256:sha(await readFile(process.execPath)),root_ids:['core'],transport:'pipe',prefix_args:[script],max_args:0,max_arg_bytes:256,max_sessions:1,max_input_bytes:4096,max_retained_output_bytes:65536,env_allow:['PATH','TEMP','TMP'],input_actions:{}}}};
|
|
25
26
|
await writeFile(policyPath,JSON.stringify(raw));
|
|
26
27
|
const policy=await loadProcessPolicy(policyPath);
|
|
27
|
-
await
|
|
28
|
+
await writeBoundStandingGrant('process', grantFile, {schema:PROCESS_GRANT_SCHEMA,policy_sha256:policy.policy_sha256,instance_nonce:'6'.repeat(64),profile_ids:['longrun'],root_ids:['core'],granted_at_utc:new Date().toISOString()});
|
|
28
29
|
const keys=await generateTrustKeypair({privateKeyPath:path.join(trust,'private.pem'),publicKeyPath:path.join(trust,'public.pem')});
|
|
29
30
|
const make=()=>createProcessRuntime({policy,signingKeyPath:keys.privateKeyPath,verifyKeyPath:keys.publicKeyPath});
|
|
30
31
|
return {root,project,stateDir,policy,keys,make,runtime:make()};
|
|
@@ -12,17 +12,19 @@ import {
|
|
|
12
12
|
processSessionStatus,
|
|
13
13
|
recheckProcessAuthority
|
|
14
14
|
} from '../src/process-policy.mjs';
|
|
15
|
+
import { writeBoundStandingGrant } from './r65-authority-fixture.mjs';
|
|
15
16
|
|
|
16
17
|
const sha = bytes => createHash('sha256').update(bytes).digest('hex');
|
|
17
18
|
|
|
18
19
|
async function fixture() {
|
|
19
20
|
const root = await mkdtemp(path.join(os.tmpdir(), 'deadbyte-process-policy-'));
|
|
21
|
+
await mkdir(path.join(root,'runtime'));
|
|
20
22
|
const project = path.join(root, 'project');
|
|
21
23
|
await mkdir(project);
|
|
22
24
|
const executable = process.execPath;
|
|
23
25
|
const executableSha = sha(await readFile(executable));
|
|
24
26
|
const policyPath = path.join(root, 'process-policy.json');
|
|
25
|
-
const grantFile = path.join(root, 'process-grant.json');
|
|
27
|
+
const grantFile = path.join(root, 'runtime', 'process-grant.json');
|
|
26
28
|
const stateDir = path.join(root, 'process-state'); const raw = {
|
|
27
29
|
schema: PROCESS_POLICY_SCHEMA, enabled: true, grant_file: grantFile, state_dir: stateDir,
|
|
28
30
|
native_host: { path: executable, sha256: executableSha },
|
|
@@ -52,14 +54,14 @@ test('process policy loads pinned native host, profile and root while grant is a
|
|
|
52
54
|
});test('process standing grant binds exact policy, profile and root', async () => {
|
|
53
55
|
const f = await fixture();
|
|
54
56
|
const policy = await loadProcessPolicy(f.policyPath);
|
|
55
|
-
await
|
|
57
|
+
await writeBoundStandingGrant('process', f.grantFile, {
|
|
56
58
|
schema: PROCESS_GRANT_SCHEMA,
|
|
57
59
|
policy_sha256: policy.policy_sha256,
|
|
58
60
|
instance_nonce: 'a'.repeat(64),
|
|
59
61
|
profile_ids: ['node'],
|
|
60
62
|
root_ids: ['core'],
|
|
61
63
|
granted_at_utc: '2026-09-14T00:00:00Z'
|
|
62
|
-
})
|
|
64
|
+
});
|
|
63
65
|
const status = await processSessionStatus(policy);
|
|
64
66
|
assert.equal(status.armed, true);
|
|
65
67
|
assert.equal(status.reason, 'standing-grant');
|
|
@@ -81,13 +83,15 @@ test('process authority recheck rejects grant generation or scope drift before s
|
|
|
81
83
|
root_ids: ['core'],
|
|
82
84
|
granted_at_utc: '2026-09-14T00:00:00Z'
|
|
83
85
|
};
|
|
84
|
-
await
|
|
86
|
+
await writeBoundStandingGrant('process', f.grantFile, grant);
|
|
85
87
|
const initial = await assertProcessArmed(policy, 'node', 'core');
|
|
86
88
|
await writeFile(f.grantFile, JSON.stringify({ ...grant, instance_nonce: 'b'.repeat(64) }));
|
|
87
89
|
await assert.rejects(() => recheckProcessAuthority(policy, initial, 'node', 'core'), /process authority changed during operation/);
|
|
88
90
|
await writeFile(f.grantFile, JSON.stringify({ ...grant, profile_ids: ['node', 'node'] }));
|
|
89
|
-
|
|
90
|
-
|
|
91
|
+
await assert.rejects(
|
|
92
|
+
() => recheckProcessAuthority(policy, initial, 'node', 'core'),
|
|
93
|
+
/process authority changed during operation|epoch-grant-mismatch/
|
|
94
|
+
);
|
|
91
95
|
});
|
|
92
96
|
|
|
93
97
|
test('process grant with wrong policy digest is disarmed', async () => {
|
|
@@ -8,6 +8,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
8
8
|
import { PROCESS_GRANT_SCHEMA, PROCESS_POLICY_SCHEMA, loadProcessPolicy } from '../src/process-policy.mjs';
|
|
9
9
|
import { createProcessRuntime } from '../src/process-runtime.mjs';
|
|
10
10
|
import { generateTrustKeypair } from '../src/trust.mjs';
|
|
11
|
+
import { writeBoundStandingGrant } from './r65-authority-fixture.mjs';
|
|
11
12
|
|
|
12
13
|
const repo = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
13
14
|
const host = path.join(repo, 'bin', 'deadbyte-process-host.exe');
|
|
@@ -21,7 +22,7 @@ async function fixture() {
|
|
|
21
22
|
const project = path.join(root, 'project');
|
|
22
23
|
const trust = path.join(root, 'trust');
|
|
23
24
|
await mkdir(project); await mkdir(trust);
|
|
24
|
-
const grantFile = path.join(root, 'process-grant.json');
|
|
25
|
+
const grantFile = path.join(root, 'runtime', 'process-grant.json');
|
|
25
26
|
const stateDir = path.join(root, 'process-state');
|
|
26
27
|
const policyPath = path.join(root, 'process-policy.json');
|
|
27
28
|
const hostSha = sha(await readFile(host));
|
|
@@ -66,11 +67,11 @@ async function fixture() {
|
|
|
66
67
|
};
|
|
67
68
|
await writeFile(policyPath, JSON.stringify(raw));
|
|
68
69
|
const policy = await loadProcessPolicy(policyPath);
|
|
69
|
-
await
|
|
70
|
+
await writeBoundStandingGrant('process', grantFile, {
|
|
70
71
|
schema: PROCESS_GRANT_SCHEMA, policy_sha256: policy.policy_sha256,
|
|
71
72
|
instance_nonce: 'a'.repeat(64), profile_ids: ['echo', 'hold', 'conptyhold', 'powershell', 'powershell_repl'], root_ids: ['core'],
|
|
72
73
|
granted_at_utc: '2026-09-14T00:00:00Z'
|
|
73
|
-
})
|
|
74
|
+
});
|
|
74
75
|
const keys = await generateTrustKeypair({
|
|
75
76
|
privateKeyPath: path.join(trust, 'private.pem'), publicKeyPath: path.join(trust, 'public.pem')
|
|
76
77
|
});
|
|
@@ -99,9 +100,11 @@ test('R31 runtime starts one approved profile and exposes ordered bounded output
|
|
|
99
100
|
assert.equal(started.profile_id, 'echo');
|
|
100
101
|
assert.equal(started.stream_id, started.session_id);
|
|
101
102
|
assert.equal(started.stream_generation, 1);
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
103
|
+
// The child may emit its first chunk while start() persists the started
|
|
104
|
+
// event. Assert stream/coverage coherence, not an impossible empty-stream
|
|
105
|
+
// timing promise that fails only under parallel suite load.
|
|
106
|
+
assert.equal(started.stream_next_seq, started.stream_coverage.logical_item_count + 1);
|
|
107
|
+
assert.ok(started.stream_coverage.retained_item_count <= started.stream_coverage.logical_item_count);
|
|
105
108
|
assert.equal(started.stream_coverage.complete, true);
|
|
106
109
|
const observed = await waitFor(f.runtime, started.session_id, text => text.includes('DBR31RUNTIME:42'));
|
|
107
110
|
assert.match(observed.text, /DBR31RUNTIME:42/);
|
|
@@ -13,6 +13,7 @@ async function runRevoke(t,prelude){
|
|
|
13
13
|
// filesystem that refuses deletion while Test-Path still sees the grant.
|
|
14
14
|
const script=`$ErrorActionPreference='Stop'
|
|
15
15
|
function Get-HostPolicyMetadata($Cfg) { return @{ArmFile='fixture-host-grant'} }
|
|
16
|
+
function Invoke-AuthorityEpochCommand($Cfg,[string]$Plane,[string]$Action,[string]$GrantFile) { return @{} }
|
|
16
17
|
${prelude}
|
|
17
18
|
${source.slice(begin,end)}
|
|
18
19
|
try { Disarm-Host @{}; exit 0 } catch { Write-Output $_.Exception.Message; exit 7 }
|
|
@@ -43,3 +43,21 @@ test('R58 doctor surfaces corrupt grants and authority dependency loss without s
|
|
|
43
43
|
const report=classifyDoctorControllerObservation(host,coreRoot);assert.equal(report.status,'WARN');assert.equal(host.authority.host.armed,true);
|
|
44
44
|
for(const finding of report.findings.filter(f=>f.level!=='PASS'))assert.ok(finding.what&&finding.evidence&&finding.next_action);
|
|
45
45
|
});
|
|
46
|
+
|
|
47
|
+
test('R66 doctor accepts strict epoch metadata on armed authority observations',()=>{
|
|
48
|
+
const snapshot=online();
|
|
49
|
+
snapshot.authority.process={configured:true,armed:true,reason:'standing-grant',epoch_seq:1,epoch_sha256:'a'.repeat(64)};
|
|
50
|
+
const validated=validateDoctorControllerObservation(snapshot,coreRoot);
|
|
51
|
+
assert.equal(validated.authority.process.epoch_seq,1);
|
|
52
|
+
assert.equal(validated.authority.process.epoch_sha256,'a'.repeat(64));
|
|
53
|
+
for(const process of [
|
|
54
|
+
{configured:true,armed:true,reason:'standing-grant',epoch_seq:1},
|
|
55
|
+
{configured:true,armed:true,reason:'standing-grant',epoch_sha256:'a'.repeat(64)},
|
|
56
|
+
{configured:true,armed:true,reason:'standing-grant',epoch_seq:0,epoch_sha256:'a'.repeat(64)},
|
|
57
|
+
{configured:true,armed:true,reason:'standing-grant',epoch_seq:1,epoch_sha256:'not-a-sha'},
|
|
58
|
+
{configured:true,armed:false,reason:'disarmed',epoch_seq:1,epoch_sha256:'a'.repeat(64)},
|
|
59
|
+
{configured:true,armed:true,reason:'standing-grant',epoch_seq:1,epoch_sha256:'a'.repeat(64),injected:true}
|
|
60
|
+
]){
|
|
61
|
+
assert.throws(()=>validateDoctorControllerObservation({...online(),authority:{...authority(),process}},coreRoot));
|
|
62
|
+
}
|
|
63
|
+
});
|
|
@@ -83,7 +83,7 @@ test('R59 compact baseline changes only at the explicitly authorized R64 read-of
|
|
|
83
83
|
assert.deepEqual(diff.added,[]);
|
|
84
84
|
assert.deepEqual(diff.removed,[]);
|
|
85
85
|
const packageVersion=JSON.parse(await readFile(path.join(repoRoot,'package.json'),'utf8')).version;
|
|
86
|
-
if(
|
|
86
|
+
if(['0.15.41','0.15.42','0.15.43'].includes(packageVersion)){
|
|
87
87
|
assert.deepEqual(diff.schema_changed,['machine_fs']);
|
|
88
88
|
assert.equal(diff.unchanged.length,42);
|
|
89
89
|
const prior=baseline.tools.find(tool=>tool.name==='machine_fs');
|
|
@@ -4,6 +4,7 @@ import {readFile} from 'node:fs/promises';
|
|
|
4
4
|
|
|
5
5
|
const read=rel=>readFile(new URL('../'+rel,import.meta.url),'utf8');
|
|
6
6
|
const json=async rel=>JSON.parse((await read(rel)).replace(/^\uFEFF/,''));
|
|
7
|
+
const currentPackage=await json('package.json');
|
|
7
8
|
const sourceSha='d6f75feada9ad0ae2c248b3a833ac9cf0e04150d2430ddaa70b3dfb64a0b0a86';
|
|
8
9
|
const runtime={
|
|
9
10
|
release_id:'v0.15.39-3cda3b1c65b7b7c5',
|
|
@@ -11,7 +12,7 @@ const runtime={
|
|
|
11
12
|
generation_manifest_sha256:'f47cb96083679e764725df1bde6eda53169a39f55772c173ab22f89a1dfaa7ab'
|
|
12
13
|
};
|
|
13
14
|
|
|
14
|
-
test('R64 release identity distinguishes unsealed source predecessor from active runtime predecessor',async()=>{
|
|
15
|
+
test('R64 release identity distinguishes unsealed source predecessor from active runtime predecessor',{skip:currentPackage.version!=='0.15.41'},async()=>{
|
|
15
16
|
const [pkg,lock,bridge,release,provenance,matrix,sbom,scope,gaps,version,gate]=await Promise.all([
|
|
16
17
|
json('package.json'),json('package-lock.json'),json('bridge/package.json'),json('RELEASE.json'),
|
|
17
18
|
json('PROVENANCE.json'),json('TEST-MATRIX.json'),json('SBOM.cdx.json'),json('docs/R64-SCOPE.json'),
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {createHash,generateKeyPairSync,sign as cryptoSign} from 'node:crypto';
|
|
4
|
+
import {mkdtemp,mkdir,writeFile,rm} 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 {createActivationClaimManager} from '../src/activation-lock-r65.mjs';
|
|
9
|
+
|
|
10
|
+
const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
11
|
+
const DOMAIN=Buffer.from('DEADBYTE-R27-SLOT-UPDATE-V1\0','utf8');
|
|
12
|
+
|
|
13
|
+
function signReceipt(body,privateKey,publicKey){
|
|
14
|
+
const bodyBytes=Buffer.from(canonicalJson(body),'utf8');
|
|
15
|
+
const signature=cryptoSign(null,Buffer.concat([DOMAIN,bodyBytes]),privateKey);
|
|
16
|
+
const keyId=sha(Buffer.from(publicKey.export({type:'spki',format:'der'})));
|
|
17
|
+
const envelope={body,body_sha256:sha(bodyBytes),attestation:{algorithm:'ed25519',domain:'DEADBYTE-R27-SLOT-UPDATE-V1',key_id:keyId,signature_base64:signature.toString('base64')}};
|
|
18
|
+
envelope.receipt_sha256=sha(Buffer.from(canonicalJson(envelope),'utf8'));
|
|
19
|
+
return envelope;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
test('stale activation lock is reclaimed only from exact terminal signed activation evidence',async()=>{
|
|
23
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r65-activation-lock-'));
|
|
24
|
+
try{
|
|
25
|
+
const updates=path.join(root,'evidence','updates'),trust=path.join(root,'trust');
|
|
26
|
+
await mkdir(updates,{recursive:true});await mkdir(trust,{recursive:true});
|
|
27
|
+
const {privateKey,publicKey}=generateKeyPairSync('ed25519');
|
|
28
|
+
await writeFile(path.join(trust,'ed25519-public.pem'),publicKey.export({type:'spki',format:'pem'}));
|
|
29
|
+
const subject='a'.repeat(64),manager=createActivationClaimManager({stateRoot:root});
|
|
30
|
+
const first=await manager.claim(subject);
|
|
31
|
+
const updateId='r27-test-terminal';
|
|
32
|
+
const dir=path.join(updates,updateId);await mkdir(dir,{recursive:true});
|
|
33
|
+
const activePointer={schema:'deadbyte.active-release.v1',release_id:'v0.15.42-test',version:'0.15.42',
|
|
34
|
+
manifest_sha256:'b'.repeat(64),release_root:path.join(root,'releases','v0.15.42-test'),
|
|
35
|
+
generation_root:path.join(root,'generations','v0.15.42-test-g1'),generation_manifest_sha256:'c'.repeat(64),
|
|
36
|
+
launcher_sha256:'d'.repeat(64),config_path:path.join(root,'generations','v0.15.42-test-g1','controller-config.psd1')};
|
|
37
|
+
const receipt=signReceipt({schema:'deadbyte.r27-slot-update-receipt.v1',update_id:updateId,release_version:'0.15.42',
|
|
38
|
+
candidate_manifest_sha256:'b'.repeat(64),rollback_performed:false,prior_pointer:{release_id:'v0.15.41-parent'},
|
|
39
|
+
active_pointer:activePointer},privateKey,publicKey);
|
|
40
|
+
const receiptPath=path.join(dir,'receipt.json');await writeFile(receiptPath,JSON.stringify(receipt));
|
|
41
|
+
await writeFile(path.join(root,'active-release.json'),JSON.stringify(activePointer));
|
|
42
|
+
const outcome={schema:'deadbyte.r65-activation-outcome.v1',claim_operation_id:first.operation_id,
|
|
43
|
+
preflight_subject_sha256:subject,candidate_version:'0.15.42',candidate_manifest_sha256:'b'.repeat(64),
|
|
44
|
+
predecessor_release_id:'v0.15.41-parent',outcome:{state:'ACTIVATED'},update_id:updateId,
|
|
45
|
+
state_path:path.join(dir,'state.json'),receipt_path:receiptPath,receipt_sha256:receipt.receipt_sha256};
|
|
46
|
+
await writeFile(path.join(updates,'r65-activation-outcome-terminal.json'),JSON.stringify(outcome));
|
|
47
|
+
const second=await manager.claim('e'.repeat(64));
|
|
48
|
+
assert.notEqual(second.operation_id,first.operation_id);
|
|
49
|
+
await second.release();
|
|
50
|
+
}finally{await rm(root,{recursive:true,force:true});}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('terminal-looking outcome with invalid receipt cannot reclaim activation lock',async()=>{
|
|
54
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r65-activation-lock-forged-'));
|
|
55
|
+
try{
|
|
56
|
+
const updates=path.join(root,'evidence','updates'),trust=path.join(root,'trust');
|
|
57
|
+
await mkdir(updates,{recursive:true});await mkdir(trust,{recursive:true});
|
|
58
|
+
const {publicKey}=generateKeyPairSync('ed25519');
|
|
59
|
+
await writeFile(path.join(trust,'ed25519-public.pem'),publicKey.export({type:'spki',format:'pem'}));
|
|
60
|
+
const subject='1'.repeat(64),manager=createActivationClaimManager({stateRoot:root});
|
|
61
|
+
const first=await manager.claim(subject);
|
|
62
|
+
const receiptPath=path.join(updates,'forged-receipt.json');
|
|
63
|
+
await writeFile(receiptPath,JSON.stringify({body:{schema:'deadbyte.r27-slot-update-receipt.v1'},receipt_sha256:'2'.repeat(64)}));
|
|
64
|
+
await writeFile(path.join(updates,'r65-activation-outcome-forged.json'),JSON.stringify({
|
|
65
|
+
schema:'deadbyte.r65-activation-outcome.v1',claim_operation_id:first.operation_id,preflight_subject_sha256:subject,
|
|
66
|
+
candidate_version:'0.15.42',candidate_manifest_sha256:'3'.repeat(64),predecessor_release_id:'v0.15.41-parent',
|
|
67
|
+
outcome:{state:'ACTIVATED'},update_id:'r27-forged',state_path:path.join(updates,'x','state.json'),
|
|
68
|
+
receipt_path:receiptPath,receipt_sha256:'2'.repeat(64)
|
|
69
|
+
}));
|
|
70
|
+
await assert.rejects(()=>manager.claim('4'.repeat(64)),/unresolved|in progress|receipt|terminal/i);
|
|
71
|
+
}finally{await rm(root,{recursive:true,force:true});}
|
|
72
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {mkdtemp,mkdir,rm,writeFile} from 'node:fs/promises';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import {createActivationClaimManager} from '../src/activation-lock-r65.mjs';
|
|
7
|
+
|
|
8
|
+
const subject='a'.repeat(64);
|
|
9
|
+
|
|
10
|
+
test('terminal pre-mutation rejected claim can be reclaimed by exact operation evidence',async()=>{
|
|
11
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r65-lock-terminal-'));
|
|
12
|
+
try{
|
|
13
|
+
const state=path.join(root,'state'),manager=createActivationClaimManager({stateRoot:state});
|
|
14
|
+
const first=await manager.claim(subject);
|
|
15
|
+
const updates=path.join(state,'evidence','updates');await mkdir(updates,{recursive:true});
|
|
16
|
+
await writeFile(path.join(updates,'r65-activation-outcome-terminal.json'),JSON.stringify({
|
|
17
|
+
schema:'deadbyte.r65-activation-outcome.v1',claim_operation_id:first.operation_id,
|
|
18
|
+
preflight_subject_sha256:subject,candidate_version:'0.15.42',candidate_manifest_sha256:'b'.repeat(64),
|
|
19
|
+
outcome:{state:'ACTIVATION_REJECTED'},update_id:null,state_path:null
|
|
20
|
+
}));
|
|
21
|
+
const second=await manager.claim(subject);
|
|
22
|
+
assert.notEqual(second.operation_id,first.operation_id);
|
|
23
|
+
await second.release();
|
|
24
|
+
await assert.rejects(first.release(),/changed|missing|ownership/i);
|
|
25
|
+
}finally{await rm(root,{recursive:true,force:true});}
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('unknown activation outcome is never reclaimed automatically',async()=>{
|
|
29
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r65-lock-unknown-'));
|
|
30
|
+
try{
|
|
31
|
+
const state=path.join(root,'state'),manager=createActivationClaimManager({stateRoot:state});
|
|
32
|
+
const first=await manager.claim(subject);
|
|
33
|
+
const updates=path.join(state,'evidence','updates');
|
|
34
|
+
await writeFile(path.join(updates,'r65-activation-outcome-unknown.json'),JSON.stringify({
|
|
35
|
+
schema:'deadbyte.r65-activation-outcome.v1',claim_operation_id:first.operation_id,
|
|
36
|
+
preflight_subject_sha256:subject,candidate_version:'0.15.42',candidate_manifest_sha256:'b'.repeat(64),
|
|
37
|
+
outcome:{state:'ACTIVATION_OUTCOME_UNKNOWN'},update_id:'r27-unknown',state_path:path.join(updates,'unknown','state.json')
|
|
38
|
+
}));
|
|
39
|
+
await assert.rejects(()=>manager.claim(subject),/unresolved|unknown|in progress/i);
|
|
40
|
+
}finally{await rm(root,{recursive:true,force:true});}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('corrupt or foreign-subject lock evidence fails closed',async()=>{
|
|
44
|
+
const root=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r65-lock-corrupt-'));
|
|
45
|
+
try{
|
|
46
|
+
const state=path.join(root,'state'),updates=path.join(state,'evidence','updates');await mkdir(updates,{recursive:true});
|
|
47
|
+
await writeFile(path.join(updates,'r65-activation.lock'),'{bad');
|
|
48
|
+
const manager=createActivationClaimManager({stateRoot:state});
|
|
49
|
+
await assert.rejects(()=>manager.claim(subject),/corrupt|invalid/i);
|
|
50
|
+
await rm(path.join(updates,'r65-activation.lock'));
|
|
51
|
+
const first=await manager.claim(subject);
|
|
52
|
+
await writeFile(path.join(updates,'r65-activation-outcome-foreign.json'),JSON.stringify({
|
|
53
|
+
schema:'deadbyte.r65-activation-outcome.v1',claim_operation_id:first.operation_id,
|
|
54
|
+
preflight_subject_sha256:'c'.repeat(64),candidate_version:'0.15.42',candidate_manifest_sha256:'b'.repeat(64),
|
|
55
|
+
outcome:{state:'ACTIVATION_REJECTED'},update_id:null,state_path:null
|
|
56
|
+
}));
|
|
57
|
+
await assert.rejects(()=>manager.claim(subject),/unresolved|in progress|subject/i);
|
|
58
|
+
}finally{await rm(root,{recursive:true,force:true});}
|
|
59
|
+
});
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {orchestrateActivation} from '../src/activation-preflight-r65.mjs';
|
|
4
|
+
|
|
5
|
+
const parent={release_id:'v0.15.41-63a78ad159ad3003',version:'0.15.41',manifest_sha256:'63a78ad159ad3003db3fefafcd468bef22599c829a867f4f10a46c7a20748b3e',generation_manifest_sha256:'19ae2efcdcde9f80c474f0e55851d4ada51c22813d3e05af03b11acb28c3fca5'};
|
|
6
|
+
const candidate={version:'0.15.42',manifest_sha256:'a'.repeat(64)};
|
|
7
|
+
const ids={bridge:{alive:true,reason:'verified',pid:10},core:{alive:true,reason:'verified',pid:11},cloud:{alive:true,reason:'verified',pid:12}};
|
|
8
|
+
const policies={process:'a'.repeat(64),coding:'b'.repeat(64),autonomous:'c'.repeat(64),desktop:'d'.repeat(64),host:'e'.repeat(64)};
|
|
9
|
+
|
|
10
|
+
function legacyEntry(armed){return {configured:true,armed,reason:armed?'standing-grant':'disarmed',epoch_seq:null,epoch_sha256:null};}
|
|
11
|
+
function epochEntry(armed,seq=7,sha='f'.repeat(64)){return {configured:true,armed,reason:armed?'standing-grant':'disarmed',epoch_seq:armed?seq:null,epoch_sha256:armed?sha:null};}
|
|
12
|
+
function authorityEntries(vector,epochNative=false){
|
|
13
|
+
return Object.fromEntries(Object.entries(vector).map(([plane,armed])=>[plane,epochNative?epochEntry(armed):legacyEntry(armed)]));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function fixture(initial={}){
|
|
17
|
+
const calls=[];
|
|
18
|
+
const initialVector=initial.authority??{process:true,coding:true,autonomous:true,desktop:false,host:true};
|
|
19
|
+
let current={state:'ONLINE',local_ready:true,...ids,supervisor:{running:true},authority:authorityEntries(initialVector,initial.epochNative===true),policy_sha256:policies,
|
|
20
|
+
active_release_id:parent.release_id,active_manifest_sha256:parent.manifest_sha256,active_generation_manifest_sha256:parent.generation_manifest_sha256,...initial};
|
|
21
|
+
if(initial.authority)current={...current,authority:authorityEntries(initial.authority,initial.epochNative===true)};
|
|
22
|
+
return {calls,observe:async()=>current,command:async name=>{
|
|
23
|
+
calls.push(name);
|
|
24
|
+
if(name.startsWith('disarm')){
|
|
25
|
+
const plane=name.slice(6);
|
|
26
|
+
current={...current,authority:{...current.authority,[plane]:legacyEntry(false)}};
|
|
27
|
+
if(plane==='coding')current={...current,authority:{...current.authority,autonomous:legacyEntry(false)}};
|
|
28
|
+
}else if(name==='supervisorstop')current={...current,supervisor:{running:false}};
|
|
29
|
+
else if(name==='stop')current={...current,state:'OFFLINE',local_ready:false,bridge:{alive:false},core:{alive:false},cloud:{alive:false}};
|
|
30
|
+
else throw new Error('unexpected authority widening command '+name);
|
|
31
|
+
},activate:async()=>{calls.push('activate');current={...current,state:'ONLINE',local_ready:true,...ids,supervisor:{running:true}};return {release_id:'v0.15.42-candidate',verified:true};},
|
|
32
|
+
setCurrent:value=>{current=value;},getCurrent:()=>current};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
test('legacy standing authority is revoked before activation and never auto-restored',async()=>{
|
|
36
|
+
const f=fixture();
|
|
37
|
+
const result=await orchestrateActivation({pointer:parent,expectedParent:parent,candidate,expectedCandidate:candidate,...f});
|
|
38
|
+
assert.equal(result.state,'ACTIVATED');
|
|
39
|
+
assert.equal(result.runtime_activation,'ACTIVATED');
|
|
40
|
+
assert.equal(result.authority_continuity,'REAUTH_REQUIRED');
|
|
41
|
+
assert.deepEqual(result.reauth_planes,['process','coding','autonomous','host']);
|
|
42
|
+
assert.deepEqual(f.calls,['disarmhost','disarmautonomous','disarmcoding','disarmprocess','activate']);
|
|
43
|
+
assert.equal(f.calls.some(name=>name.startsWith('arm')),false);
|
|
44
|
+
assert.deepEqual(result.final_authority,{process:false,coding:false,autonomous:false,desktop:false,host:false});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('parked dirty predecessor reaches clean offline before activation',async()=>{
|
|
48
|
+
const f=fixture({state:'PARKED',authority:{process:false,coding:false,autonomous:false,desktop:false,host:false}});
|
|
49
|
+
const result=await orchestrateActivation({pointer:parent,expectedParent:parent,candidate,expectedCandidate:candidate,...f});
|
|
50
|
+
assert.equal(result.state,'ACTIVATED');
|
|
51
|
+
assert.equal(result.authority_continuity,'NOT_APPLICABLE');
|
|
52
|
+
assert.deepEqual(f.calls,['supervisorstop','stop','activate']);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('legacy revoke failure is terminal for the original action and never dispatches activation',async()=>{
|
|
56
|
+
const f=fixture();
|
|
57
|
+
const command=f.command;
|
|
58
|
+
f.command=async name=>{if(name==='disarmcoding'){f.calls.push(name);throw new Error('forced revoke failure');}await command(name);};
|
|
59
|
+
const result=await orchestrateActivation({pointer:parent,expectedParent:parent,candidate,expectedCandidate:candidate,...f});
|
|
60
|
+
assert.equal(result.state,'ACTIVATION_OUTCOME_UNKNOWN');
|
|
61
|
+
assert.equal(f.calls.includes('activate'),false);
|
|
62
|
+
assert.equal(f.calls.some(name=>name.startsWith('arm')),false);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('unexpected successor authority after activation is not reported as safe activation',async()=>{
|
|
66
|
+
const f=fixture();
|
|
67
|
+
const activate=f.activate;
|
|
68
|
+
f.activate=async()=>{
|
|
69
|
+
const result=await activate();
|
|
70
|
+
const current=f.getCurrent();
|
|
71
|
+
f.setCurrent({...current,authority:{...current.authority,process:legacyEntry(true)}});
|
|
72
|
+
return result;
|
|
73
|
+
};
|
|
74
|
+
const result=await orchestrateActivation({pointer:parent,expectedParent:parent,candidate,expectedCandidate:candidate,...f});
|
|
75
|
+
assert.equal(result.state,'ACTIVATION_OUTCOME_UNKNOWN');
|
|
76
|
+
assert.equal(f.calls.some(name=>name.startsWith('arm')),false);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('concurrent activation attempts admit only one transition',async()=>{
|
|
80
|
+
const f=fixture({authority:{process:false,coding:false,autonomous:false,desktop:false,host:false}});
|
|
81
|
+
let held=false,releaseFirst;
|
|
82
|
+
const claim=async()=>{if(held)throw new Error('activation already in progress');held=true;return async()=>{held=false;};};
|
|
83
|
+
const activate=f.activate;
|
|
84
|
+
f.activate=async()=>{await new Promise(resolve=>{releaseFirst=resolve;});return activate();};
|
|
85
|
+
const options={pointer:parent,expectedParent:parent,candidate,expectedCandidate:candidate,...f,claim};
|
|
86
|
+
const first=orchestrateActivation(options);
|
|
87
|
+
while(!releaseFirst)await new Promise(resolve=>setTimeout(resolve,1));
|
|
88
|
+
let second;
|
|
89
|
+
try{
|
|
90
|
+
second=await Promise.race([orchestrateActivation(options),new Promise(resolve=>setTimeout(()=>resolve({state:'TIMED_OUT'}),50))]);
|
|
91
|
+
assert.equal(second.state,'ACTIVATION_REJECTED');
|
|
92
|
+
}finally{releaseFirst();}
|
|
93
|
+
assert.equal((await first).state,'ACTIVATED');
|
|
94
|
+
assert.deepEqual(f.calls,['activate']);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('successor policy drift cannot become an exact legacy authority restoration',async()=>{
|
|
98
|
+
for(const plane of ['host','process']){
|
|
99
|
+
const vector={process:false,coding:false,autonomous:false,desktop:false,host:false};
|
|
100
|
+
vector[plane]=true;
|
|
101
|
+
const f=fixture({authority:vector});
|
|
102
|
+
const activate=f.activate;
|
|
103
|
+
let drift=false;
|
|
104
|
+
f.activate=async()=>{const result=await activate();drift=true;return result;};
|
|
105
|
+
const observe=f.observe;
|
|
106
|
+
f.observe=async()=>{
|
|
107
|
+
const value=await observe();
|
|
108
|
+
return drift?{...value,policy_sha256:{...value.policy_sha256,[plane]:'9'.repeat(64)}}:value;
|
|
109
|
+
};
|
|
110
|
+
const result=await orchestrateActivation({pointer:parent,expectedParent:parent,candidate,expectedCandidate:candidate,...f});
|
|
111
|
+
assert.equal(result.state,'ACTIVATED');
|
|
112
|
+
assert.equal(result.authority_continuity,'REAUTH_REQUIRED');
|
|
113
|
+
assert.deepEqual(result.reauth_planes,[plane]);
|
|
114
|
+
assert.equal(f.calls.some(name=>name.startsWith('arm')),false);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test('epoch-native standing authority remains fail-closed until transaction continuity engine is wired',async()=>{
|
|
119
|
+
const f=fixture({epochNative:true,authority:{process:true,coding:true,autonomous:true,desktop:false,host:false}});
|
|
120
|
+
const result=await orchestrateActivation({pointer:parent,expectedParent:parent,candidate,expectedCandidate:candidate,...f});
|
|
121
|
+
assert.equal(result.state,'ACTIVATION_REJECTED');
|
|
122
|
+
assert.equal(result.preflight,'AUTHORITY_CONTINUITY_ENGINE_REQUIRED');
|
|
123
|
+
assert.equal(result.authority_continuity,'UNVERIFIED');
|
|
124
|
+
assert.deepEqual(f.calls,[]);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test('epoch-native continuity engine revokes before switch and restores exact bounded authority afterward',async()=>{
|
|
128
|
+
const f=fixture({epochNative:true,authority:{process:true,coding:true,autonomous:true,desktop:false,host:false}});
|
|
129
|
+
const capturedNonces={process:'1'.repeat(64),coding:'2'.repeat(64),autonomous:'3'.repeat(64)};
|
|
130
|
+
const restoredNonces={process:'4'.repeat(64),coding:'5'.repeat(64),autonomous:'6'.repeat(64)};
|
|
131
|
+
const continuityEngine={
|
|
132
|
+
capture:async({observation,subject_sha256})=>{
|
|
133
|
+
f.calls.push('continuity:capture');
|
|
134
|
+
assert.equal(observation.authority.process.armed,true);
|
|
135
|
+
return {update_id:'r65-epoch-native-test',subject_sha256,planes:['process','coding','autonomous'],captured_nonces:capturedNonces};
|
|
136
|
+
},
|
|
137
|
+
revoke:async capture=>{
|
|
138
|
+
f.calls.push('continuity:revoke');
|
|
139
|
+
assert.equal(capture.update_id,'r65-epoch-native-test');
|
|
140
|
+
const current=f.getCurrent();
|
|
141
|
+
f.setCurrent({...current,authority:authorityEntries({process:false,coding:false,autonomous:false,desktop:false,host:false},true)});
|
|
142
|
+
return {process:'TRANSACTION_REVOKED',coding:'TRANSACTION_REVOKED',autonomous:'TRANSACTION_REVOKED'};
|
|
143
|
+
},
|
|
144
|
+
restore:async ({capture,successor_observation})=>{
|
|
145
|
+
f.calls.push('continuity:restore');
|
|
146
|
+
assert.equal(successor_observation.authority.process.armed,false);
|
|
147
|
+
const current=f.getCurrent();
|
|
148
|
+
f.setCurrent({...current,authority:{
|
|
149
|
+
process:epochEntry(true,9,'7'.repeat(64)),coding:epochEntry(true,9,'8'.repeat(64)),autonomous:epochEntry(true,9,'9'.repeat(64)),
|
|
150
|
+
desktop:epochEntry(false),host:epochEntry(false)
|
|
151
|
+
}});
|
|
152
|
+
return {authority_outcomes:{process:'RESTORED_EXACT',coding:'RESTORED_EXACT',autonomous:'RESTORED_EXACT',desktop:'NOT_APPLICABLE',host:'NOT_APPLICABLE'},
|
|
153
|
+
restored_nonces:restoredNonces,reauth_planes:[]};
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
const result=await orchestrateActivation({pointer:parent,expectedParent:parent,candidate,expectedCandidate:candidate,...f,continuityEngine});
|
|
157
|
+
assert.equal(result.state,'ACTIVATED');
|
|
158
|
+
assert.equal(result.runtime_activation,'ACTIVATED');
|
|
159
|
+
assert.equal(result.authority_continuity,'RESTORED_EXACT');
|
|
160
|
+
assert.deepEqual(result.authority_outcomes,{process:'RESTORED_EXACT',coding:'RESTORED_EXACT',autonomous:'RESTORED_EXACT',desktop:'NOT_APPLICABLE',host:'NOT_APPLICABLE'});
|
|
161
|
+
assert.deepEqual(f.calls,['continuity:capture','continuity:revoke','activate','continuity:restore']);
|
|
162
|
+
assert.notEqual(restoredNonces.process,capturedNonces.process);
|
|
163
|
+
assert.notEqual(restoredNonces.coding,capturedNonces.coding);
|
|
164
|
+
assert.notEqual(restoredNonces.autonomous,capturedNonces.autonomous);
|
|
165
|
+
assert.deepEqual(result.final_authority,{process:true,coding:true,autonomous:true,desktop:false,host:false});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
test('terminal activation outcome is persisted before activation claim release',async()=>{
|
|
170
|
+
const f=fixture({authority:{process:false,coding:false,autonomous:false,desktop:false,host:false}});
|
|
171
|
+
const order=[];
|
|
172
|
+
const claim=async()=>{order.push('claim');return async()=>{order.push('release');};};
|
|
173
|
+
const activate=f.activate;
|
|
174
|
+
f.activate=async()=>{order.push('activate-start');const value=await activate();order.push('activate-end');return value;};
|
|
175
|
+
const recordOutcome=async outcome=>{assert.equal(outcome.state,'ACTIVATED');order.push('record');};
|
|
176
|
+
const result=await orchestrateActivation({pointer:parent,expectedParent:parent,candidate,expectedCandidate:candidate,...f,claim,recordOutcome});
|
|
177
|
+
assert.equal(result.state,'ACTIVATED');
|
|
178
|
+
assert.deepEqual(order,['claim','activate-start','activate-end','record','release']);
|
|
179
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {classifyActivationPreflight,authorityTransitionPlan} from '../src/activation-preflight-r65.mjs';
|
|
4
|
+
|
|
5
|
+
const parent={release_id:'v0.15.41-63a78ad159ad3003',version:'0.15.41',manifest_sha256:'63a78ad159ad3003db3fefafcd468bef22599c829a867f4f10a46c7a20748b3e',generation_manifest_sha256:'19ae2efcdcde9f80c474f0e55851d4ada51c22813d3e05af03b11acb28c3fca5'};
|
|
6
|
+
const candidate={version:'0.15.42',manifest_sha256:'a'.repeat(64)};
|
|
7
|
+
const authority={process:true,coding:true,autonomous:true,desktop:false,host:false};
|
|
8
|
+
const policies={process:'1'.repeat(64),coding:'2'.repeat(64),autonomous:'3'.repeat(64),desktop:'4'.repeat(64),host:'5'.repeat(64)};
|
|
9
|
+
const online={state:'ONLINE',local_ready:true,bridge:{alive:true,reason:'verified',pid:10},core:{alive:true,reason:'verified',pid:11},cloud:{alive:true,reason:'verified',pid:12},supervisor:{running:true},authority,policy_sha256:policies,
|
|
10
|
+
active_release_id:parent.release_id,active_manifest_sha256:parent.manifest_sha256,active_generation_manifest_sha256:parent.generation_manifest_sha256};
|
|
11
|
+
const input={pointer:parent,expectedParent:parent,candidate,expectedCandidate:candidate,observation:online};
|
|
12
|
+
|
|
13
|
+
test('preflight accepts exact online predecessor with full observed identity and authority vector',()=>{
|
|
14
|
+
const result=classifyActivationPreflight(input);
|
|
15
|
+
assert.equal(result.state,'ONLINE_READY');assert.equal(result.safe_to_activate,true);
|
|
16
|
+
assert.deepEqual(result.authority,authority);
|
|
17
|
+
assert.match(result.subject_sha256,/^[0-9a-f]{64}$/);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test('host armed is not safe; plan only disarms Host and restores exact captured vector',()=>{
|
|
21
|
+
const armed={...authority,host:true};
|
|
22
|
+
const result=classifyActivationPreflight({...input,observation:{...online,authority:armed}});
|
|
23
|
+
assert.equal(result.state,'ONLINE_HOST_ARMED');assert.equal(result.safe_to_activate,false);
|
|
24
|
+
const plan=authorityTransitionPlan(result.authority);
|
|
25
|
+
assert.deepEqual(plan.before,['disarmhost']);
|
|
26
|
+
assert.deepEqual(plan.after,['armhost']);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test('parked dirty, generation drift and identity loss fail closed without an activation command',()=>{
|
|
30
|
+
assert.equal(classifyActivationPreflight({...input,observation:{...online,state:'PARKED'}}).state,'PARKED_DIRTY');
|
|
31
|
+
assert.equal(classifyActivationPreflight({...input,pointer:{...parent,generation_manifest_sha256:'b'.repeat(64)}}).state,'POINTER_DRIFT');
|
|
32
|
+
assert.equal(classifyActivationPreflight({...input,observation:{...online,active_release_id:'wrong'}}).state,'POINTER_DRIFT');
|
|
33
|
+
assert.equal(classifyActivationPreflight({...input,observation:{...online,policy_sha256:{...policies,host:null}}}).state,'UNKNOWN');
|
|
34
|
+
assert.equal(classifyActivationPreflight({...input,observation:{...online,cloud:{alive:false,reason:'missing'}}}).state,'RUNTIME_DRIFT');
|
|
35
|
+
});
|