deadbyte-mcp 0.15.65 → 0.15.67
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/MANIFEST.SHA256 +68 -21
- package/PROVENANCE.json +35 -9
- package/README.txt +31 -18
- package/RELEASE.json +43 -7
- package/SBOM.cdx.json +2 -2
- package/TEST-MATRIX.json +45 -2
- package/bridge/package.json +1 -1
- package/controller/deadbyte-process-policy.json +4 -4
- package/docs/R168-ACTIVATION-RECONCILIATION-BLOCKER-20261026.md +39 -0
- package/docs/R168-CRASH-JOURNAL-PROTECTED-PREFLIGHT-20261026.md +46 -0
- package/docs/R168-FIVE-PLANE-FENCE-20261026.md +41 -0
- package/docs/R168-NATIVE-EXCLUSION-TERMINAL-CONTRACT-20261026.md +53 -0
- package/docs/R168-NPX-REMOTE-SKEW-HOTFIX-20261026.md +41 -0
- package/docs/R168-READONLY-AUDIT-AND-RESUME-20261026.md +56 -0
- package/docs/R168-SIGNED-RECONCILIATION-PRECOMMIT-20261026.md +45 -0
- package/docs/R169-NPX-ACTIVE-CONTROLLER-DELEGATION-20261026.md +57 -0
- package/node_modules/which/CHANGELOG.md +166 -0
- package/package.json +5 -2
- package/scripts/gate-windows-r168.ps1 +75 -0
- package/scripts/npm-release-r48.mjs +23 -3
- package/scripts/npm-release-readiness-r168.mjs +70 -0
- package/scripts/r168-audit-readonly.mjs +32 -0
- package/scripts/r168-npm-registry-readonly.mjs +19 -0
- package/scripts/r168-protected-preflight.mjs +65 -0
- package/scripts/r168-release-scan.mjs +28 -0
- package/scripts/r168-source-full-test-runner.mjs +46 -0
- package/scripts/r168-subject-readonly.mjs +49 -0
- package/scripts/r172-protected-preflight.mjs +42 -0
- package/src/activation-continuity-abort-r172.mjs +108 -0
- package/src/activation-lock-r65.mjs +5 -1
- package/src/activation-preflight-r65.mjs +2 -1
- package/src/activation-reconciliation-audit-r168.mjs +200 -0
- package/src/activation-reconciliation-consent-r168.mjs +235 -0
- package/src/activation-reconciliation-fence-r168.mjs +87 -0
- package/src/activation-reconciliation-journal-r168.mjs +100 -0
- package/src/activation-reconciliation-receipt-store-r168.mjs +42 -0
- package/src/activation-reconciliation-terminal-r168.mjs +111 -0
- package/src/active-controller-delegation-r169.mjs +57 -0
- package/src/deadbyte-cli.mjs +38 -0
- package/src/npm-publication-resume-r168.mjs +25 -0
- package/src/remote-one-shot-coherence-r168.mjs +43 -0
- package/src/version.mjs +1 -1
- package/test/r130-release-security-contract.test.mjs +7 -7
- package/test/r132-release-identity.test.mjs +8 -8
- package/test/r153-prepublish-identity.test.mjs +1 -1
- package/test/r163-isolated-slot-rollback-restore.test.mjs +2 -2
- package/test/r165-live-cli-skew.test.mjs +7 -7
- package/test/r167-release-contract.test.mjs +11 -10
- package/test/r168-combined-targeted-runner.mjs +14 -0
- package/test/r168-crash-journal.test.mjs +149 -0
- package/test/r168-five-plane-fence-runner.mjs +11 -0
- package/test/r168-five-plane-fence.test.mjs +128 -0
- package/test/r168-focused-suite.test.mjs +4 -0
- package/test/r168-native-five-plane-runner.mjs +11 -0
- package/test/r168-native-five-plane-scratch.test.mjs +49 -0
- package/test/r168-native-r85-protected-scratch.test.mjs +44 -0
- package/test/r168-npm-resume-identity.test.mjs +49 -0
- package/test/r168-owner-reconciliation-contract.test.mjs +250 -0
- package/test/r168-protected-entrypoint.test.mjs +53 -0
- package/test/r168-reconciliation-audit.test.mjs +42 -0
- package/test/r168-release-contract.test.mjs +46 -0
- package/test/r168-remote-one-shot-coherence.test.mjs +66 -0
- package/test/r169-delegate-active.test.mjs +36 -0
- package/test/r169-delegate-cli-integration.test.mjs +93 -0
- package/test/r169-targeted-runner.mjs +16 -0
- package/test/r172-activation-recovery.test.mjs +95 -0
- package/test/r172-release-contract.test.mjs +35 -0
- package/test/r59-tool-surface-compat.test.mjs +1 -1
- package/test/r63-one-shot-coherence.test.mjs +10 -5
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {createHash,generateKeyPairSync,sign} from 'node:crypto';
|
|
4
|
+
import {canonicalJson} from '../src/canonical-json.mjs';
|
|
5
|
+
import {persistR168OneUseAuthorization} from '../src/activation-reconciliation-receipt-store-r168.mjs';
|
|
6
|
+
import {createR168TerminalResult,verifyR168TerminalResult} from '../src/activation-reconciliation-terminal-r168.mjs';
|
|
7
|
+
import {mkdtemp,mkdir,readFile,rm,readdir} from 'node:fs/promises';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import os from 'node:os';
|
|
10
|
+
import {
|
|
11
|
+
createR168ReconciliationSubject,makeR168ConsentChallenge,
|
|
12
|
+
verifyR168OwnerApproval,createR168SignedAuthorization,verifyR168SignedAuthorization
|
|
13
|
+
} from '../src/activation-reconciliation-consent-r168.mjs';
|
|
14
|
+
|
|
15
|
+
const H=v=>createHash('sha256').update(String(v)).digest('hex');
|
|
16
|
+
const domain=Buffer.from('DEADBYTE-R168-OWNER-APPROVAL-V1\0');
|
|
17
|
+
const owner=generateKeyPairSync('ed25519');
|
|
18
|
+
const machine=generateKeyPairSync('ed25519');
|
|
19
|
+
const head={process:'a',coding:'b',autonomous:'c',desktop:'d',host:'e'};
|
|
20
|
+
const counts={process:27,coding:29,autonomous:24,desktop:28,host:26};
|
|
21
|
+
const observation=Object.freeze({
|
|
22
|
+
schema:'deadbyte.r168-readonly-reconciliation-observation.v1',
|
|
23
|
+
status:'NO_MATCHING_TRANSACTION_OBSERVED',terminalization_authorized:false,
|
|
24
|
+
old_operation_id:'c84d7ffa4eee80a2f467acda3ddd9332',
|
|
25
|
+
old_continuity_update_id:'r65-ef42bc84451ce024c4dee736d4047b36',
|
|
26
|
+
old_claim_sha256:H('lock'),old_outcome_sha256:H('outcome'),predecessor_pointer_sha256:H('ptr'),
|
|
27
|
+
coding_policy_sha256:H('policy'),coding_grant_sha256:H('grant'),coding_epoch_sha256:H(head.coding),
|
|
28
|
+
candidate_manifest_sha256:H('candidate'),signed_event_count:134,update_inventory_count:376,
|
|
29
|
+
epoch_history:Object.fromEntries(Object.entries(head).map(([p,c])=>[p,{
|
|
30
|
+
status:'PASS',verified_events:counts[p],event_sha256:H(c),head_action:p==='coding'?'grant':'user_revoke'
|
|
31
|
+
}])),
|
|
32
|
+
observation_sha256:null
|
|
33
|
+
});
|
|
34
|
+
const {observation_sha256:_,...observationBody}=observation;
|
|
35
|
+
const observationFinal=Object.freeze({...observationBody,observation_sha256:H(canonicalJson(observationBody))});
|
|
36
|
+
const claimSnapshot={fileId:'1234567890abcdef:1234567890abcdef1234567890abcdef',sha256:observationFinal.old_claim_sha256};
|
|
37
|
+
const makeSubject=()=>createR168ReconciliationSubject({observation:observationFinal,
|
|
38
|
+
claimSnapshot,stateRoot:'C:\\Users\\DEADBYTE\\.deadbyte-mcp'});
|
|
39
|
+
const now='2026-10-26T08:00:00.000Z';
|
|
40
|
+
function approvalFor(subject,{bodyChanges={},signingKey=owner.privateKey}={}){
|
|
41
|
+
const body={schema:'deadbyte.r168-owner-approval.v1',action:'terminalize_unknown_without_replay',
|
|
42
|
+
subject_sha256:subject.subject_sha256,claim_operation_id:subject.claim_operation_id,
|
|
43
|
+
nonce:H('one-time-approval'),issued_at_utc:'2026-10-26T07:59:00.000Z',
|
|
44
|
+
expires_at_utc:'2026-10-26T08:04:00.000Z',...bodyChanges};
|
|
45
|
+
const bytes=Buffer.concat([domain,Buffer.from(canonicalJson(body))]);
|
|
46
|
+
return {body,attestation:{algorithm:'ed25519',domain:'DEADBYTE-R168-OWNER-APPROVAL-V1',
|
|
47
|
+
signature_base64:sign(null,bytes,signingKey).toString('base64')}};
|
|
48
|
+
}
|
|
49
|
+
test('R168 binds subject to exact claim FileId, five signed heads and all observed hashes',()=>{
|
|
50
|
+
const subject=makeSubject();
|
|
51
|
+
assert.equal(subject.schema,'deadbyte.r168-reconciliation-subject.v1');
|
|
52
|
+
assert.equal(subject.claim_file_id,claimSnapshot.fileId);
|
|
53
|
+
assert.equal(subject.old_outcome_sha256,observation.old_outcome_sha256);
|
|
54
|
+
assert.equal(Object.keys(subject.epoch_history).length,5);
|
|
55
|
+
assert.match(subject.subject_sha256,/^[a-f0-9]{64}$/);
|
|
56
|
+
assert.match(makeR168ConsentChallenge(subject),new RegExp(subject.claim_operation_id));
|
|
57
|
+
});
|
|
58
|
+
test('R168 rejects an audit claiming terminal authority or a mismatched lock identity',()=>{
|
|
59
|
+
assert.throws(()=>createR168ReconciliationSubject({observation:{...observationFinal,terminalization_authorized:true},claimSnapshot,
|
|
60
|
+
stateRoot:'C:\\Users\\DEADBYTE\\.deadbyte-mcp'}),/terminal|observation/i);
|
|
61
|
+
assert.throws(()=>createR168ReconciliationSubject({observation:observationFinal,
|
|
62
|
+
claimSnapshot:{...claimSnapshot,sha256:H('replaced')},stateRoot:'C:\\Users\\DEADBYTE\\.deadbyte-mcp'}),/claim|lock/i);
|
|
63
|
+
assert.throws(()=>createR168ReconciliationSubject({observation:{...observationFinal,old_outcome_sha256:H('new')},claimSnapshot,
|
|
64
|
+
stateRoot:'C:\\Users\\DEADBYTE\\.deadbyte-mcp'}),/digest|observation/i);
|
|
65
|
+
});
|
|
66
|
+
test('R168 owner consent requires an independent matching Ed25519 signature and fresh single-use identity',()=>{
|
|
67
|
+
const subject=makeSubject(),approval=approvalFor(subject);
|
|
68
|
+
assert.equal(verifyR168OwnerApproval({subject,approval,ownerPublicKey:owner.publicKey,now}),true);
|
|
69
|
+
assert.throws(()=>verifyR168OwnerApproval({subject,approval,ownerPublicKey:machine.publicKey,now}),/signature|owner/i);
|
|
70
|
+
assert.throws(()=>verifyR168OwnerApproval({subject,approval,ownerPublicKey:owner.publicKey,
|
|
71
|
+
now:'2026-10-26T08:05:00.000Z'}),/expired|window/i);
|
|
72
|
+
assert.throws(()=>verifyR168OwnerApproval({subject,approval:approvalFor(subject,{bodyChanges:{subject_sha256:H('other')}}),
|
|
73
|
+
ownerPublicKey:owner.publicKey,now}),/subject|approval/i);
|
|
74
|
+
assert.throws(()=>verifyR168OwnerApproval({subject,approval:approvalFor(subject,{bodyChanges:{nonce:'nothex'}}),
|
|
75
|
+
ownerPublicKey:owner.publicKey,now}),/nonce|approval/i);
|
|
76
|
+
});
|
|
77
|
+
test('R168 machine-signed reconciliation receipt is not authorization to delete until commit is separately approved',()=>{
|
|
78
|
+
const subject=makeSubject(),approval=approvalFor(subject);
|
|
79
|
+
const receipt=createR168SignedAuthorization({subject,approval,ownerPublicKey:owner.publicKey,
|
|
80
|
+
machinePrivateKey:machine.privateKey,now,typedConfirmation:makeR168ConsentChallenge(subject)});
|
|
81
|
+
assert.equal(receipt.body.terminalization_committed,false);
|
|
82
|
+
assert.equal(receipt.body.activation_authorized,false);
|
|
83
|
+
assert.equal(verifyR168SignedAuthorization({subject,approval,receipt,ownerPublicKey:owner.publicKey,
|
|
84
|
+
machinePublicKey:machine.publicKey,now}),true);
|
|
85
|
+
assert.throws(()=>createR168SignedAuthorization({subject,approval,ownerPublicKey:owner.publicKey,
|
|
86
|
+
machinePrivateKey:machine.privateKey,now,typedConfirmation:'YES'}),/confirmation/i);
|
|
87
|
+
assert.throws(()=>createR168SignedAuthorization({subject,approval:null,ownerPublicKey:owner.publicKey,
|
|
88
|
+
machinePrivateKey:machine.privateKey,now,typedConfirmation:makeR168ConsentChallenge(subject)}),/owner|approval/i);
|
|
89
|
+
const swapped={...receipt,body:{...receipt.body,terminalization_committed:true}};
|
|
90
|
+
assert.throws(()=>verifyR168SignedAuthorization({subject,approval,receipt:swapped,ownerPublicKey:owner.publicKey,
|
|
91
|
+
machinePublicKey:machine.publicKey,now}),/receipt|signature|digest/i);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('R168 forged extra approval fields and mismatched machine/owner key roles fail closed',()=>{
|
|
95
|
+
const subject=makeSubject();
|
|
96
|
+
const extra=approvalFor(subject,{bodyChanges:{grant_plane:'host'}});
|
|
97
|
+
assert.throws(()=>verifyR168OwnerApproval({subject,approval:extra,ownerPublicKey:owner.publicKey,now}),
|
|
98
|
+
/approval|shape|fields/i);
|
|
99
|
+
const sameKeyApproval=approvalFor(subject,{signingKey:machine.privateKey});
|
|
100
|
+
assert.throws(()=>createR168SignedAuthorization({subject,approval:sameKeyApproval,
|
|
101
|
+
ownerPublicKey:machine.publicKey,machinePrivateKey:machine.privateKey,
|
|
102
|
+
now,typedConfirmation:makeR168ConsentChallenge(subject)}),/independent|same key/i);
|
|
103
|
+
});
|
|
104
|
+
test('R168 signed subject does not survive changes to signed epoch history or claim binding',()=>{
|
|
105
|
+
const subject=makeSubject();
|
|
106
|
+
const tampered={...subject,epoch_history:{...subject.epoch_history,coding:{
|
|
107
|
+
...subject.epoch_history.coding,event_sha256:H('different')}}};
|
|
108
|
+
assert.throws(()=>verifyR168OwnerApproval({subject:tampered,approval:approvalFor(subject),
|
|
109
|
+
ownerPublicKey:owner.publicKey,now}),/subject digest/i);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test('R168 one-use owner authorization receipt is exclusive, verified and never overwrites existing evidence',async()=>{
|
|
113
|
+
const dir=await mkdtemp(path.join(os.tmpdir(),'r168-receipt-only-'));
|
|
114
|
+
try{
|
|
115
|
+
const evidenceDir=path.join(dir,'evidence');
|
|
116
|
+
await mkdir(evidenceDir);
|
|
117
|
+
const subject=makeSubject(),approval=approvalFor(subject);
|
|
118
|
+
const receipt=createR168SignedAuthorization({subject,approval,ownerPublicKey:owner.publicKey,
|
|
119
|
+
machinePrivateKey:machine.privateKey,now,typedConfirmation:makeR168ConsentChallenge(subject)});
|
|
120
|
+
const args={evidenceDir,subject,approval,receipt,ownerPublicKey:owner.publicKey,
|
|
121
|
+
machinePublicKey:machine.publicKey,now};
|
|
122
|
+
const persisted=await persistR168OneUseAuthorization(args);
|
|
123
|
+
assert.match(persisted.sha256,/^[a-f0-9]{64}$/);
|
|
124
|
+
const body=JSON.parse(await readFile(persisted.path,'utf8'));
|
|
125
|
+
assert.equal(body.receipt.body.subject_sha256,subject.subject_sha256);
|
|
126
|
+
assert.equal(body.receipt.body.terminalization_committed,false);
|
|
127
|
+
await assert.rejects(persistR168OneUseAuthorization(args),/exists|one-use|clobber/i);
|
|
128
|
+
assert.deepEqual((await readdir(evidenceDir)).length,1);
|
|
129
|
+
assert.equal(H(await readFile(persisted.path)),persisted.sha256);
|
|
130
|
+
await assert.rejects(persistR168OneUseAuthorization({...args,
|
|
131
|
+
receipt:{...receipt,receipt_sha256:H('tampered')}}),/receipt|digest|signature/i);
|
|
132
|
+
}finally{await rm(dir,{recursive:true,force:true});}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('R168 rejects a signed but incomplete or extra-field reconciliation subject',()=>{
|
|
136
|
+
const incompleteBody={schema:'deadbyte.r168-reconciliation-subject.v1',
|
|
137
|
+
claim_operation_id:'c84d7ffa4eee80a2f467acda3ddd9332'};
|
|
138
|
+
const incomplete={...incompleteBody,subject_sha256:H(canonicalJson(incompleteBody))};
|
|
139
|
+
assert.throws(()=>verifyR168OwnerApproval({subject:incomplete,approval:approvalFor(incomplete),
|
|
140
|
+
ownerPublicKey:owner.publicKey,now}),/subject|shape|incomplete/i);
|
|
141
|
+
const original=makeSubject();
|
|
142
|
+
const {subject_sha256,...body}=original;
|
|
143
|
+
const modified={...body,unreviewed_authority:'host'};
|
|
144
|
+
const changed={...modified,subject_sha256:H(canonicalJson(modified))};
|
|
145
|
+
assert.throws(()=>verifyR168OwnerApproval({subject:changed,approval:approvalFor(changed),
|
|
146
|
+
ownerPublicKey:owner.publicKey,now}),/subject|shape|field/i);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test('R168 receipt verifier enforces independent owner/machine public-key roles',()=>{
|
|
150
|
+
const subject=makeSubject(),approval=approvalFor(subject);
|
|
151
|
+
const receipt=createR168SignedAuthorization({subject,approval,ownerPublicKey:owner.publicKey,
|
|
152
|
+
machinePrivateKey:machine.privateKey,now,typedConfirmation:makeR168ConsentChallenge(subject)});
|
|
153
|
+
assert.throws(()=>verifyR168SignedAuthorization({subject,approval,receipt,
|
|
154
|
+
ownerPublicKey:owner.publicKey,machinePublicKey:owner.publicKey,now}),/independent|same key/i);
|
|
155
|
+
});
|
|
156
|
+
test('R168 receipt verifier refuses future creation time within an otherwise valid approval window',()=>{
|
|
157
|
+
const subject=makeSubject(),approval=approvalFor(subject);
|
|
158
|
+
const receipt=createR168SignedAuthorization({subject,approval,ownerPublicKey:owner.publicKey,
|
|
159
|
+
machinePrivateKey:machine.privateKey,now,typedConfirmation:makeR168ConsentChallenge(subject)});
|
|
160
|
+
assert.throws(()=>verifyR168SignedAuthorization({subject,approval,receipt,ownerPublicKey:owner.publicKey,
|
|
161
|
+
machinePublicKey:machine.publicKey,now:'2026-10-26T07:59:30.000Z'}),/future|time|window/i);
|
|
162
|
+
});
|
|
163
|
+
test('R168 receipt verifier rejects machine-signed extra authority fields even when signature is valid',()=>{
|
|
164
|
+
const subject=makeSubject(),approval=approvalFor(subject);
|
|
165
|
+
const original=createR168SignedAuthorization({subject,approval,ownerPublicKey:owner.publicKey,
|
|
166
|
+
machinePrivateKey:machine.privateKey,now,typedConfirmation:makeR168ConsentChallenge(subject)});
|
|
167
|
+
const body={...original.body,enable_host:true};
|
|
168
|
+
const machineDomain=Buffer.from('DEADBYTE-R168-RECONCILIATION-ATTESTATION-V1\\0'.replace('\\0','\0'));
|
|
169
|
+
const unsigned={body,body_sha256:H(canonicalJson(body)),
|
|
170
|
+
attestation:{algorithm:'ed25519',domain:'DEADBYTE-R168-RECONCILIATION-ATTESTATION-V1',
|
|
171
|
+
signature_base64:sign(null,Buffer.concat([machineDomain,Buffer.from(canonicalJson(body))]),
|
|
172
|
+
machine.privateKey).toString('base64')}};
|
|
173
|
+
const forged={...unsigned,receipt_sha256:H(canonicalJson(unsigned))};
|
|
174
|
+
assert.throws(()=>verifyR168SignedAuthorization({subject,approval,receipt:forged,ownerPublicKey:owner.publicKey,
|
|
175
|
+
machinePublicKey:machine.publicKey,now}),/field|shape|extra/i);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test('R168 terminal result signed only after exact claim CAS removal and stable five-plane heads',()=>{
|
|
179
|
+
const subject=makeSubject(),approval=approvalFor(subject);
|
|
180
|
+
const prepared=createR168SignedAuthorization({subject,approval,ownerPublicKey:owner.publicKey,
|
|
181
|
+
machinePrivateKey:machine.privateKey,now,typedConfirmation:makeR168ConsentChallenge(subject)});
|
|
182
|
+
const after={
|
|
183
|
+
native_cas_result:'REMOVED',claim_absent:true,
|
|
184
|
+
pointer_sha256:subject.predecessor_pointer_sha256,old_outcome_sha256:subject.old_outcome_sha256,
|
|
185
|
+
coding_policy_sha256:subject.coding_policy_sha256,coding_grant_sha256:subject.coding_grant_sha256,
|
|
186
|
+
coding_epoch_sha256:subject.coding_epoch_sha256,epoch_history:subject.epoch_history
|
|
187
|
+
};
|
|
188
|
+
const terminal=createR168TerminalResult({subject,approval,prepared,
|
|
189
|
+
ownerPublicKey:owner.publicKey,machinePrivateKey:machine.privateKey,after,now});
|
|
190
|
+
assert.equal(terminal.body.terminalization_committed,true);
|
|
191
|
+
assert.equal(terminal.body.activation_authorized,false);
|
|
192
|
+
assert.equal(verifyR168TerminalResult({subject,approval,prepared,terminal,
|
|
193
|
+
ownerPublicKey:owner.publicKey,machinePublicKey:machine.publicKey,after,now}),true);
|
|
194
|
+
});
|
|
195
|
+
test('R168 rejects terminal signing when CAS is not removed or history/pointer drifted',()=>{
|
|
196
|
+
const subject=makeSubject(),approval=approvalFor(subject);
|
|
197
|
+
const prepared=createR168SignedAuthorization({subject,approval,ownerPublicKey:owner.publicKey,
|
|
198
|
+
machinePrivateKey:machine.privateKey,now,typedConfirmation:makeR168ConsentChallenge(subject)});
|
|
199
|
+
const good={native_cas_result:'REMOVED',claim_absent:true,
|
|
200
|
+
pointer_sha256:subject.predecessor_pointer_sha256,old_outcome_sha256:subject.old_outcome_sha256,
|
|
201
|
+
coding_policy_sha256:subject.coding_policy_sha256,coding_grant_sha256:subject.coding_grant_sha256,
|
|
202
|
+
coding_epoch_sha256:subject.coding_epoch_sha256,epoch_history:subject.epoch_history};
|
|
203
|
+
const build=after=>createR168TerminalResult({subject,approval,prepared,
|
|
204
|
+
ownerPublicKey:owner.publicKey,machinePrivateKey:machine.privateKey,after,now});
|
|
205
|
+
assert.throws(()=>build({...good,native_cas_result:'CHANGED'}),/CAS|claim|removed/i);
|
|
206
|
+
assert.throws(()=>build({...good,claim_absent:false}),/CAS|claim|absent/i);
|
|
207
|
+
assert.throws(()=>build({...good,pointer_sha256:H('replaced')}),/pointer|predecessor/i);
|
|
208
|
+
assert.throws(()=>build({...good,epoch_history:{...good.epoch_history,coding:{
|
|
209
|
+
...good.epoch_history.coding,event_sha256:H('revoked')}}}),/epoch|history|coding/i);
|
|
210
|
+
});
|
|
211
|
+
test('R168 signed terminal receipt is bound to independent machine key and current observed state',()=>{
|
|
212
|
+
const subject=makeSubject(),approval=approvalFor(subject);
|
|
213
|
+
const prepared=createR168SignedAuthorization({subject,approval,ownerPublicKey:owner.publicKey,
|
|
214
|
+
machinePrivateKey:machine.privateKey,now,typedConfirmation:makeR168ConsentChallenge(subject)});
|
|
215
|
+
const after={native_cas_result:'REMOVED',claim_absent:true,
|
|
216
|
+
pointer_sha256:subject.predecessor_pointer_sha256,old_outcome_sha256:subject.old_outcome_sha256,
|
|
217
|
+
coding_policy_sha256:subject.coding_policy_sha256,coding_grant_sha256:subject.coding_grant_sha256,
|
|
218
|
+
coding_epoch_sha256:subject.coding_epoch_sha256,epoch_history:subject.epoch_history};
|
|
219
|
+
const terminal=createR168TerminalResult({subject,approval,prepared,
|
|
220
|
+
ownerPublicKey:owner.publicKey,machinePrivateKey:machine.privateKey,after,now});
|
|
221
|
+
assert.throws(()=>verifyR168TerminalResult({subject,approval,prepared,terminal,
|
|
222
|
+
ownerPublicKey:owner.publicKey,machinePublicKey:owner.publicKey,after,now}),/independent|signature|key/i);
|
|
223
|
+
assert.throws(()=>verifyR168TerminalResult({subject,approval,prepared,terminal,
|
|
224
|
+
ownerPublicKey:owner.publicKey,machinePublicKey:machine.publicKey,
|
|
225
|
+
after:{...after,claim_absent:false},now}),/CAS|claim|absent/i);
|
|
226
|
+
assert.throws(()=>verifyR168TerminalResult({subject,approval,prepared,
|
|
227
|
+
terminal:{...terminal,body:{...terminal.body,activation_authorized:true}},
|
|
228
|
+
ownerPublicKey:owner.publicKey,machinePublicKey:machine.publicKey,after,now}),/receipt|result|digest|mismatch/i);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test('R168 archived terminal result stays verifiable after one-time owner approval expires',()=>{
|
|
232
|
+
const subject=makeSubject(),approval=approvalFor(subject);
|
|
233
|
+
const prepared=createR168SignedAuthorization({subject,approval,ownerPublicKey:owner.publicKey,
|
|
234
|
+
machinePrivateKey:machine.privateKey,now,typedConfirmation:makeR168ConsentChallenge(subject)});
|
|
235
|
+
const after={native_cas_result:'REMOVED',claim_absent:true,
|
|
236
|
+
pointer_sha256:subject.predecessor_pointer_sha256,old_outcome_sha256:subject.old_outcome_sha256,
|
|
237
|
+
coding_policy_sha256:subject.coding_policy_sha256,coding_grant_sha256:subject.coding_grant_sha256,
|
|
238
|
+
coding_epoch_sha256:subject.coding_epoch_sha256,epoch_history:subject.epoch_history};
|
|
239
|
+
const terminal=createR168TerminalResult({subject,approval,prepared,
|
|
240
|
+
ownerPublicKey:owner.publicKey,machinePrivateKey:machine.privateKey,after,now});
|
|
241
|
+
assert.equal(verifyR168TerminalResult({subject,approval,prepared,terminal,
|
|
242
|
+
ownerPublicKey:owner.publicKey,machinePublicKey:machine.publicKey,after,
|
|
243
|
+
now:'2026-10-27T08:00:00.000Z'}),true);
|
|
244
|
+
assert.throws(()=>createR168TerminalResult({subject,approval,prepared,
|
|
245
|
+
ownerPublicKey:owner.publicKey,machinePrivateKey:machine.privateKey,after,
|
|
246
|
+
now:'2026-10-27T08:00:00.000Z'}),/approval|expired|window/i);
|
|
247
|
+
assert.throws(()=>verifyR168TerminalResult({subject,approval,prepared,terminal,
|
|
248
|
+
ownerPublicKey:owner.publicKey,machinePublicKey:machine.publicKey,after,
|
|
249
|
+
now:'2026-10-26T07:59:59.000Z'}),/future|time|observation/i);
|
|
250
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {spawnSync} from 'node:child_process';
|
|
4
|
+
import {readFile} from 'node:fs/promises';
|
|
5
|
+
import {fileURLToPath} from 'node:url';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
|
|
8
|
+
const root=fileURLToPath(new URL('..',import.meta.url));
|
|
9
|
+
const script=path.join(root,'scripts','r172-protected-preflight.mjs');
|
|
10
|
+
const protectedRoot='C:\\Program Files';
|
|
11
|
+
const relative=path.win32.relative(protectedRoot,root);
|
|
12
|
+
const isProtected=process.platform==='win32'&&relative!==''&&relative!=='..'&&!relative.startsWith('..'+path.win32.sep)&&!path.win32.isAbsolute(relative);
|
|
13
|
+
function launch(file,...args){return spawnSync(process.execPath,[file,...args],{
|
|
14
|
+
encoding:'utf8',windowsHide:true,shell:false,timeout:15000,maxBuffer:65536
|
|
15
|
+
});}
|
|
16
|
+
|
|
17
|
+
test('R172 installed preflight respects R89 protected-vs-writable execution context',()=>{
|
|
18
|
+
const result=launch(script);
|
|
19
|
+
if(isProtected){
|
|
20
|
+
assert.equal(result.status,0,result.stderr);
|
|
21
|
+
assert.match(result.stdout,/R172_PROTECTED_PREFLIGHT_READONLY_PASS/);
|
|
22
|
+
assert.match(result.stdout,/"terminalization_authorized":false/);
|
|
23
|
+
assert.match(result.stdout,/"activation_authorized":false/);
|
|
24
|
+
}else{
|
|
25
|
+
assert.notEqual(result.status,0);
|
|
26
|
+
assert.match(result.stderr,/R172_PROTECTED_PREFLIGHT=BLOCKED/);
|
|
27
|
+
assert.match(result.stderr,/R172_.*_(UNPROTECTED|VERSION_DRIFT)/);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('R172 writable development copy remains blocked when protected fixture exists',()=>{
|
|
32
|
+
const source=path.resolve(process.env.DEADBYTE_EXPECTED_DEVELOPMENT_ROOT||root);
|
|
33
|
+
if(path.win32.relative(protectedRoot,source).toLowerCase()===path.win32.relative(protectedRoot,root).toLowerCase()&&isProtected)
|
|
34
|
+
return test.skip('no separate development source configured');
|
|
35
|
+
const result=launch(path.join(source,'scripts','r172-protected-preflight.mjs'));
|
|
36
|
+
assert.notEqual(result.status,0);
|
|
37
|
+
assert.match(result.stderr,/R172_PROTECTED_PREFLIGHT=BLOCKED/);
|
|
38
|
+
assert.match(result.stderr,/R172_.*_UNPROTECTED/);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('R172 entrypoint does not accept flags to skip manifest, R89 or root checks',()=>{
|
|
42
|
+
const result=launch(script,'--skip-trust');
|
|
43
|
+
assert.notEqual(result.status,0);
|
|
44
|
+
assert.match(result.stderr,/R172_PROTECTED_PREFLIGHT=BLOCKED/);
|
|
45
|
+
assert.match(result.stderr,/UNKNOWN_OPTION/);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('R172 protected preflight source is read-only and calls exact manifest and OS ACL verification',async()=>{
|
|
49
|
+
const src=await readFile(script,'utf8');
|
|
50
|
+
assert.match(src,/verifyManifestTreeExact\(/);
|
|
51
|
+
assert.match(src,/inspectTrustedInstallation\(/);
|
|
52
|
+
assert.doesNotMatch(src,/deletePinnedRemoteMarker\(|unlink\(|transaction_revoke\(/);
|
|
53
|
+
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {rejectR168MutationEvidence} from '../src/activation-reconciliation-audit-r168.mjs';
|
|
4
|
+
|
|
5
|
+
const continuityId='r65-ef42bc84451ce024c4dee736d4047b36';
|
|
6
|
+
const created='2026-10-26T04:31:15.000Z';
|
|
7
|
+
const event=(changes={})=>({
|
|
8
|
+
plane:'coding',seq:1,action:'grant',update_id:null,
|
|
9
|
+
recorded_at_utc:'2026-10-26T04:30:15.000Z',...changes
|
|
10
|
+
});
|
|
11
|
+
const invoke=events=>rejectR168MutationEvidence({events,continuityId,claimCreatedAt:created});
|
|
12
|
+
|
|
13
|
+
test('R168 historical signed-event inventory may be observed only when older than unknown claim',()=>{
|
|
14
|
+
assert.deepEqual(invoke([event(),event({plane:'process',action:'user_revoke',seq:2})]),
|
|
15
|
+
{checked_events:2,old_continuity_events:0});
|
|
16
|
+
});
|
|
17
|
+
test('R168 old continuity transaction cannot be treated as no mutation',()=>{
|
|
18
|
+
assert.throws(()=>invoke([event({action:'transaction_revoke',update_id:continuityId})]),
|
|
19
|
+
/old continuity mutation exists/);
|
|
20
|
+
assert.throws(()=>invoke([event({action:'transaction_restore',update_id:continuityId})]),
|
|
21
|
+
/old continuity mutation exists/);
|
|
22
|
+
});
|
|
23
|
+
test('R168 an event after claim, including unrelated grant or revoke, is not a no-mutation proof',()=>{
|
|
24
|
+
assert.throws(()=>invoke([event({recorded_at_utc:'2026-10-26T04:31:16.000Z'})]),
|
|
25
|
+
/authority event at or after old claim/);
|
|
26
|
+
assert.throws(()=>invoke([event({action:'user_revoke',recorded_at_utc:created})]),
|
|
27
|
+
/authority event at or after old claim/);
|
|
28
|
+
});
|
|
29
|
+
test('R168 malformed, missing or oversized event inventory is rejected',()=>{
|
|
30
|
+
assert.throws(()=>invoke([event({plane:'unexpected'})]),/event identity incomplete/);
|
|
31
|
+
assert.throws(()=>invoke([event({seq:0})]),/event identity incomplete/);
|
|
32
|
+
assert.throws(()=>invoke([event({recorded_at_utc:'invalid'})]),/authority event at or after old claim/);
|
|
33
|
+
assert.throws(()=>rejectR168MutationEvidence({events:[],continuityId:'wrong',claimCreatedAt:created}),/continuity identity/);
|
|
34
|
+
assert.throws(()=>invoke(new Array(50001).fill(event())),/event inventory invalid/);
|
|
35
|
+
});
|
|
36
|
+
test('R168 no-mutation diagnostic must remain non-signing and non-terminalizing',async()=>{
|
|
37
|
+
const fs=await import('node:fs/promises');
|
|
38
|
+
const url=new URL('../src/activation-reconciliation-audit-r168.mjs',import.meta.url);
|
|
39
|
+
const src=await fs.readFile(url,'utf8');
|
|
40
|
+
assert.match(src,/terminalization_authorized:false/);
|
|
41
|
+
assert.doesNotMatch(src,/\bunlink\s*\(|\brename\s*\(|\bwriteFile\s*\(/);
|
|
42
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {readFile} from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import {fileURLToPath} from 'node:url';
|
|
6
|
+
import {DEADBYTE_VERSION} from '../src/version.mjs';
|
|
7
|
+
import {assertR168NpmPublishReady,assertR168PackedIdentity} from '../scripts/npm-release-readiness-r168.mjs';
|
|
8
|
+
const root=path.resolve(fileURLToPath(new URL('..',import.meta.url)));
|
|
9
|
+
const read=async name=>JSON.parse(await readFile(path.join(root,name),'utf8'));
|
|
10
|
+
const parent='v0.15.61-055d1ff0aa99e60b';
|
|
11
|
+
const predecessorManifest='3316654646cc870584a7c969c2032af6c795229ff8c65e5a8b794f97c2458c41';
|
|
12
|
+
const predecessorTarball='fc3ae5b1505e316bc9dfedf8d063cb00641754c8f5f1306a31f8cc4bb84c9484';
|
|
13
|
+
|
|
14
|
+
test('R172 preserves the immutable R168 publication lineage separately from current identity',async()=>{
|
|
15
|
+
const release=await read('RELEASE.json');
|
|
16
|
+
assert.equal(release.runtime_predecessor.version,'0.15.61');
|
|
17
|
+
assert.equal(release.runtime_predecessor.release_id,parent);
|
|
18
|
+
assert.equal(release.parent.release_id,parent);
|
|
19
|
+
assert.equal(release.r168.scope,'REMOTE_VERSION_SKEW_FAIL_CLOSED_AND_READONLY_RECONCILIATION');
|
|
20
|
+
assert.equal(release.r168.source_predecessor_version,'0.15.65');
|
|
21
|
+
assert.equal(release.r168.source_predecessor_manifest_sha256,predecessorManifest);
|
|
22
|
+
assert.equal(release.r168.source_predecessor_tarball_sha256,predecessorTarball);
|
|
23
|
+
assert.equal(release.r168.owner_approval,'NOT_PRESENT');
|
|
24
|
+
assert.equal(release.r168.old_r65_activation_claim,'PRESERVED_UNKNOWN');
|
|
25
|
+
assert.equal(release.r168.production_activation,'NOT_RUN');
|
|
26
|
+
assert.equal(release.r168.public_tool_delta,0);
|
|
27
|
+
assert.equal(release.r168.authority_scope_delta,0);
|
|
28
|
+
assert.equal(release.r168.stable_final_claim,false);
|
|
29
|
+
});
|
|
30
|
+
test('R168 exact-byte publisher refuses absent full protected closure',async()=>{
|
|
31
|
+
const release=await read('RELEASE.json'),saved=process.env.DEADBYTE_R168_CLOSURE_ROOT;
|
|
32
|
+
try{
|
|
33
|
+
delete process.env.DEADBYTE_R168_CLOSURE_ROOT;
|
|
34
|
+
await assert.rejects(assertR168NpmPublishReady(release,{root}),/PUBLISH_BLOCKED R168/);
|
|
35
|
+
}finally{
|
|
36
|
+
if(saved===undefined)delete process.env.DEADBYTE_R168_CLOSURE_ROOT;
|
|
37
|
+
else process.env.DEADBYTE_R168_CLOSURE_ROOT=saved;
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
test('R168 publisher refuses any tarball digest mismatch',()=>{
|
|
41
|
+
const expected={tarball_sha256:'a'.repeat(64),tarball_integrity:'sha512-expected',
|
|
42
|
+
tarball_shasum:'b'.repeat(40)};
|
|
43
|
+
assert.throws(()=>assertR168PackedIdentity({...expected,
|
|
44
|
+
observedSha256:'c'.repeat(64),observedIntegrity:'sha512-other',observedShasum:'d'.repeat(40)}),
|
|
45
|
+
/PUBLISH_BLOCKED R168/);
|
|
46
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {readFile} from 'node:fs/promises';
|
|
4
|
+
import {selectR168OneShotRemotePath} from '../src/remote-one-shot-coherence-r168.mjs';
|
|
5
|
+
|
|
6
|
+
const active={version:'0.15.61',release_id:'v0.15.61-055d1ff0aa99e60b'};
|
|
7
|
+
test('R168 status commands on skew observe the active release without activation/disarm',()=>{
|
|
8
|
+
for(const command of ['status','processstatus','codingstatus','autonomousstatus','desktopstatus','hoststatus']){
|
|
9
|
+
const r=selectR168OneShotRemotePath({command,active,cliVersion:'0.15.65'});
|
|
10
|
+
assert.equal(r.mode,'observe_active');
|
|
11
|
+
assert.equal(r.may_activate,false);
|
|
12
|
+
assert.equal(r.may_mutate_authority,false);
|
|
13
|
+
assert.equal(r.active_version,'0.15.61');
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
test('R169 authority forwarding only, lifecycle remains skew-blocked',()=>{
|
|
17
|
+
const delegated=['armprocess','armcoding','armautonomous','armdesktop','armhost',
|
|
18
|
+
'disarmprocess','disarmcoding','disarmautonomous','disarmdesktop','disarmhost'];
|
|
19
|
+
const blocked=['restart','stop','start','resume','clean','toggle','fulltest','test','menu','url'];
|
|
20
|
+
for(const command of [...delegated,...blocked]){
|
|
21
|
+
const result=selectR168OneShotRemotePath({command,active,cliVersion:'0.15.65'});
|
|
22
|
+
assert.equal(result.mode,delegated.includes(command)?'delegate_active':'blocked_version_skew',command);
|
|
23
|
+
assert.equal(result.may_activate,false);
|
|
24
|
+
assert.equal(result.may_mutate_authority,delegated.includes(command));
|
|
25
|
+
assert.ok(result.message.includes('0.15.61'));
|
|
26
|
+
assert.ok(result.message.includes('0.15.65'));
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
test('R168 coherent one-shot still defers to existing signed coherence verification',()=>{
|
|
30
|
+
const r=selectR168OneShotRemotePath({command:'status',active:{...active,version:'0.15.65'},cliVersion:'0.15.65'});
|
|
31
|
+
assert.equal(r.mode,'verify_coherent');
|
|
32
|
+
assert.equal(r.may_activate,false);
|
|
33
|
+
});
|
|
34
|
+
test('R168 absent binding permits read-only observation but never authority mutation',()=>{
|
|
35
|
+
const observation=selectR168OneShotRemotePath({command:'status',active:null,cliVersion:'0.15.65'});
|
|
36
|
+
assert.equal(observation.mode,'observe_unbound');
|
|
37
|
+
assert.equal(observation.may_activate,false);
|
|
38
|
+
assert.equal(observation.may_mutate_authority,false);
|
|
39
|
+
for(const command of ['armcoding','disarmcoding','restart']){
|
|
40
|
+
assert.throws(()=>selectR168OneShotRemotePath({command,active:null,cliVersion:'0.15.65'}),/active|binding/i);
|
|
41
|
+
}
|
|
42
|
+
assert.throws(()=>selectR168OneShotRemotePath({command:'status',active:{version:'bad'},cliVersion:'0.15.65'}),/version|binding/i);
|
|
43
|
+
assert.throws(()=>selectR168OneShotRemotePath({command:'invalid',active,cliVersion:'0.15.65'}),/unsupported|command/i);
|
|
44
|
+
});
|
|
45
|
+
test('R168 CLI wires classification before synchronizeOneShotRuntime and no longer upgrades status',async()=>{
|
|
46
|
+
const src=await readFile(new URL('../src/deadbyte-cli.mjs',import.meta.url),'utf8');
|
|
47
|
+
const command=src.slice(src.indexOf(' const command=positional[0];'),src.indexOf('\nasync function runSupervisor'));
|
|
48
|
+
assert.match(src,/selectR168OneShotRemotePath/);
|
|
49
|
+
assert.match(command,/selectR168OneShotRemotePath/);
|
|
50
|
+
assert.match(command,/blocked_version_skew/);
|
|
51
|
+
assert.match(command,/observe_active/);
|
|
52
|
+
assert.ok(command.indexOf('selectR168OneShotRemotePath')<command.indexOf('synchronizeOneShotRuntime'));
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test('R168 foreground remote refuses npm-cache version skew before session marker, revoke or activation',async()=>{
|
|
56
|
+
const cli=await readFile(new URL('../src/deadbyte-cli.mjs',import.meta.url),'utf8');
|
|
57
|
+
const start=cli.indexOf('async function runRemoteSession(');
|
|
58
|
+
const end=cli.indexOf('\nasync function runRemote(',start);
|
|
59
|
+
assert.ok(start>0&&end>start);
|
|
60
|
+
const source=cli.slice(start,end);
|
|
61
|
+
assert.match(source,/selectR168OneShotRemotePath/);
|
|
62
|
+
const safety=source.indexOf('selectR168OneShotRemotePath');
|
|
63
|
+
for(const operation of ['recoverRemoteSession(ctx','acquireForegroundSessionLock({root','synchronizeForegroundRuntime(ctx']){
|
|
64
|
+
assert.ok(safety<source.indexOf(operation),'version guard must precede '+operation);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {selectR168OneShotRemotePath} from '../src/remote-one-shot-coherence-r168.mjs';
|
|
4
|
+
|
|
5
|
+
const active={version:'0.15.61',release_id:'v0.15.61-055d1ff0aa99e60b'};
|
|
6
|
+
const cliVersion='0.15.66';
|
|
7
|
+
|
|
8
|
+
test('R169 explicit arm and disarm commands delegate to already installed active runtime without activating npm cache',()=>{
|
|
9
|
+
const commands=['armprocess','disarmprocess','armcoding','disarmcoding','armautonomous',
|
|
10
|
+
'disarmautonomous','armdesktop','disarmdesktop','armhost','disarmhost'];
|
|
11
|
+
for(const command of commands){
|
|
12
|
+
const result=selectR168OneShotRemotePath({command,active,cliVersion});
|
|
13
|
+
assert.equal(result.mode,'delegate_active',command);
|
|
14
|
+
assert.equal(result.may_activate,false,command);
|
|
15
|
+
assert.equal(result.active_version,'0.15.61',command);
|
|
16
|
+
assert.equal(result.may_mutate_authority,true,command);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
test('R169 leaves status as read-only and dangerous lifecycle commands fail closed on skew',()=>{
|
|
20
|
+
assert.equal(selectR168OneShotRemotePath({command:'status',active,cliVersion}).mode,'observe_active');
|
|
21
|
+
for(const command of ['restart','stop','start','resume','clean','toggle','park','menu','fulltest','test','hosttest','codingtest']){
|
|
22
|
+
const decision=selectR168OneShotRemotePath({command,active,cliVersion});
|
|
23
|
+
assert.equal(decision.mode,'blocked_version_skew',command);
|
|
24
|
+
assert.equal(decision.may_activate,false,command);
|
|
25
|
+
assert.equal(decision.may_mutate_authority,false,command);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
test('R169 never delegates privilege changes if caller is older than active runtime',()=>{
|
|
29
|
+
const latest={version:'0.15.67',release_id:'v0.15.67-0000000000000000'};
|
|
30
|
+
const r=selectR168OneShotRemotePath({command:'armprocess',active:latest,cliVersion});
|
|
31
|
+
assert.equal(r.mode,'blocked_version_skew');
|
|
32
|
+
assert.equal(r.may_mutate_authority,false);
|
|
33
|
+
});
|
|
34
|
+
test('R169 missing active runtime cannot turn arm into an implicit installer',()=>{
|
|
35
|
+
assert.throws(()=>selectR168OneShotRemotePath({command:'armprocess',active:null,cliVersion}),/active runtime binding/);
|
|
36
|
+
});
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {mkdtemp,mkdir,readFile,writeFile,rm} from 'node:fs/promises';
|
|
4
|
+
import {spawnSync} from 'node:child_process';
|
|
5
|
+
import {createHash} from 'node:crypto';
|
|
6
|
+
import {fileURLToPath} from 'node:url';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import os from 'node:os';
|
|
9
|
+
|
|
10
|
+
const cli=fileURLToPath(new URL('../src/deadbyte-cli.mjs',import.meta.url));
|
|
11
|
+
const hash=x=>createHash('sha256').update(x).digest('hex');
|
|
12
|
+
async function scratch(){
|
|
13
|
+
const state=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r169-cli-'));
|
|
14
|
+
const bin=path.join(state,'bin'),rel=path.join(state,'releases','fixture'),
|
|
15
|
+
gen=path.join(state,'generations','fixture');
|
|
16
|
+
for(const dir of [bin,rel,gen])await mkdir(dir,{recursive:true});
|
|
17
|
+
const launcher=path.join(bin,'DEADBYTE-MCP.ps1');
|
|
18
|
+
const log=path.join(state,'invocations.txt');
|
|
19
|
+
const source=`param([Parameter(ValueFromRemainingArguments=$true)][string[]]$ControllerArgs)
|
|
20
|
+
[IO.File]::AppendAllText('${log.replaceAll('\\','\\\\')}',([string]::Join(' ', $ControllerArgs)+'|'))
|
|
21
|
+
if($ControllerArgs[0] -eq 'armprocess') { Write-Output 'R169_CONTROLLER_COMMAND=armprocess';exit 0 }
|
|
22
|
+
if($ControllerArgs[0] -eq 'processstatus') { Write-Output 'Process plane: DISARMED';exit 0 }
|
|
23
|
+
Write-Error ('R169_FIXTURE_UNEXPECTED_COMMAND='+$ControllerArgs[0]);exit 5
|
|
24
|
+
`;
|
|
25
|
+
await writeFile(launcher,source,'utf8');
|
|
26
|
+
await writeFile(log,'','utf8');
|
|
27
|
+
const manifest=Buffer.from('R169 release placeholder manifest');
|
|
28
|
+
const generation=Buffer.from('R169 generation placeholder manifest');
|
|
29
|
+
await writeFile(path.join(rel,'MANIFEST.SHA256'),manifest);
|
|
30
|
+
await writeFile(path.join(gen,'GENERATION.SHA256'),generation);
|
|
31
|
+
await writeFile(path.join(gen,'controller-config.psd1'),'# scratch only\n');
|
|
32
|
+
const manifestSha=hash(manifest);
|
|
33
|
+
const pointer={schema:'deadbyte.active-release.v1',version:'0.15.61',
|
|
34
|
+
release_id:'v0.15.61-'+manifestSha.slice(0,16),
|
|
35
|
+
manifest_sha256:manifestSha,release_root:rel,generation_root:gen,
|
|
36
|
+
generation_manifest_sha256:hash(generation),
|
|
37
|
+
launcher_sha256:hash(await readFile(launcher)),config_path:path.join(gen,'controller-config.psd1')};
|
|
38
|
+
await writeFile(path.join(state,'active-release.json'),JSON.stringify(pointer)+'\n');
|
|
39
|
+
const run=(command='armprocess')=>spawnSync(process.execPath,[cli,'remote',command],{
|
|
40
|
+
cwd:path.dirname(cli),encoding:'utf8',shell:false,windowsHide:true,timeout:20000,
|
|
41
|
+
env:{...process.env,DEADBYTE_STATE_ROOT:state},maxBuffer:128*1024});
|
|
42
|
+
return {state,launcher,log,run};
|
|
43
|
+
}
|
|
44
|
+
test('R169 npm-cache CLI delegates explicit armprocess to pinned installed runtime without auto-activation',async()=>{
|
|
45
|
+
if(process.platform!=='win32')return;
|
|
46
|
+
const {state,log,run}=await scratch();
|
|
47
|
+
try{
|
|
48
|
+
const result=run();
|
|
49
|
+
assert.equal(result.status,0,result.stderr);
|
|
50
|
+
assert.match(result.stdout,/R169_CONTROLLER_COMMAND=armprocess/);
|
|
51
|
+
assert.equal(await readFile(log,'utf8'),'armprocess|');
|
|
52
|
+
const pointer=JSON.parse(await readFile(path.join(state,'active-release.json'),'utf8'));
|
|
53
|
+
assert.equal(pointer.version,'0.15.61');
|
|
54
|
+
}finally{await rm(state,{force:true,recursive:true});}
|
|
55
|
+
});
|
|
56
|
+
test('R169 rejects changed launcher bytes before invoking privileged controller',async()=>{
|
|
57
|
+
if(process.platform!=='win32')return;
|
|
58
|
+
const {state,launcher,log,run}=await scratch();
|
|
59
|
+
try{
|
|
60
|
+
await writeFile(launcher,"throw 'modified launcher'\n");
|
|
61
|
+
const result=run();
|
|
62
|
+
assert.notEqual(result.status,0);
|
|
63
|
+
assert.match(result.stderr,/launcher|digest|hash|integrity|pin/i);
|
|
64
|
+
assert.equal(await readFile(log,'utf8'),'');
|
|
65
|
+
}finally{await rm(state,{force:true,recursive:true});}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('R169 rejects changed release manifest before any installed authority dispatch',async()=>{
|
|
69
|
+
if(process.platform!=='win32')return;
|
|
70
|
+
const {state,log,run}=await scratch();
|
|
71
|
+
try{
|
|
72
|
+
const pointer=JSON.parse(await readFile(path.join(state,'active-release.json'),'utf8'));
|
|
73
|
+
await writeFile(path.join(pointer.release_root,'MANIFEST.SHA256'),'tampered manifest');
|
|
74
|
+
const outcome=run();
|
|
75
|
+
assert.notEqual(outcome.status,0);
|
|
76
|
+
assert.match(outcome.stderr,/release manifest digest mismatch/);
|
|
77
|
+
assert.equal(await readFile(log,'utf8'),'');
|
|
78
|
+
}finally{await rm(state,{force:true,recursive:true});}
|
|
79
|
+
});
|
|
80
|
+
test('R169 rejects escaped release root and does not dispatch to an untrusted controller',async()=>{
|
|
81
|
+
if(process.platform!=='win32')return;
|
|
82
|
+
const {state,log,run}=await scratch();
|
|
83
|
+
try{
|
|
84
|
+
const pointerPath=path.join(state,'active-release.json');
|
|
85
|
+
const pointer=JSON.parse(await readFile(pointerPath,'utf8'));
|
|
86
|
+
pointer.release_root=path.join(os.tmpdir(),'escape-release');
|
|
87
|
+
await writeFile(pointerPath,JSON.stringify(pointer)+'\n');
|
|
88
|
+
const outcome=run();
|
|
89
|
+
assert.notEqual(outcome.status,0);
|
|
90
|
+
assert.match(outcome.stderr,/escapes state root/);
|
|
91
|
+
assert.equal(await readFile(log,'utf8'),'');
|
|
92
|
+
}finally{await rm(state,{force:true,recursive:true});}
|
|
93
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// R169 focused regression: existing R168 safety + new npm active-runtime delegation.
|
|
2
|
+
import {spawnSync} from 'node:child_process';
|
|
3
|
+
import {fileURLToPath} from 'node:url';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
const dir=path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const cases=['r168-five-plane-fence.test.mjs','r168-focused-suite.test.mjs',
|
|
7
|
+
'r168-crash-journal.test.mjs','r168-protected-entrypoint.test.mjs',
|
|
8
|
+
'r168-remote-one-shot-coherence.test.mjs',
|
|
9
|
+
'r169-delegate-active.test.mjs','r169-delegate-cli-integration.test.mjs'];
|
|
10
|
+
const r=spawnSync(process.execPath,['--test','--test-concurrency=1',...cases.map(n=>path.join(dir,n))],{
|
|
11
|
+
cwd:dir,encoding:'utf8',shell:false,windowsHide:true,timeout:120000,maxBuffer:4*1024*1024
|
|
12
|
+
});
|
|
13
|
+
process.stdout.write(r.stdout??'');
|
|
14
|
+
process.stderr.write(r.stderr??'');
|
|
15
|
+
if(r.error)throw r.error;
|
|
16
|
+
process.exitCode=r.status??1;
|