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,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
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// R169 pre-dispatch identity fence. Only routes an explicit user-issued
|
|
2
|
+
// authority command to the already installed controller, never upgrades or
|
|
3
|
+
// fabricates an authority grant.
|
|
4
|
+
import {createHash} from 'node:crypto';
|
|
5
|
+
import {lstat,readFile,realpath} from 'node:fs/promises';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
|
|
8
|
+
const SHA=/^[0-9a-f]{64}$/;
|
|
9
|
+
const hash=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
10
|
+
function deny(condition,reason){if(!condition)throw new Error('R169 installed controller '+reason);}
|
|
11
|
+
const canonical=pathName=>path.win32.normalize(path.win32.resolve(pathName)).toLowerCase();
|
|
12
|
+
function descendant(parent,child){
|
|
13
|
+
const r=path.win32.relative(parent,child);
|
|
14
|
+
return r!==''&&r!=='..'&&!r.startsWith('..'+path.win32.sep)&&!path.win32.isAbsolute(r);
|
|
15
|
+
}
|
|
16
|
+
async function fileBytes(file){
|
|
17
|
+
const before=await lstat(file);
|
|
18
|
+
deny(before.isFile()&&!before.isSymbolicLink()&&before.size>0&&before.size<=8*1024*1024,
|
|
19
|
+
'file not regular or bounded');
|
|
20
|
+
deny(canonical(await realpath(file))===canonical(file),'file reparse not allowed');
|
|
21
|
+
const bytes=await readFile(file);
|
|
22
|
+
deny(bytes.length===before.size,'file length changed');
|
|
23
|
+
return bytes;
|
|
24
|
+
}
|
|
25
|
+
export async function assertR169PinnedActiveDelegation({stateRoot,launcherPath,active}={}){
|
|
26
|
+
deny(process.platform==='win32','Windows only');
|
|
27
|
+
deny(typeof stateRoot==='string'&&path.win32.isAbsolute(stateRoot),'state root invalid');
|
|
28
|
+
deny(active&&typeof active==='object'&&
|
|
29
|
+
SHA.test(String(active.manifest_sha256))&&
|
|
30
|
+
SHA.test(String(active.generation_manifest_sha256))&&
|
|
31
|
+
SHA.test(String(active.launcher_sha256))&&
|
|
32
|
+
typeof active.release_id==='string'&&
|
|
33
|
+
typeof active.release_root==='string'&&
|
|
34
|
+
typeof active.generation_root==='string'&&
|
|
35
|
+
typeof active.config_path==='string','active release binding incomplete');
|
|
36
|
+
const root=path.win32.resolve(stateRoot);
|
|
37
|
+
const expectedLauncher=path.win32.join(root,'bin','DEADBYTE-MCP.ps1');
|
|
38
|
+
deny(canonical(launcherPath)===canonical(expectedLauncher),'launcher path not active stable launcher');
|
|
39
|
+
deny(descendant(path.win32.join(root,'releases'),active.release_root)&&
|
|
40
|
+
descendant(path.win32.join(root,'generations'),active.generation_root),
|
|
41
|
+
'release/generation escapes state root');
|
|
42
|
+
deny(canonical(active.config_path)===canonical(path.win32.join(active.generation_root,'controller-config.psd1')),
|
|
43
|
+
'generation controller config not bound');
|
|
44
|
+
const launcher=await fileBytes(launcherPath);
|
|
45
|
+
deny(hash(launcher)===active.launcher_sha256,'launcher digest mismatch');
|
|
46
|
+
const releaseManifest=await fileBytes(path.win32.join(active.release_root,'MANIFEST.SHA256'));
|
|
47
|
+
deny(hash(releaseManifest)===active.manifest_sha256,'release manifest digest mismatch');
|
|
48
|
+
const generationManifest=await fileBytes(path.win32.join(active.generation_root,'GENERATION.SHA256'));
|
|
49
|
+
deny(hash(generationManifest)===active.generation_manifest_sha256,
|
|
50
|
+
'generation manifest digest mismatch');
|
|
51
|
+
// Installed PowerShell launcher enforces its own identity/generation/
|
|
52
|
+
// controller provenance again before invoking the actual controller.
|
|
53
|
+
return Object.freeze({release_id:active.release_id,version:active.version,
|
|
54
|
+
launcher_sha256:active.launcher_sha256,manifest_sha256:active.manifest_sha256,
|
|
55
|
+
generation_manifest_sha256:active.generation_manifest_sha256,
|
|
56
|
+
activation_attempted:false});
|
|
57
|
+
}
|
package/src/deadbyte-cli.mjs
CHANGED
|
@@ -19,6 +19,8 @@ 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';
|
|
23
|
+
import {assertR169PinnedActiveDelegation} from './active-controller-delegation-r169.mjs';
|
|
22
24
|
import { windowsPowerShellEnvironment } from './runtime-update.mjs';
|
|
23
25
|
import { resolveStateRoot } from './state-root.mjs';
|
|
24
26
|
import {foregroundIdentityMatches,queryForegroundIdentity,validForegroundIdentity,proveRestrictedPidReuse} from './foreground-process-identity.mjs';
|
|
@@ -787,6 +789,16 @@ async function runRemoteSession({resumeCursor=null,verbose=false,trace=false,fil
|
|
|
787
789
|
const display=prepareRemoteConsole();
|
|
788
790
|
const displayMode=display.mode;
|
|
789
791
|
const ctx=await remoteContext();
|
|
792
|
+
// Never attempt automatic activation or cleanup from an npm-cache build on
|
|
793
|
+
// an EPOCH_NATIVE production runtime. This guard precedes session recovery,
|
|
794
|
+
// marker claims, revocations and any controller startup.
|
|
795
|
+
const active=await readActiveRuntimeBinding(ctx.root);
|
|
796
|
+
if(active){
|
|
797
|
+
const decision=selectR168OneShotRemotePath({command:'start',active,cliVersion:DEADBYTE_VERSION});
|
|
798
|
+
if(decision.mode==='blocked_version_skew') throw new Error(decision.message);
|
|
799
|
+
}
|
|
800
|
+
// First installation without an active pointer retains the existing
|
|
801
|
+
// unbound bootstrap path. A bound older release must never auto-upgrade.
|
|
790
802
|
if(displayMode==='ascii') writeRemote(process.stdout,`[DISPLAY] ASCII fallback: ${display.reason}\n`,displayMode);
|
|
791
803
|
if(resumeCursor!==null) await replayRemoteResults(ctx.root,resumeCursor,{displayMode});
|
|
792
804
|
const startupLock=await acquireForegroundSessionLock({root:ctx.root,powershell:ctx.powershell});
|
|
@@ -946,6 +958,32 @@ async function runRemote(args){
|
|
|
946
958
|
if(positional.length>1) throw new Error('remote accepts one controller command');
|
|
947
959
|
const command=positional[0];if(!REMOTE_COMMANDS.has(command)) throw new Error(`unsupported remote command '${command}'`);
|
|
948
960
|
const ctx=await remoteContext();
|
|
961
|
+
const active=await readActiveRuntimeBinding(ctx.root);
|
|
962
|
+
const decision=selectR168OneShotRemotePath({command,active,cliVersion:DEADBYTE_VERSION});
|
|
963
|
+
if(decision.mode==='blocked_version_skew') throw new Error(decision.message);
|
|
964
|
+
if(decision.mode==='delegate_active'){
|
|
965
|
+
// R169 explicitly delegates ONLY plane arm/disarm verbs to the installed
|
|
966
|
+
// controller. The launcher verifies its own identity and policy; this path
|
|
967
|
+
// never launches deploy-r48.mjs or attempts runtime coherence activation.
|
|
968
|
+
await assertR169PinnedActiveDelegation({stateRoot:ctx.root,launcherPath:ctx.launcher,active});
|
|
969
|
+
const unchanged=await readActiveRuntimeBinding(ctx.root);
|
|
970
|
+
if(unchanged?.release_id!==active.release_id||
|
|
971
|
+
unchanged?.generation_manifest_sha256!==active.generation_manifest_sha256)
|
|
972
|
+
throw new Error('R169 active runtime changed before delegated command; no dispatch');
|
|
973
|
+
const result=invokeController(ctx,command,{allowFailure:true,timeoutMs:REMOTE_STATUS_TIMEOUT_MS});
|
|
974
|
+
const final=await readActiveRuntimeBinding(ctx.root);
|
|
975
|
+
if(final?.release_id!==active.release_id||
|
|
976
|
+
final?.generation_manifest_sha256!==active.generation_manifest_sha256)
|
|
977
|
+
throw new Error('R169 active runtime changed during delegated command; outcome unknown');
|
|
978
|
+
process.exitCode=result.status;
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
981
|
+
if(decision.mode==='observe_active'||decision.mode==='observe_unbound'){
|
|
982
|
+
if(decision.mode==='observe_active')await reportVersionCoherence(ctx.root,{displayMode:'ascii'});
|
|
983
|
+
const observed=invokeController(ctx,command,{allowFailure:true,timeoutMs:REMOTE_STATUS_TIMEOUT_MS});
|
|
984
|
+
process.exitCode=observed.status;
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
949
987
|
await synchronizeOneShotRuntime(ctx,{displayMode:'ascii'});
|
|
950
988
|
const result=invokeController(ctx,command,{allowFailure:true});
|
|
951
989
|
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,43 @@
|
|
|
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 DELEGATE=new Set(['armprocess','disarmprocess','armcoding','disarmcoding','armautonomous',
|
|
13
|
+
'disarmautonomous','armdesktop','disarmdesktop','armhost','disarmhost']);
|
|
14
|
+
const safe=(mode,active,cli,message=null)=>Object.freeze({
|
|
15
|
+
mode,active_version:active,cli_version:cli,message,
|
|
16
|
+
may_activate:false,may_mutate_authority:false
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export function selectR168OneShotRemotePath({command,active,cliVersion}={}){
|
|
20
|
+
if(typeof command!=='string'||!COMMANDS.has(command))
|
|
21
|
+
throw new Error('R168 unsupported remote controller command');
|
|
22
|
+
if(active===null||active===undefined){
|
|
23
|
+
if(OBSERVE.has(command))return safe('observe_unbound',null,cliVersion,
|
|
24
|
+
'R168 active runtime binding absent: read-only status only, never activate.');
|
|
25
|
+
throw new Error('R168 active runtime binding unavailable for authority mutation');
|
|
26
|
+
}
|
|
27
|
+
if(typeof active!=='object'||typeof active.version!=='string'||
|
|
28
|
+
!/^\d+\.\d+\.\d+$/.test(active.version))
|
|
29
|
+
throw new Error('R168 active runtime binding unavailable or invalid');
|
|
30
|
+
const relation=compareRuntimeVersions(cliVersion,active.version);
|
|
31
|
+
if(relation===0) return safe('verify_coherent',active.version,cliVersion);
|
|
32
|
+
const explanation=
|
|
33
|
+
'R168 version skew: installed runtime '+active.version+', npm CLI '+cliVersion+
|
|
34
|
+
'. Automatic activation from npm cache is forbidden. Existing runtime remains unchanged.';
|
|
35
|
+
if(OBSERVE.has(command))return safe('observe_active',active.version,cliVersion,explanation);
|
|
36
|
+
if(relation>0&&DELEGATE.has(command))return Object.freeze({
|
|
37
|
+
...safe('delegate_active',active.version,cliVersion,
|
|
38
|
+
explanation+' Forwarding explicit plane authority request to installed active controller.'),
|
|
39
|
+
may_mutate_authority:true
|
|
40
|
+
});
|
|
41
|
+
return safe('blocked_version_skew',active.version,cliVersion,
|
|
42
|
+
explanation+' Run the corresponding command through the installed active-release launcher only if specifically authorized.');
|
|
43
|
+
}
|
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 R172 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.67');
|
|
17
|
+
assert.equal(release.train,'R172');
|
|
18
|
+
assert.equal(matrix.train,'R172');
|
|
19
|
+
assert.equal(release.source_predecessor.version,'0.15.66');
|
|
20
|
+
assert.equal(release.source_predecessor.train,'R168');
|
|
21
|
+
assert.match(release.source_predecessor.source_path,/0\.15\.66-r168-activation-reconcile/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,23 +8,23 @@ 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('R172 package identity preserves R132 evidence and published R168 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,'R172');
|
|
19
|
+
assert.equal(matrix.train,'R172');
|
|
20
|
+
assert.equal(release.source_predecessor.version,'0.15.66');
|
|
21
|
+
assert.equal(release.source_predecessor.train,'R168');
|
|
22
|
+
assert.match(release.source_predecessor.source_path,/0\.15\.66-r168-activation-reconcile/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,'
|
|
27
|
-
assert.equal(
|
|
26
|
+
assert.equal(release.source_predecessor.tarball_sha256,'31b194a4fbff7dbdb9f4f275b4ea49c5115f7b0bb2241c0c72d72ef690311a5d');
|
|
27
|
+
assert.equal(release.r172.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);
|
|
30
30
|
assert.equal(prov.runtime_predecessor.manifest_sha256,release.runtime_predecessor.manifest_sha256);
|
|
@@ -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.67');
|
|
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.67';
|
|
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('R172 isolated immutable slot activates, rolls back, and restores 0.15.67 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.67');
|
|
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\.67/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.67';
|
|
12
12
|
const R166_MANIFEST='ad3c091fbbe2bac6d234548724008b67305fd483ad6542f92d5aeb1aa84f5849';
|
|
13
13
|
const R166_TARBALL='3d22412c3cebabd66bfa92ca4dbaaf3ef49acba7f92436a16485d7e4cb18c653';
|
|
14
14
|
|
|
15
|
-
test('
|
|
15
|
+
test('R172 package identity binds published R168 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,21 @@ 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,'R172');
|
|
24
|
+
assert.equal(matrix.train,'R172');
|
|
25
|
+
assert.equal(bom.serialNumber,'urn:cdx:deadbyte-mcp:r172:0.15.67');
|
|
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.66');
|
|
32
|
+
assert.equal(release.source_predecessor.train,'R168');
|
|
33
|
+
assert.equal(release.source_predecessor.tarball_sha256,'31b194a4fbff7dbdb9f4f275b4ea49c5115f7b0bb2241c0c72d72ef690311a5d');
|
|
34
34
|
assert.equal(release.r167.source_predecessor_manifest_sha256,R166_MANIFEST);
|
|
35
|
-
assert.equal(provenance.source_predecessor_manifest_file_sha256,
|
|
36
|
-
assert.equal(
|
|
35
|
+
assert.equal(provenance.source_predecessor_manifest_file_sha256,'962e227dace247d59aa860211388fbde461c5f9c8a7e8a878e34a14a27d355f8');
|
|
36
|
+
assert.equal(release.r172.source_predecessor_manifest_sha256,'962e227dace247d59aa860211388fbde461c5f9c8a7e8a878e34a14a27d355f8');
|
|
37
|
+
assert.equal(release.r172.source_predecessor_tarball_sha256,'31b194a4fbff7dbdb9f4f275b4ea49c5115f7b0bb2241c0c72d72ef690311a5d');
|
|
37
38
|
assert.equal(release.r167.public_tool_delta,0);
|
|
38
39
|
assert.equal(release.r167.authority_scope_delta,0);
|
|
39
40
|
assert.equal(release.r167.production_activation,'NOT_RUN');
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// R168 targeted test entrypoint. Does not invoke production authority.
|
|
2
|
+
import {spawnSync} from 'node:child_process';
|
|
3
|
+
import {fileURLToPath} from 'node:url';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
|
|
6
|
+
const dir=path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const cases=['r168-five-plane-fence.test.mjs','r168-focused-suite.test.mjs','r168-crash-journal.test.mjs','r168-protected-entrypoint.test.mjs','r168-remote-one-shot-coherence.test.mjs'];
|
|
8
|
+
const result=spawnSync(process.execPath,['--test','--test-concurrency=1',...cases.map(x=>path.join(dir,x))],{
|
|
9
|
+
cwd:dir,encoding:'utf8',shell:false,windowsHide:true,timeout:120000,maxBuffer:3*1024*1024
|
|
10
|
+
});
|
|
11
|
+
if(result.stdout)process.stdout.write(result.stdout);
|
|
12
|
+
if(result.stderr)process.stderr.write(result.stderr);
|
|
13
|
+
if(result.error)throw result.error;
|
|
14
|
+
process.exitCode=result.status??1;
|