deadbyte-mcp 0.15.65 → 0.15.66
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 +56 -19
- package/PROVENANCE.json +19 -7
- package/README.txt +31 -18
- package/RELEASE.json +25 -7
- package/SBOM.cdx.json +2 -2
- package/TEST-MATRIX.json +32 -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/package.json +4 -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/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/deadbyte-cli.mjs +20 -0
- package/src/npm-publication-resume-r168.mjs +25 -0
- package/src/remote-one-shot-coherence-r168.mjs +36 -0
- package/src/version.mjs +1 -1
- package/test/r130-release-security-contract.test.mjs +7 -7
- package/test/r132-release-identity.test.mjs +7 -7
- 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 +10 -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 +56 -0
- package/test/r168-remote-one-shot-coherence.test.mjs +65 -0
- package/test/r59-tool-surface-compat.test.mjs +1 -1
- package/test/r63-one-shot-coherence.test.mjs +10 -5
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// R168 five-plane R85 exclusion, with a deliberately non-injectable production
|
|
2
|
+
// entrypoint. Simulation is ONLY a pure test of sequencing: no production proof.
|
|
3
|
+
import {randomBytes} from 'node:crypto';
|
|
4
|
+
import {lstat} from 'node:fs/promises';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import {canonicalJson} from './canonical-json.mjs';
|
|
7
|
+
import {acquireHeldEpochLock} from './epoch-lock-r85.mjs';
|
|
8
|
+
import {queryForegroundIdentity,validForegroundIdentity} from './foreground-process-identity.mjs';
|
|
9
|
+
|
|
10
|
+
const PLANES=Object.freeze(['process','coding','autonomous','desktop','host']);
|
|
11
|
+
const validate=(stateRoot,onHeld)=>{
|
|
12
|
+
if(typeof onHeld!=='function'||typeof stateRoot!=='string'||!path.isAbsolute(stateRoot))
|
|
13
|
+
throw new Error('R168 held epoch fence arguments invalid');
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
async function runHeldFence({stateRoot,onHeld,acquire,owner}){
|
|
17
|
+
validate(stateRoot,onHeld);
|
|
18
|
+
if(typeof acquire!=='function'||!validForegroundIdentity(owner)||owner.pid!==process.pid)
|
|
19
|
+
throw new Error('R168 native owner process identity unavailable');
|
|
20
|
+
const root=path.resolve(stateRoot),runtime=path.join(root,'runtime','authority-epochs');
|
|
21
|
+
const folders=PLANES.map(plane=>({plane,dir:path.join(runtime,plane)}));
|
|
22
|
+
// Preflight the entire directory set before acquiring any lock. Never make
|
|
23
|
+
// missing directories or silently repair a stale owner in reconciliation.
|
|
24
|
+
for(const entry of folders){
|
|
25
|
+
const stat=await lstat(entry.dir);
|
|
26
|
+
if(!stat.isDirectory()||stat.isSymbolicLink())
|
|
27
|
+
throw new Error('R168 epoch plane directory untrusted or unavailable: '+entry.plane);
|
|
28
|
+
}
|
|
29
|
+
const leases=[];
|
|
30
|
+
let started=false,primary=null,result;
|
|
31
|
+
try{
|
|
32
|
+
for(const entry of folders){
|
|
33
|
+
const bytes=Buffer.from(canonicalJson({
|
|
34
|
+
schema:'deadbyte.authority-epoch-lock.r65.v1',
|
|
35
|
+
pid:process.pid,token:randomBytes(16).toString('hex'),
|
|
36
|
+
created_at_utc:new Date().toISOString(),owner_identity:owner
|
|
37
|
+
}),'utf8');
|
|
38
|
+
const lease=await acquire(path.join(entry.dir,'.lock'),bytes);
|
|
39
|
+
if(!lease||typeof lease.release!=='function')
|
|
40
|
+
throw new Error('R168 epoch native lock busy: '+entry.plane);
|
|
41
|
+
leases.push({plane:entry.plane,lease});
|
|
42
|
+
}
|
|
43
|
+
started=true;
|
|
44
|
+
result=await onHeld(Object.freeze({
|
|
45
|
+
planeOrder:PLANES,
|
|
46
|
+
assertHeld:()=>{
|
|
47
|
+
if(!started||leases.length!==PLANES.length)
|
|
48
|
+
throw new Error('R168 five-plane native exclusion not held');
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
}));
|
|
52
|
+
}catch(error){primary=error;}
|
|
53
|
+
const releaseErrors=[];
|
|
54
|
+
for(const {plane,lease} of leases.reverse()){
|
|
55
|
+
try{await lease.release();}
|
|
56
|
+
catch(error){releaseErrors.push(new Error('R168 failed to release '+plane+' native epoch lock',{cause:error}));}
|
|
57
|
+
}
|
|
58
|
+
if(releaseErrors.length&&primary)
|
|
59
|
+
throw new AggregateError([primary,...releaseErrors],'R168 native epoch fence and cleanup ambiguous');
|
|
60
|
+
if(releaseErrors.length)throw new AggregateError(releaseErrors,'R168 native epoch fence release ambiguous');
|
|
61
|
+
if(primary)throw primary;
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Only this entrypoint can be used by any future trusted reconciliation commit.
|
|
66
|
+
// The R85 loader itself enforces R89 and native binary/source identity.
|
|
67
|
+
export async function withR168FivePlaneEpochFence(options={}){
|
|
68
|
+
if(!options||typeof options!=='object'||Array.isArray(options)||
|
|
69
|
+
Object.keys(options).some(key=>!['stateRoot','onHeld'].includes(key)))
|
|
70
|
+
throw new Error('R168 protected fence forbids injected or unknown options');
|
|
71
|
+
validate(options.stateRoot,options.onHeld);
|
|
72
|
+
if(process.platform!=='win32')throw new Error('R168 production epoch exclusion requires Windows');
|
|
73
|
+
const owner=queryForegroundIdentity(process.pid);
|
|
74
|
+
return runHeldFence({...options,owner,acquire:acquireHeldEpochLock});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Non-authoritative simulation only. Deliberately NOT called by production
|
|
78
|
+
// entrypoint; test-worker environment is not a security boundary.
|
|
79
|
+
export async function simulateR168FivePlaneEpochFence({
|
|
80
|
+
stateRoot,onHeld,testAcquire,testOwnerIdentity
|
|
81
|
+
}={}){
|
|
82
|
+
if(process.env.NODE_TEST_CONTEXT!=='child-v8')
|
|
83
|
+
throw new Error('R168 simulation requires a Node test worker');
|
|
84
|
+
if(typeof testAcquire!=='function')
|
|
85
|
+
throw new Error('R168 simulation requires a fake acquire');
|
|
86
|
+
return runHeldFence({stateRoot,onHeld,acquire:testAcquire,owner:testOwnerIdentity});
|
|
87
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// R168 fail-closed, non-mutating reconciliation intent journal.
|
|
2
|
+
// This is a standalone receipt contract. Production CAS MUST NOT use it
|
|
3
|
+
// without a trusted R89 entrypoint, held five-plane R85 locks, actual owner
|
|
4
|
+
// approval and native FileIdInfo observations. No delete/revoke here.
|
|
5
|
+
import {createHash} from 'node:crypto';
|
|
6
|
+
import {lstat,open,readFile,realpath} from 'node:fs/promises';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import {canonicalJson} from './canonical-json.mjs';
|
|
9
|
+
import {verifyR168SignedAuthorization} from './activation-reconciliation-consent-r168.mjs';
|
|
10
|
+
import {verifyR168TerminalResult} from './activation-reconciliation-terminal-r168.mjs';
|
|
11
|
+
|
|
12
|
+
const SHA=/^[a-f0-9]{64}$/;
|
|
13
|
+
const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
14
|
+
const stable=body=>Buffer.from(canonicalJson(body)+'\n','utf8');
|
|
15
|
+
function check(condition,reason){if(!condition)throw new Error('R168 journal '+reason);}
|
|
16
|
+
async function normalDirectory(value){
|
|
17
|
+
check(typeof value==='string'&&path.isAbsolute(value),'evidence directory must be absolute');
|
|
18
|
+
const dir=path.resolve(value),stat=await lstat(dir);
|
|
19
|
+
check(stat.isDirectory()&&!stat.isSymbolicLink(),'evidence directory not ordinary');
|
|
20
|
+
check(path.resolve(await realpath(dir)).toLowerCase()===dir.toLowerCase(),
|
|
21
|
+
'evidence directory reparse denied');
|
|
22
|
+
return dir;
|
|
23
|
+
}
|
|
24
|
+
function content(subject,approval,prepared){
|
|
25
|
+
const body={schema:'deadbyte.r168-terminal-intent.v1',
|
|
26
|
+
phase:'INTENT_PREPARED_ONLY',
|
|
27
|
+
subject_sha256:subject.subject_sha256,
|
|
28
|
+
claim_operation_id:subject.claim_operation_id,
|
|
29
|
+
claim_file_id:subject.claim_file_id,
|
|
30
|
+
claim_file_sha256:subject.claim_file_sha256,
|
|
31
|
+
predecessor_pointer_sha256:subject.predecessor_pointer_sha256,
|
|
32
|
+
owner_approval_nonce:approval.body.nonce,
|
|
33
|
+
owner_approval_sha256:sha(Buffer.from(canonicalJson(approval))),
|
|
34
|
+
prepared_receipt_sha256:prepared.receipt_sha256,
|
|
35
|
+
terminalization_committed:false,activation_authorized:false};
|
|
36
|
+
return {body,body_sha256:sha(Buffer.from(canonicalJson(body)))};
|
|
37
|
+
}
|
|
38
|
+
export async function prepareR168TerminalIntent({
|
|
39
|
+
evidenceDir,subject,approval,prepared,ownerPublicKey,machinePublicKey,now
|
|
40
|
+
}={}){
|
|
41
|
+
verifyR168SignedAuthorization({subject,approval,receipt:prepared,
|
|
42
|
+
ownerPublicKey,machinePublicKey,now});
|
|
43
|
+
const dir=await normalDirectory(evidenceDir);
|
|
44
|
+
check(SHA.test(approval.body.nonce),'owner approval nonce invalid');
|
|
45
|
+
const file=path.join(dir,'r168-terminal-intent-'+approval.body.nonce+'.json');
|
|
46
|
+
const encoded=stable(content(subject,approval,prepared));
|
|
47
|
+
check(encoded.length<32768,'intent size exceeded');
|
|
48
|
+
let handle;
|
|
49
|
+
try{handle=await open(file,'wx',0o600);}
|
|
50
|
+
catch(error){if(error?.code==='EEXIST')throw new Error('R168 journal one-use intent already exists; no-clobber');throw error;}
|
|
51
|
+
try{
|
|
52
|
+
// A write or fsync error leaves any incomplete intent in place to block
|
|
53
|
+
// replay. We never rename/rewrite it to appear as a successful receipt.
|
|
54
|
+
await handle.writeFile(encoded);
|
|
55
|
+
await handle.sync();
|
|
56
|
+
}finally{await handle.close();}
|
|
57
|
+
const checkBytes=await readFile(file);
|
|
58
|
+
check(checkBytes.equals(encoded),'intent changed during readback');
|
|
59
|
+
return Object.freeze({file,sha256:sha(encoded),bytes:encoded.length,
|
|
60
|
+
state:'INTENT_PREPARED_ONLY',activation_authorized:false});
|
|
61
|
+
}
|
|
62
|
+
export async function classifyR168Recovery({
|
|
63
|
+
recordFile,subject,approval,prepared,ownerPublicKey,machinePublicKey,
|
|
64
|
+
observedClaim,terminal=null,after=null,now
|
|
65
|
+
}={}){
|
|
66
|
+
// Historical validity of a recorded intent remains verifiable after the
|
|
67
|
+
// one-time owner's approval window expires. This DOES NOT grant fresh consent.
|
|
68
|
+
verifyR168SignedAuthorization({subject,approval,receipt:prepared,ownerPublicKey,machinePublicKey,
|
|
69
|
+
now:prepared?.body?.created_at_utc});
|
|
70
|
+
check(typeof recordFile==='string'&&path.isAbsolute(recordFile),'record path invalid');
|
|
71
|
+
const stat=await lstat(recordFile);
|
|
72
|
+
check(stat.isFile()&&!stat.isSymbolicLink()&&stat.size>0&&stat.size<=32768,
|
|
73
|
+
'journal file is not regular or bounded');
|
|
74
|
+
const original=await readFile(recordFile);
|
|
75
|
+
check(original.length===stat.size,'journal file changed during audit');
|
|
76
|
+
let record;
|
|
77
|
+
try{record=JSON.parse(original.toString('utf8'));}catch{throw new Error('R168 journal damaged JSON');}
|
|
78
|
+
const expected=content(subject,approval,prepared);
|
|
79
|
+
check(record?.body_sha256===expected.body_sha256&&
|
|
80
|
+
canonicalJson(record.body)===canonicalJson(expected.body)&&
|
|
81
|
+
sha(Buffer.from(canonicalJson(record.body)))===record.body_sha256,
|
|
82
|
+
'journal digest/identity tampered');
|
|
83
|
+
check(observedClaim&&typeof observedClaim==='object','claim observation absent');
|
|
84
|
+
if(observedClaim.state==='PRESENT'){
|
|
85
|
+
if(terminal)throw new Error('R168 journal terminal/claim state ambiguous');
|
|
86
|
+
const same=observedClaim.fileId===subject.claim_file_id&&
|
|
87
|
+
observedClaim.sha256===subject.claim_file_sha256;
|
|
88
|
+
return Object.freeze({state:same?'PRECOMMIT_CLAIM_PRESENT_BLOCKED':'CLAIM_CHANGED_BLOCKED',
|
|
89
|
+
activation_authorized:false,terminalization_committed:false});
|
|
90
|
+
}
|
|
91
|
+
check(observedClaim.state==='ABSENT','claim observation unverified');
|
|
92
|
+
if(!terminal)return Object.freeze({state:'CAS_OUTCOME_UNKNOWN_BLOCKED',
|
|
93
|
+
activation_authorized:false,terminalization_committed:false});
|
|
94
|
+
check(after,'terminal post-CAS evidence missing');
|
|
95
|
+
verifyR168TerminalResult({subject,approval,prepared,terminal,
|
|
96
|
+
ownerPublicKey,machinePublicKey,after,now});
|
|
97
|
+
return Object.freeze({state:'TERMINAL_VERIFIED_ACTIVATION_SEPARATE',
|
|
98
|
+
activation_authorized:false,terminalization_committed:true,
|
|
99
|
+
terminal_receipt_sha256:terminal.receipt_sha256});
|
|
100
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// R168 exclusive storage for *authorization-only* receipts.
|
|
2
|
+
// Does not sign an owner approval, terminalize claims or invoke native deletion.
|
|
3
|
+
import {createHash} from 'node:crypto';
|
|
4
|
+
import {lstat,open,readFile,realpath} from 'node:fs/promises';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import {verifyR168SignedAuthorization} from './activation-reconciliation-consent-r168.mjs';
|
|
7
|
+
|
|
8
|
+
const SHA=/^[a-f0-9]{64}$/;
|
|
9
|
+
const digest=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
10
|
+
|
|
11
|
+
export async function persistR168OneUseAuthorization({
|
|
12
|
+
evidenceDir,subject,approval,receipt,ownerPublicKey,machinePublicKey,now
|
|
13
|
+
}={}){
|
|
14
|
+
verifyR168SignedAuthorization({subject,approval,receipt,ownerPublicKey,machinePublicKey,now});
|
|
15
|
+
if(typeof evidenceDir!=='string'||!path.isAbsolute(evidenceDir))
|
|
16
|
+
throw new Error('R168 receipt evidence directory must be absolute');
|
|
17
|
+
const dir=path.resolve(evidenceDir);
|
|
18
|
+
const stats=await lstat(dir);
|
|
19
|
+
if(!stats.isDirectory()||stats.isSymbolicLink()||path.resolve(await realpath(dir))!==dir)
|
|
20
|
+
throw new Error('R168 receipt directory not a real bounded directory');
|
|
21
|
+
const nonce=approval.body.nonce;
|
|
22
|
+
if(!SHA.test(String(nonce)))throw new Error('R168 receipt one-use nonce invalid');
|
|
23
|
+
const file=path.join(dir,'r168-reconciliation-authorization-'+nonce+'.json');
|
|
24
|
+
const data=Buffer.from(JSON.stringify({
|
|
25
|
+
schema:'deadbyte.r168-reconciliation-authorization-record.v1',
|
|
26
|
+
status:'PREPARED_ONLY',
|
|
27
|
+
owner_approval:approval,
|
|
28
|
+
receipt
|
|
29
|
+
})+'\n','utf8');
|
|
30
|
+
if(data.length>32768)throw new Error('R168 authorization receipt oversized');
|
|
31
|
+
let handle;
|
|
32
|
+
try{handle=await open(file,'wx',0o600);}
|
|
33
|
+
catch(error){if(error?.code==='EEXIST')throw new Error('R168 one-use receipt already exists; no-clobber');throw error;}
|
|
34
|
+
try{
|
|
35
|
+
await handle.writeFile(data);
|
|
36
|
+
await handle.sync();
|
|
37
|
+
}finally{await handle.close();}
|
|
38
|
+
const observed=await readFile(file);
|
|
39
|
+
if(!observed.equals(data))throw new Error('R168 one-use authorization receipt changed during verification');
|
|
40
|
+
return Object.freeze({path:file,sha256:digest(data),bytes:data.length,
|
|
41
|
+
terminalization_committed:false,activation_authorized:false});
|
|
42
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// R168 cryptographic terminal-result contract. Pure: no CAS, filesystem,
|
|
2
|
+
// owner-key discovery or authority mutation occurs here. A protected caller
|
|
3
|
+
// must obtain every 'after' field from native handle-bound observations under
|
|
4
|
+
// its five-plane epoch fence before invoking this contract.
|
|
5
|
+
import {createHash,createPublicKey,sign,verify} from 'node:crypto';
|
|
6
|
+
import {canonicalJson} from './canonical-json.mjs';
|
|
7
|
+
import {verifyR168SignedAuthorization} from './activation-reconciliation-consent-r168.mjs';
|
|
8
|
+
|
|
9
|
+
const DOMAIN='DEADBYTE-R168-TERMINAL-RESULT-V1';
|
|
10
|
+
const SHA=/^[0-9a-f]{64}$/;
|
|
11
|
+
const PLANES=['process','coding','autonomous','desktop','host'];
|
|
12
|
+
const hash=body=>createHash('sha256').update(Buffer.from(canonicalJson(body),'utf8')).digest('hex');
|
|
13
|
+
const message=body=>Buffer.concat([Buffer.from(DOMAIN+'\0','utf8'),
|
|
14
|
+
Buffer.from(canonicalJson(body),'utf8')]);
|
|
15
|
+
function requireR168(ok,reason){if(!ok)throw new Error('R168 terminal '+reason);}
|
|
16
|
+
|
|
17
|
+
function verifiedAfter(subject,after){
|
|
18
|
+
requireR168(after&&typeof after==='object'&&!Array.isArray(after)&&
|
|
19
|
+
Object.keys(after).length===8&&
|
|
20
|
+
['native_cas_result','claim_absent','pointer_sha256','old_outcome_sha256',
|
|
21
|
+
'coding_policy_sha256','coding_grant_sha256','coding_epoch_sha256','epoch_history']
|
|
22
|
+
.every(k=>Object.hasOwn(after,k)),'post-CAS evidence field shape invalid');
|
|
23
|
+
requireR168(after.native_cas_result==='REMOVED'&&after.claim_absent===true,
|
|
24
|
+
'claim native CAS removal and absence not verified');
|
|
25
|
+
requireR168(after.pointer_sha256===subject.predecessor_pointer_sha256,
|
|
26
|
+
'predecessor pointer changed');
|
|
27
|
+
requireR168(after.old_outcome_sha256===subject.old_outcome_sha256,
|
|
28
|
+
'old outcome evidence changed');
|
|
29
|
+
requireR168(after.coding_policy_sha256===subject.coding_policy_sha256&&
|
|
30
|
+
after.coding_grant_sha256===subject.coding_grant_sha256&&
|
|
31
|
+
after.coding_epoch_sha256===subject.coding_epoch_sha256,
|
|
32
|
+
'Coding grant/epoch changed after CAS');
|
|
33
|
+
requireR168(after.epoch_history&&typeof after.epoch_history==='object'&&
|
|
34
|
+
Object.keys(after.epoch_history).length===PLANES.length&&
|
|
35
|
+
canonicalJson(after.epoch_history)===canonicalJson(subject.epoch_history),
|
|
36
|
+
'signed five-plane epoch history changed after CAS');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function expectedBody({subject,prepared,after,now}){
|
|
40
|
+
verifiedAfter(subject,after);
|
|
41
|
+
requireR168(SHA.test(String(prepared?.receipt_sha256)), 'prepared receipt digest invalid');
|
|
42
|
+
return Object.freeze({
|
|
43
|
+
schema:'deadbyte.r168-terminal-result.v1',
|
|
44
|
+
action:'terminalize_unknown_without_replay',
|
|
45
|
+
subject_sha256:subject.subject_sha256,
|
|
46
|
+
prepared_receipt_sha256:prepared.receipt_sha256,
|
|
47
|
+
claim_file_sha256:subject.claim_file_sha256,claim_file_id:subject.claim_file_id,
|
|
48
|
+
old_outcome_sha256:after.old_outcome_sha256,
|
|
49
|
+
predecessor_pointer_sha256:after.pointer_sha256,
|
|
50
|
+
coding_policy_sha256:after.coding_policy_sha256,
|
|
51
|
+
coding_grant_sha256:after.coding_grant_sha256,
|
|
52
|
+
coding_epoch_sha256:after.coding_epoch_sha256,
|
|
53
|
+
epoch_history:structuredClone(after.epoch_history),
|
|
54
|
+
native_cas_result:'REMOVED',claim_absent:true,
|
|
55
|
+
observed_at_utc:now,
|
|
56
|
+
terminalization_committed:true,activation_authorized:false
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function createR168TerminalResult({
|
|
61
|
+
subject,approval,prepared,ownerPublicKey,machinePrivateKey,after,now
|
|
62
|
+
}={}){
|
|
63
|
+
requireR168(machinePrivateKey?.asymmetricKeyType==='ed25519'&&machinePrivateKey?.type==='private',
|
|
64
|
+
'trusted machine signing key invalid');
|
|
65
|
+
const machinePublicKey=createPublicKey(machinePrivateKey);
|
|
66
|
+
verifyR168SignedAuthorization({subject,approval,receipt:prepared,
|
|
67
|
+
ownerPublicKey,machinePublicKey,now});
|
|
68
|
+
const body=expectedBody({subject,prepared,after,now});
|
|
69
|
+
const signature=sign(null,message(body),machinePrivateKey);
|
|
70
|
+
const unsigned={body,body_sha256:hash(body),
|
|
71
|
+
attestation:{algorithm:'ed25519',domain:DOMAIN,signature_base64:signature.toString('base64')}};
|
|
72
|
+
return Object.freeze({...unsigned,receipt_sha256:hash(unsigned)});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function verifyR168TerminalResult({
|
|
76
|
+
subject,approval,prepared,terminal,ownerPublicKey,machinePublicKey,after,now
|
|
77
|
+
}={}){
|
|
78
|
+
// Archived evidence remains verifiable after one-time approval expiry, but
|
|
79
|
+
// only if the signed preparation AND terminal observation occurred inside
|
|
80
|
+
// that original approval interval. Never interpret replay as live consent.
|
|
81
|
+
verifyR168SignedAuthorization({subject,approval,receipt:prepared,
|
|
82
|
+
ownerPublicKey,machinePublicKey,now:prepared?.body?.created_at_utc});
|
|
83
|
+
const terminalTime=terminal?.body?.observed_at_utc;
|
|
84
|
+
const observed=Date.parse(terminalTime),preparedAt=Date.parse(prepared?.body?.created_at_utc);
|
|
85
|
+
const expires=Date.parse(approval?.body?.expires_at_utc);
|
|
86
|
+
const checkTime=Date.parse(now??new Date().toISOString());
|
|
87
|
+
requireR168(typeof terminalTime==='string'&&
|
|
88
|
+
terminalTime.length===24&&terminalTime.endsWith('Z')&&
|
|
89
|
+
Number.isFinite(observed)&&new Date(observed).toISOString()===terminalTime&&
|
|
90
|
+
Number.isFinite(checkTime)&&observed>=preparedAt&&observed<expires&&observed<=checkTime,
|
|
91
|
+
'terminal observation time outside approved window or in the future');
|
|
92
|
+
const expected=expectedBody({subject,prepared,after,now:terminalTime});
|
|
93
|
+
requireR168(terminal?.body&&canonicalJson(terminal.body)===canonicalJson(expected),
|
|
94
|
+
'terminal result body differs from verified post-CAS evidence');
|
|
95
|
+
requireR168(terminal.body_sha256===hash(terminal.body),
|
|
96
|
+
'terminal result body digest invalid');
|
|
97
|
+
const {receipt_sha256,...unsigned}=terminal;
|
|
98
|
+
requireR168(receipt_sha256===hash(unsigned),'terminal result receipt digest mismatch');
|
|
99
|
+
requireR168(terminal.attestation?.algorithm==='ed25519'&&terminal.attestation?.domain===DOMAIN,
|
|
100
|
+
'terminal result attestation identity invalid');
|
|
101
|
+
const encoded=terminal.attestation.signature_base64;
|
|
102
|
+
const signature=Buffer.from(String(encoded??''),'base64');
|
|
103
|
+
requireR168(signature.length===64&&signature.toString('base64')===encoded,
|
|
104
|
+
'terminal result signature encoding invalid');
|
|
105
|
+
const key=machinePublicKey?.type==='public'&&machinePublicKey?.asymmetricKeyType==='ed25519'?
|
|
106
|
+
machinePublicKey:createPublicKey(machinePublicKey);
|
|
107
|
+
requireR168(key.asymmetricKeyType==='ed25519'&&
|
|
108
|
+
verify(null,message(terminal.body),key,signature),
|
|
109
|
+
'terminal result machine signature invalid');
|
|
110
|
+
return true;
|
|
111
|
+
}
|
package/src/deadbyte-cli.mjs
CHANGED
|
@@ -19,6 +19,7 @@ import { printDoctor } from './doctor-r45.mjs';
|
|
|
19
19
|
import { classifyRemoteAuditOutcome, createLiveTimelineFilter, createLiveTimelineTracker, createLiveTimelineWriter, defaultForegroundTimelineOptions, formatRemoteLogResetNotice, formatCompactAuditEvent, formatRemoteLifecycle, formatRemoteToolCall, formatRuntimeDiagnosticLine, selectLiveLogNames } from './remote-live-log.mjs';
|
|
20
20
|
import { prepareRemoteConsole, remoteDisplayText } from './remote-console.mjs';
|
|
21
21
|
import { ensureRuntimeCoherence } from './runtime-coherence.mjs';
|
|
22
|
+
import {selectR168OneShotRemotePath} from './remote-one-shot-coherence-r168.mjs';
|
|
22
23
|
import { windowsPowerShellEnvironment } from './runtime-update.mjs';
|
|
23
24
|
import { resolveStateRoot } from './state-root.mjs';
|
|
24
25
|
import {foregroundIdentityMatches,queryForegroundIdentity,validForegroundIdentity,proveRestrictedPidReuse} from './foreground-process-identity.mjs';
|
|
@@ -787,6 +788,16 @@ async function runRemoteSession({resumeCursor=null,verbose=false,trace=false,fil
|
|
|
787
788
|
const display=prepareRemoteConsole();
|
|
788
789
|
const displayMode=display.mode;
|
|
789
790
|
const ctx=await remoteContext();
|
|
791
|
+
// Never attempt automatic activation or cleanup from an npm-cache build on
|
|
792
|
+
// an EPOCH_NATIVE production runtime. This guard precedes session recovery,
|
|
793
|
+
// marker claims, revocations and any controller startup.
|
|
794
|
+
const active=await readActiveRuntimeBinding(ctx.root);
|
|
795
|
+
if(active){
|
|
796
|
+
const decision=selectR168OneShotRemotePath({command:'start',active,cliVersion:DEADBYTE_VERSION});
|
|
797
|
+
if(decision.mode==='blocked_version_skew') throw new Error(decision.message);
|
|
798
|
+
}
|
|
799
|
+
// First installation without an active pointer retains the existing
|
|
800
|
+
// unbound bootstrap path. A bound older release must never auto-upgrade.
|
|
790
801
|
if(displayMode==='ascii') writeRemote(process.stdout,`[DISPLAY] ASCII fallback: ${display.reason}\n`,displayMode);
|
|
791
802
|
if(resumeCursor!==null) await replayRemoteResults(ctx.root,resumeCursor,{displayMode});
|
|
792
803
|
const startupLock=await acquireForegroundSessionLock({root:ctx.root,powershell:ctx.powershell});
|
|
@@ -946,6 +957,15 @@ async function runRemote(args){
|
|
|
946
957
|
if(positional.length>1) throw new Error('remote accepts one controller command');
|
|
947
958
|
const command=positional[0];if(!REMOTE_COMMANDS.has(command)) throw new Error(`unsupported remote command '${command}'`);
|
|
948
959
|
const ctx=await remoteContext();
|
|
960
|
+
const active=await readActiveRuntimeBinding(ctx.root);
|
|
961
|
+
const decision=selectR168OneShotRemotePath({command,active,cliVersion:DEADBYTE_VERSION});
|
|
962
|
+
if(decision.mode==='blocked_version_skew') throw new Error(decision.message);
|
|
963
|
+
if(decision.mode==='observe_active'||decision.mode==='observe_unbound'){
|
|
964
|
+
if(decision.mode==='observe_active')await reportVersionCoherence(ctx.root,{displayMode:'ascii'});
|
|
965
|
+
const observed=invokeController(ctx,command,{allowFailure:true,timeoutMs:REMOTE_STATUS_TIMEOUT_MS});
|
|
966
|
+
process.exitCode=observed.status;
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
949
969
|
await synchronizeOneShotRuntime(ctx,{displayMode:'ascii'});
|
|
950
970
|
const result=invokeController(ctx,command,{allowFailure:true});
|
|
951
971
|
process.exitCode=result.status;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// R168: derive resume metadata only from a hash-pinned immutable npm artifact.
|
|
2
|
+
import {createHash} from 'node:crypto';
|
|
3
|
+
|
|
4
|
+
const HEX64=/^[0-9a-f]{64}$/;
|
|
5
|
+
const digest=(alg,bytes,encoding='hex')=>createHash(alg).update(bytes).digest(encoding);
|
|
6
|
+
|
|
7
|
+
export function resumePackRecord({filename,localBytes,claim,version,manifestSha256}={}){
|
|
8
|
+
if(typeof version!=='string'||!/^\d+\.\d+\.\d+$/.test(version)||
|
|
9
|
+
filename!=='deadbyte-mcp-'+version+'.tgz')
|
|
10
|
+
throw new Error('npm resume version or filename invalid');
|
|
11
|
+
if(!Buffer.isBuffer(localBytes)||localBytes.length<1)
|
|
12
|
+
throw new Error('npm resume preserved tarball bytes unavailable');
|
|
13
|
+
if(!HEX64.test(String(manifestSha256))||
|
|
14
|
+
!claim||typeof claim!=='object'||Array.isArray(claim)||
|
|
15
|
+
claim.schema!=='deadbyte.npm-publication-claim.r65.v1'||claim.version!==version||
|
|
16
|
+
claim.manifest_sha256!==manifestSha256||
|
|
17
|
+
!HEX64.test(String(claim.tarball_sha256)))
|
|
18
|
+
throw new Error('npm resume immutable claim manifest or version mismatch');
|
|
19
|
+
const sha256=digest('sha256',localBytes);
|
|
20
|
+
if(sha256!==claim.tarball_sha256)
|
|
21
|
+
throw new Error('npm resume preserved tarball differs from immutable claim digest');
|
|
22
|
+
return Object.freeze({filename,sha256,
|
|
23
|
+
integrity:'sha512-'+digest('sha512',localBytes,'base64'),
|
|
24
|
+
shasum:digest('sha1',localBytes)});
|
|
25
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// R168 one-shot command dispatch. Pure policy. Does not spawn or modify state.
|
|
2
|
+
import {compareRuntimeVersions} from './runtime-coherence.mjs';
|
|
3
|
+
|
|
4
|
+
const COMMANDS=new Set([
|
|
5
|
+
'menu','start','resume','park','stop','restart','status','url','test','fulltest','toggle','clean','machinetest',
|
|
6
|
+
'armprocess','disarmprocess','processstatus','armhost','disarmhost','hoststatus','hosttest',
|
|
7
|
+
'armdesktop','disarmdesktop','desktopstatus',
|
|
8
|
+
'armcoding','disarmcoding','codingstatus','codingtest',
|
|
9
|
+
'armautonomous','disarmautonomous','autonomousstatus','autonomoustest'
|
|
10
|
+
]);
|
|
11
|
+
const OBSERVE=new Set(['status','processstatus','codingstatus','autonomousstatus','desktopstatus','hoststatus']);
|
|
12
|
+
const safe=(mode,active,cli,message=null)=>Object.freeze({
|
|
13
|
+
mode,active_version:active,cli_version:cli,message,
|
|
14
|
+
may_activate:false,may_mutate_authority:false
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export function selectR168OneShotRemotePath({command,active,cliVersion}={}){
|
|
18
|
+
if(typeof command!=='string'||!COMMANDS.has(command))
|
|
19
|
+
throw new Error('R168 unsupported remote controller command');
|
|
20
|
+
if(active===null||active===undefined){
|
|
21
|
+
if(OBSERVE.has(command))return safe('observe_unbound',null,cliVersion,
|
|
22
|
+
'R168 active runtime binding absent: read-only status only, never activate.');
|
|
23
|
+
throw new Error('R168 active runtime binding unavailable for authority mutation');
|
|
24
|
+
}
|
|
25
|
+
if(typeof active!=='object'||typeof active.version!=='string'||
|
|
26
|
+
!/^\d+\.\d+\.\d+$/.test(active.version))
|
|
27
|
+
throw new Error('R168 active runtime binding unavailable or invalid');
|
|
28
|
+
const relation=compareRuntimeVersions(cliVersion,active.version);
|
|
29
|
+
if(relation===0) return safe('verify_coherent',active.version,cliVersion);
|
|
30
|
+
const explanation=
|
|
31
|
+
'R168 version skew: installed runtime '+active.version+', npm CLI '+cliVersion+
|
|
32
|
+
'. Automatic activation from npm cache is forbidden. Existing runtime remains unchanged.';
|
|
33
|
+
if(OBSERVE.has(command))return safe('observe_active',active.version,cliVersion,explanation);
|
|
34
|
+
return safe('blocked_version_skew',active.version,cliVersion,
|
|
35
|
+
explanation+' Run the corresponding command through the installed active-release launcher only if specifically authorized.');
|
|
36
|
+
}
|
package/src/version.mjs
CHANGED
|
@@ -7,18 +7,18 @@ import {DEADBYTE_VERSION} from '../src/version.mjs';
|
|
|
7
7
|
|
|
8
8
|
const root=path.resolve(fileURLToPath(new URL('..',import.meta.url)));
|
|
9
9
|
const read=async file=>JSON.parse(await readFile(path.join(root,file),'utf8'));
|
|
10
|
-
test('current successor retains the R130 security baseline with truthful
|
|
10
|
+
test('current successor retains the R130 security baseline with truthful R168 identity',async()=>{
|
|
11
11
|
const [pkg,release,provenance,bom,lock,matrix]=await Promise.all([
|
|
12
12
|
read('package.json'),read('RELEASE.json'),read('PROVENANCE.json'),
|
|
13
13
|
read('SBOM.cdx.json'),read('package-lock.json'),read('TEST-MATRIX.json')]);
|
|
14
14
|
assert.equal(pkg.name,'deadbyte-mcp');
|
|
15
15
|
for(const actual of [pkg.version,release.version,provenance.version,matrix.version,bom.metadata.component.version,lock.version,lock.packages[''].version,DEADBYTE_VERSION])
|
|
16
|
-
assert.equal(actual,'0.15.
|
|
17
|
-
assert.equal(release.train,'
|
|
18
|
-
assert.equal(matrix.train,'
|
|
19
|
-
assert.equal(release.source_predecessor.version,'0.15.
|
|
20
|
-
assert.equal(release.source_predecessor.train,'
|
|
21
|
-
assert.match(release.source_predecessor.source_path,/0\.15\.
|
|
16
|
+
assert.equal(actual,'0.15.66');
|
|
17
|
+
assert.equal(release.train,'R168');
|
|
18
|
+
assert.equal(matrix.train,'R168');
|
|
19
|
+
assert.equal(release.source_predecessor.version,'0.15.65');
|
|
20
|
+
assert.equal(release.source_predecessor.train,'R167');
|
|
21
|
+
assert.match(release.source_predecessor.source_path,/0\.15\.65-r167-activation-path/i);
|
|
22
22
|
assert.equal(release.source_predecessor.manifest_status,'VERIFIED_FINAL_BYTES');
|
|
23
23
|
assert.equal(release.runtime_predecessor.version,'0.15.61');
|
|
24
24
|
assert.equal(provenance.r160.source_predecessor_manifest_status,'STALE_REJECTED');
|
|
@@ -8,22 +8,22 @@ import {DEADBYTE_VERSION} from '../src/version.mjs';
|
|
|
8
8
|
const root=path.resolve(fileURLToPath(new URL('..',import.meta.url)));
|
|
9
9
|
const read=async rel=>JSON.parse(await readFile(path.join(root,rel),'utf8'));
|
|
10
10
|
|
|
11
|
-
test('
|
|
11
|
+
test('R168 package identity preserves R132 evidence and published R167 source predecessor',async()=>{
|
|
12
12
|
const [pkg,lock,release,prov,bom,matrix]=await Promise.all([
|
|
13
13
|
read('package.json'),read('package-lock.json'),read('RELEASE.json'),
|
|
14
14
|
read('PROVENANCE.json'),read('SBOM.cdx.json'),read('TEST-MATRIX.json')]);
|
|
15
15
|
for(const value of [pkg.version,lock.version,lock.packages[''].version,release.version,prov.version,bom.metadata.component.version,matrix.version,DEADBYTE_VERSION]){
|
|
16
16
|
assert.equal(value,pkg.version);
|
|
17
17
|
}
|
|
18
|
-
assert.equal(release.train,'
|
|
19
|
-
assert.equal(matrix.train,'
|
|
20
|
-
assert.equal(release.source_predecessor.version,'0.15.
|
|
21
|
-
assert.equal(release.source_predecessor.train,'
|
|
22
|
-
assert.match(release.source_predecessor.source_path,/0\.15\.
|
|
18
|
+
assert.equal(release.train,'R168');
|
|
19
|
+
assert.equal(matrix.train,'R168');
|
|
20
|
+
assert.equal(release.source_predecessor.version,'0.15.65');
|
|
21
|
+
assert.equal(release.source_predecessor.train,'R167');
|
|
22
|
+
assert.match(release.source_predecessor.source_path,/0\.15\.65-r167-activation-path/i);
|
|
23
23
|
assert.equal(prov.r160.source_predecessor_manifest_status,'STALE_REJECTED');
|
|
24
24
|
assert.equal(release.r165.published_source_manifest_sha256,'4adf7d94682fbb71c4a91eb3147ecc48076bbc5d79cb67fcb875cde6a1bb4805');
|
|
25
25
|
assert.equal(release.r165.published_source_tarball_sha256,'812e97609b7c51353ed25f2202129737e43bb4fbe0778c3adec43ec065352641');
|
|
26
|
-
assert.equal(release.source_predecessor.tarball_sha256,'
|
|
26
|
+
assert.equal(release.source_predecessor.tarball_sha256,'fc3ae5b1505e316bc9dfedf8d063cb00641754c8f5f1306a31f8cc4bb84c9484');
|
|
27
27
|
assert.equal(prov.source_predecessor_tarball_sha256,release.source_predecessor.tarball_sha256);
|
|
28
28
|
assert.equal(release.runtime_predecessor.version,'0.15.61');
|
|
29
29
|
assert.equal(prov.runtime_predecessor.release_id,release.runtime_predecessor.release_id);
|
|
@@ -26,7 +26,7 @@ test('R153 historical verifier refuses current successor package identity',async
|
|
|
26
26
|
const state={pkg:JSON.parse(await readFile(path.join(root,'package.json'),'utf8')),
|
|
27
27
|
release:JSON.parse(await readFile(path.join(root,'RELEASE.json'),'utf8')),
|
|
28
28
|
bridge:JSON.parse(await readFile(path.join(root,'bridge/package.json'),'utf8'))};
|
|
29
|
-
assert.equal(state.pkg.version,'0.15.
|
|
29
|
+
assert.equal(state.pkg.version,'0.15.66');
|
|
30
30
|
assert.throws(()=>verifyR153Lineage(state),/package version/);
|
|
31
31
|
await assert.rejects(verifyR153IdentityTree(root),/package version/);
|
|
32
32
|
});
|
|
@@ -8,7 +8,7 @@ import {fileURLToPath} from 'node:url';
|
|
|
8
8
|
import {atomicWriteActivePointer,materializeReleaseSlot,parseActivePointer,parseManifest,renderStableLauncherPs1,verifyManifestTreeExact} from '../src/release-slot.mjs';
|
|
9
9
|
|
|
10
10
|
const candidate=path.dirname(path.dirname(fileURLToPath(import.meta.url)));
|
|
11
|
-
const version='0.15.
|
|
11
|
+
const version='0.15.66';
|
|
12
12
|
const parent=path.join(os.homedir(),'.deadbyte-mcp','releases','v0.15.61-055d1ff0aa99e60b');
|
|
13
13
|
const parentDigest='055d1ff0aa99e60bbf9e9f136b02ebd4fffb8391712560f7607cff30af94dcdf';
|
|
14
14
|
const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
@@ -21,7 +21,7 @@ async function generation(state,label){
|
|
|
21
21
|
await writeFile(path.join(root,'GENERATION.SHA256'),manifest,{flag:'wx'});
|
|
22
22
|
return {root,manifest_sha256:sha(manifest),config_path:path.join(root,'controller-config.psd1')};
|
|
23
23
|
}
|
|
24
|
-
test('
|
|
24
|
+
test('R168 isolated immutable slot activates, rolls back, and restores 0.15.66 without authority or production mutation',async()=>{
|
|
25
25
|
const stateBase=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r163-isolated-'));
|
|
26
26
|
const state=path.join(stateBase,'state');
|
|
27
27
|
try{
|
|
@@ -17,7 +17,7 @@ const hashMarker=c=>c.repeat(64);
|
|
|
17
17
|
async function fixture(t,kind){
|
|
18
18
|
assert.equal(typeof envRoot,'string'); assert.ok(path.isAbsolute(envRoot));
|
|
19
19
|
assert.match(sha(await readFile(path.join(envRoot,'MANIFEST.SHA256'))),/^[0-9a-f]{64}$/);
|
|
20
|
-
assert.equal(JSON.parse(await readFile(path.join(envRoot,'package.json'),'utf8')).version,'0.15.
|
|
20
|
+
assert.equal(JSON.parse(await readFile(path.join(envRoot,'package.json'),'utf8')).version,'0.15.66');
|
|
21
21
|
const dir=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r164-skew-e2e-'));
|
|
22
22
|
t.after(async()=>{await rm(dir,{recursive:true,force:true,maxRetries:10,retryDelay:150});});
|
|
23
23
|
const state=path.join(dir,'state'),bin=path.join(state,'bin');
|
|
@@ -65,26 +65,26 @@ async function run(fx){
|
|
|
65
65
|
assert.doesNotMatch(trace,/\b(?:start|restart|armprocess|armcoding|armautonomous|deploy)\b/i,'unexpected runtime operation');
|
|
66
66
|
return {output:(result.stderr||'')+'\n'+(result.stdout||''),trace};
|
|
67
67
|
}
|
|
68
|
-
gate('
|
|
68
|
+
gate('R168 executable CLI blocks npm-cache activation on version skew before touching R159 history',async t=>{
|
|
69
69
|
const fx=await fixture(t,'completed');
|
|
70
70
|
const r=await run(fx);
|
|
71
|
-
assert.match(r.output,/
|
|
71
|
+
assert.match(r.output,/R168 version skew.*0\.15\.61.*0\.15\.66/i,r.output);
|
|
72
72
|
assert.doesNotMatch(r.output,/R159 ambiguous multiple completed/i);
|
|
73
|
-
assert.
|
|
73
|
+
assert.equal(r.trace,'');
|
|
74
74
|
assert.doesNotMatch(r.trace,/disarmdesktop/);
|
|
75
75
|
});
|
|
76
76
|
gate('R165 executable CLI rejects pending current reconciliation before upgrade',async t=>{
|
|
77
77
|
const fx=await fixture(t,'pending'),r=await run(fx);
|
|
78
|
-
assert.match(r.output,/
|
|
78
|
+
assert.match(r.output,/R168 version skew/i,r.output);
|
|
79
79
|
assert.doesNotMatch(r.trace,/disarmhost/);
|
|
80
80
|
});
|
|
81
81
|
gate('R165 executable CLI rejects tampered terminal before upgrade',async t=>{
|
|
82
82
|
const fx=await fixture(t,'tampered'),r=await run(fx);
|
|
83
|
-
assert.match(r.output,/
|
|
83
|
+
assert.match(r.output,/R168 version skew/i,r.output);
|
|
84
84
|
assert.doesNotMatch(r.trace,/disarmhost/);
|
|
85
85
|
});
|
|
86
86
|
gate('R165 executable CLI keeps ambiguous history fail-closed when predecessor is not ONLINE',async t=>{
|
|
87
87
|
const fx=await fixture(t,'offline'),r=await run(fx);
|
|
88
|
-
assert.match(r.output,/
|
|
88
|
+
assert.match(r.output,/R168 version skew/i,r.output);
|
|
89
89
|
assert.doesNotMatch(r.trace,/disarmhost/);
|
|
90
90
|
});
|
|
@@ -8,11 +8,11 @@ import {DEADBYTE_VERSION} from '../src/version.mjs';
|
|
|
8
8
|
const root=path.resolve(fileURLToPath(new URL('..',import.meta.url)));
|
|
9
9
|
const read=async rel=>JSON.parse(await readFile(path.join(root,rel),'utf8'));
|
|
10
10
|
const readOptional=async rel=>{try{return await read(rel);}catch(error){if(error?.code==='ENOENT')return null;throw error;}};
|
|
11
|
-
const VERSION='0.15.
|
|
11
|
+
const VERSION='0.15.66';
|
|
12
12
|
const R166_MANIFEST='ad3c091fbbe2bac6d234548724008b67305fd483ad6542f92d5aeb1aa84f5849';
|
|
13
13
|
const R166_TARBALL='3d22412c3cebabd66bfa92ca4dbaaf3ef49acba7f92436a16485d7e4cb18c653';
|
|
14
14
|
|
|
15
|
-
test('
|
|
15
|
+
test('R168 package identity binds published R167 source separately from active R153 runtime',async()=>{
|
|
16
16
|
const [pkg,lock,bridge,release,provenance,matrix,bom]=await Promise.all([
|
|
17
17
|
read('package.json'),readOptional('package-lock.json'),read('bridge/package.json'),read('RELEASE.json'),
|
|
18
18
|
read('PROVENANCE.json'),read('TEST-MATRIX.json'),read('SBOM.cdx.json')]);
|
|
@@ -20,20 +20,20 @@ test('R167 package identity binds published R166 source separately from active R
|
|
|
20
20
|
provenance.version,matrix.version,bom.metadata.component.version,DEADBYTE_VERSION];
|
|
21
21
|
if(lock)versions.push(lock.version,lock.packages[''].version);
|
|
22
22
|
for(const version of versions)assert.equal(version,VERSION);
|
|
23
|
-
assert.equal(release.train,'
|
|
24
|
-
assert.equal(matrix.train,'
|
|
25
|
-
assert.equal(bom.serialNumber,'urn:cdx:deadbyte-mcp:
|
|
23
|
+
assert.equal(release.train,'R168');
|
|
24
|
+
assert.equal(matrix.train,'R168');
|
|
25
|
+
assert.equal(bom.serialNumber,'urn:cdx:deadbyte-mcp:r168:0.15.66');
|
|
26
26
|
assert.deepEqual(release.runtime_predecessor,{
|
|
27
27
|
version:'0.15.61',release_id:'v0.15.61-055d1ff0aa99e60b',
|
|
28
28
|
manifest_sha256:'055d1ff0aa99e60bbf9e9f136b02ebd4fffb8391712560f7607cff30af94dcdf',
|
|
29
29
|
generation_manifest_sha256:'f940e794daac2dda097a66301c4b85c4b29a2df7c669e549503c05a2a55ff6c4'
|
|
30
30
|
});
|
|
31
|
-
assert.equal(release.source_predecessor.version,'0.15.
|
|
32
|
-
assert.equal(release.source_predecessor.train,'
|
|
33
|
-
assert.equal(release.source_predecessor.tarball_sha256,
|
|
31
|
+
assert.equal(release.source_predecessor.version,'0.15.65');
|
|
32
|
+
assert.equal(release.source_predecessor.train,'R167');
|
|
33
|
+
assert.equal(release.source_predecessor.tarball_sha256,'fc3ae5b1505e316bc9dfedf8d063cb00641754c8f5f1306a31f8cc4bb84c9484');
|
|
34
34
|
assert.equal(release.r167.source_predecessor_manifest_sha256,R166_MANIFEST);
|
|
35
|
-
assert.equal(provenance.source_predecessor_manifest_file_sha256,
|
|
36
|
-
assert.equal(provenance.source_predecessor_tarball_sha256,
|
|
35
|
+
assert.equal(provenance.source_predecessor_manifest_file_sha256,'3316654646cc870584a7c969c2032af6c795229ff8c65e5a8b794f97c2458c41');
|
|
36
|
+
assert.equal(provenance.source_predecessor_tarball_sha256,'fc3ae5b1505e316bc9dfedf8d063cb00641754c8f5f1306a31f8cc4bb84c9484');
|
|
37
37
|
assert.equal(release.r167.public_tool_delta,0);
|
|
38
38
|
assert.equal(release.r167.authority_scope_delta,0);
|
|
39
39
|
assert.equal(release.r167.production_activation,'NOT_RUN');
|