deadbyte-mcp 0.15.68 → 0.15.69
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 +81 -38
- package/PROVENANCE.json +35 -23
- package/README.txt +44 -31
- package/RELEASE.json +30 -17
- package/SBOM.cdx.json +2 -2
- package/TEST-MATRIX.json +25 -2
- package/bridge/package.json +1 -1
- package/controller/deadbyte-controller.ps1 +65 -1
- package/controller/deadbyte-process-policy.json +4 -4
- package/docs/R184-package-lock.json +97 -0
- package/docs/R186-OWNER-CONSENT-HANDOFF.txt +87 -0
- package/package.json +1 -1
- package/scripts/install-r186-owner-trust.ps1 +59 -0
- package/scripts/r175-focused-runner.mjs +38 -0
- package/scripts/r176-log-preview.mjs +18 -0
- package/scripts/r177-owner-trust-inventory.ps1 +36 -0
- package/scripts/r178-full-regression.mjs +21 -0
- package/scripts/r178-npm-ci.mjs +10 -0
- package/scripts/r178-npm-preflight.mjs +16 -0
- package/scripts/r178-release-gates-readonly.mjs +9 -0
- package/scripts/r178-shard-regression.mjs +25 -0
- package/scripts/r179-predecessor-registry.mjs +24 -0
- package/scripts/r179-protected-inventory.ps1 +21 -0
- package/scripts/r186-focused-runner.mjs +22 -0
- package/scripts/r186-owner-anchor-inspect.mjs +6 -0
- package/scripts/r186-owner-consent-check.mjs +86 -0
- package/scripts/r186-owner-grant-cli.mjs +45 -0
- package/scripts/r186-owner-key-local.mjs +22 -0
- package/scripts/r186-owner-review-export.mjs +34 -0
- package/src/deadbyte-cli.mjs +69 -56
- package/src/r174-owner-webauthn.mjs +176 -0
- package/src/r175-foreground-boundary.mjs +28 -0
- package/src/r186-owner-anchor.mjs +12 -0
- package/src/r186-owner-consent-preflight.mjs +63 -0
- package/src/r186-owner-grant-broker.mjs +96 -0
- package/src/r186-owner-local-signing.mjs +25 -0
- package/src/r186-owner-request.mjs +40 -0
- package/src/r186-owner-review-export.mjs +49 -0
- package/src/remote-live-log.mjs +81 -5
- package/src/remote-session-guardian.mjs +23 -0
- package/src/version.mjs +1 -1
- package/test/helpers/r175-active-pointer.mjs +28 -0
- package/test/helpers/release-lock.mjs +12 -0
- package/test/r129-sdk-security-baseline.test.mjs +2 -1
- package/test/r130-release-security-contract.test.mjs +11 -10
- package/test/r132-release-identity.test.mjs +12 -11
- package/test/r153-prepublish-identity.test.mjs +1 -1
- package/test/r155-default-live-log.test.mjs +17 -16
- package/test/r160-package-decision-manifest.test.mjs +1 -1
- package/test/r160-release-contract.test.mjs +4 -5
- package/test/r163-isolated-slot-rollback-restore.test.mjs +6 -6
- package/test/r165-live-cli-skew.test.mjs +6 -6
- package/test/r167-release-contract.test.mjs +15 -13
- package/test/r168-protected-entrypoint.test.mjs +5 -2
- package/test/r168-release-contract.test.mjs +4 -3
- package/test/r168-remote-one-shot-coherence.test.mjs +2 -1
- package/test/r172-release-contract.test.mjs +7 -7
- package/test/r173-release-contract.test.mjs +18 -17
- package/test/r174-owner-webauthn.test.mjs +144 -0
- package/test/r175-cli-flags.test.mjs +23 -0
- package/test/r175-foreground-boundary.test.mjs +87 -0
- package/test/r175-guardian-owner-boundary.test.mjs +56 -0
- package/test/r176-pretty-live-log.test.mjs +54 -0
- package/test/r177-windows-absolute-live-path.test.mjs +37 -0
- package/test/r179-absolute-path-live-log.test.mjs +40 -0
- package/test/r179-release-contract.test.mjs +62 -0
- package/test/r186-controller-owner-grant.test.mjs +20 -0
- package/test/r186-owner-consent-preflight.test.mjs +98 -0
- package/test/r186-owner-enrollment.test.mjs +20 -0
- package/test/r186-owner-grant-broker.test.mjs +85 -0
- package/test/r186-owner-local-signing.test.mjs +30 -0
- package/test/r186-owner-request.test.mjs +28 -0
- package/test/r186-owner-review-export.test.mjs +52 -0
- package/test/r48-release-contract.test.mjs +2 -1
- package/test/r51-runtime-coherence.test.mjs +4 -2
- package/test/r58-cleanup-retention.test.mjs +5 -12
- package/test/r58-concurrent-recovery.test.mjs +5 -1
- package/test/r58-stale-owner.test.mjs +9 -3
- package/test/r59-tool-surface-compat.test.mjs +1 -1
- package/test/remote-live-log-r37.test.mjs +2 -2
- package/test/remote-live-log-recovery-r36.test.mjs +1 -1
- package/test/remote-session-cli.test.mjs +92 -126
- package/node_modules/which/CHANGELOG.md +0 -166
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {createLiveTimelineTracker,defaultForegroundTimelineOptions,summarizeLiveToolArgs} from '../src/remote-live-log.mjs';
|
|
4
|
+
const base=String.raw`D:\Projects`;
|
|
5
|
+
const sub=String.raw`deadbyte-mcp\development\deadbyte-mcp-0.15.69-r174-owner-consent\src\alpha.mjs`;
|
|
6
|
+
const full=String.raw`D:\Projects\deadbyte-mcp\development\deadbyte-mcp-0.15.69-r174-owner-consent\src\alpha.mjs`;
|
|
7
|
+
const env={rootPaths:{projects:base,core:String.raw`C:\Users\DEADBYTE\.deadbyte-mcp\releases\v0.15.68-ff29dbb2fbb89862`}};
|
|
8
|
+
const tracker=()=>createLiveTimelineTracker({clock:()=>Date.parse('2026-10-27T12:25:56.000Z'),...defaultForegroundTimelineOptions({verbose:false}),...env});
|
|
9
|
+
test('R179 absolute Windows file path visible, with root identity, on START and PASS',()=>{
|
|
10
|
+
const t=tracker();
|
|
11
|
+
const start=t.observe({event_type:'tool.request',request_id:'win-full-path',tool:'coding_file_write',args:{root_id:'projects',path:sub,content:'safe'}} ,{sequence:1});
|
|
12
|
+
assert.match(start,/📁|📂/);assert.ok(start.includes(full),start);
|
|
13
|
+
assert.match(start,/root=projects/);
|
|
14
|
+
const done=t.observe({event_type:'tool.result',request_id:'win-full-path',tool:'coding_file_write',outcome:{status:'ok',duration_ms:13}},{sequence:2});
|
|
15
|
+
assert.ok(done.includes(full),done);assert.match(done,/root=projects/);
|
|
16
|
+
assert.match(done,/PASS/);
|
|
17
|
+
});
|
|
18
|
+
test('R179 Windows PowerShell command script_path uses canonical full path',()=>{
|
|
19
|
+
const t=tracker();
|
|
20
|
+
const start=t.observe({event_type:'tool.request',request_id:'script-path',tool:'coding_command_run',args:{root_id:'projects',command_id:'powershell_script',script_path:sub}},{sequence:1});
|
|
21
|
+
assert.ok(start.includes(full),start);
|
|
22
|
+
assert.match(start,/powershell_script/);
|
|
23
|
+
});
|
|
24
|
+
test('R179 unknown root fails to invent a drive-letter path',()=>{
|
|
25
|
+
const str=summarizeLiveToolArgs('coding_file_read',{root_id:'mystery',path:'src/alpha.mjs'},{rootPaths:env.rootPaths});
|
|
26
|
+
assert.doesNotMatch(str,/[A-Z]:\\/);
|
|
27
|
+
assert.match(str,/mystery/);
|
|
28
|
+
});
|
|
29
|
+
test('R179 path traversal is not represented as an in-root file',()=>{
|
|
30
|
+
const str=summarizeLiveToolArgs('coding_file_read',{root_id:'projects',path:'..\\secret.txt'},{rootPaths:env.rootPaths});
|
|
31
|
+
assert.doesNotMatch(str,/D:\\secret.txt/);
|
|
32
|
+
assert.match(str,/outside|unresolved|denied/i);
|
|
33
|
+
});
|
|
34
|
+
test('R179 log path presentation keeps tool argument secrets redacted',()=>{
|
|
35
|
+
const t=tracker();
|
|
36
|
+
const start=t.observe({event_type:'tool.request',request_id:'secret-path',tool:'coding_file_write',args:{
|
|
37
|
+
root_id:'projects',path:sub,content:'secret-data',private_key:'DO_NOT_SHOW_KEY'
|
|
38
|
+
}},{sequence:1});
|
|
39
|
+
assert.doesNotMatch(start,/secret-data|DO_NOT_SHOW_KEY/);
|
|
40
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
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 {readReleaseLock} from './helpers/release-lock.mjs';
|
|
8
|
+
const root=path.resolve(fileURLToPath(new URL('..',import.meta.url)));
|
|
9
|
+
const read=async file=>JSON.parse(await readFile(path.join(root,file),'utf8'));
|
|
10
|
+
const parent='v0.15.68-ff29dbb2fbb89862';
|
|
11
|
+
const manifest='ff29dbb2fbb898625592afb47835a4a62ef289429f1e0a89a83242398cd98885';
|
|
12
|
+
const generation='2887d4a8d54ea6fc6b28869072f72491c892dfe28178b3537936dce958147bd2';
|
|
13
|
+
const tarball='1a7f1fe05a06178b8d772a140be6e0470790b743cef690c3f8f8165cc61075bf';
|
|
14
|
+
test('R179 version, lockfile, bridge, matrix, SBOM and provenance identify exact 0.15.69 successor',async()=>{
|
|
15
|
+
const [pkg,lock,bridge,release,provenance,matrix,sbom]=await Promise.all([
|
|
16
|
+
read('package.json'),readReleaseLock(root),read('bridge/package.json'),read('RELEASE.json'),
|
|
17
|
+
read('PROVENANCE.json'),read('TEST-MATRIX.json'),read('SBOM.cdx.json')]);
|
|
18
|
+
for(const version of [pkg.version,lock.version,lock.packages[''].version,bridge.version,release.version,provenance.version,
|
|
19
|
+
matrix.version,sbom.metadata.component.version,DEADBYTE_VERSION])assert.equal(version,'0.15.69');
|
|
20
|
+
assert.equal(release.train,'R179');assert.equal(matrix.train,'R179');
|
|
21
|
+
assert.equal(sbom.serialNumber,'urn:cdx:deadbyte-mcp:r179:0.15.69');
|
|
22
|
+
assert.equal(release.parent.release_id,parent);assert.equal(release.parent.manifest_sha256,manifest);
|
|
23
|
+
assert.equal(release.parent.generation_manifest_sha256,generation);
|
|
24
|
+
assert.equal(release.source_predecessor.version,'0.15.68');
|
|
25
|
+
assert.equal(release.source_predecessor.manifest_sha256,manifest);
|
|
26
|
+
assert.equal(release.source_predecessor.tarball_sha256,tarball);
|
|
27
|
+
assert.equal(release.runtime_predecessor.release_id,parent);
|
|
28
|
+
assert.equal(provenance.runtime_predecessor.release_id,parent);
|
|
29
|
+
assert.equal(provenance.parent_tarball_sha256,tarball);
|
|
30
|
+
assert.equal(provenance.source_predecessor_tarball_sha256,tarball);
|
|
31
|
+
});
|
|
32
|
+
test('R179 does not misrepresent owner Autonomous enrollment, protected regression or publication as finished',async()=>{
|
|
33
|
+
const [release,prov,matrix]=await Promise.all(['RELEASE.json','PROVENANCE.json','TEST-MATRIX.json'].map(read));
|
|
34
|
+
assert.equal(release.r179.owner_approved_autonomous_grant,'NOT_ISSUED');
|
|
35
|
+
assert.equal(release.r179.protected_full_regression,'NOT_VERIFIED');
|
|
36
|
+
assert.equal(release.r179.npm_publish,'NOT_RUN');
|
|
37
|
+
assert.equal(release.r179.production_activation,'NOT_RUN');
|
|
38
|
+
assert.equal(release.r179.stable_final_claim,false);
|
|
39
|
+
assert.equal(prov.r179.independent_owner_enrollment,'NOT_PRESENT');
|
|
40
|
+
assert.equal(prov.r179.protected_full_regression,'NOT_RUN');
|
|
41
|
+
assert.equal(matrix.r179.owner_consent,'NOT_VERIFIED');
|
|
42
|
+
assert.equal(matrix.r179.registry_publish,'NOT_RUN');
|
|
43
|
+
assert.ok(matrix.required.some(r=>r.id==='r179-owner-authorized-autonomous'));
|
|
44
|
+
assert.ok(matrix.required.some(r=>r.id==='r179-protected-full'));
|
|
45
|
+
});
|
|
46
|
+
test('R179 retains historical R173 source ancestor evidence without promoting it to current parent',async()=>{
|
|
47
|
+
const [release,prov]=await Promise.all(['RELEASE.json','PROVENANCE.json'].map(read));
|
|
48
|
+
assert.equal(release.r173.source_predecessor_version,'0.15.67');
|
|
49
|
+
assert.equal(release.r173.source_predecessor_manifest_sha256,'4fd7bfc76b1de6401c142b607cc84e847434b28c888bc99ceb09c9e8e5adc8f8');
|
|
50
|
+
assert.equal(prov.r173.source_predecessor_version,'0.15.67');
|
|
51
|
+
assert.equal(release.source_predecessor.version,'0.15.68');
|
|
52
|
+
assert.notEqual(release.source_predecessor.tarball_sha256,release.r173.source_predecessor_tarball_sha256);
|
|
53
|
+
});
|
|
54
|
+
test('R184 ships a semantic lock snapshot matching installed package identity and dependencies',async()=>{
|
|
55
|
+
const [pkg,lock,snapshot]=await Promise.all([
|
|
56
|
+
read('package.json'),readReleaseLock(root),read('docs/R184-package-lock.json')]);
|
|
57
|
+
assert.deepEqual(snapshot,lock);
|
|
58
|
+
assert.equal(snapshot.name,pkg.name);
|
|
59
|
+
assert.equal(snapshot.version,pkg.version);
|
|
60
|
+
assert.equal(snapshot.packages[''].version,pkg.version);
|
|
61
|
+
assert.deepEqual(snapshot.packages[''].dependencies,pkg.dependencies);
|
|
62
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {readFile} from 'node:fs/promises';
|
|
4
|
+
|
|
5
|
+
test('R186 successor Autonomous arm delegates non-legacy issuance to protected owner broker',async()=>{
|
|
6
|
+
const source=await readFile(new URL('../controller/deadbyte-controller.ps1',import.meta.url),'utf8');
|
|
7
|
+
assert.match(source,/function Invoke-R186ProtectedAutonomousGrant/);
|
|
8
|
+
const arm=/function Arm-Autonomous\(\$Cfg\) \{([\s\S]*?)\n\}/.exec(source)?.[1]??'';
|
|
9
|
+
assert.match(arm,/Invoke-R186ProtectedAutonomousGrant/);
|
|
10
|
+
assert.match(arm,/R70_OWNER_ENROLLMENT_REQUIRED/);
|
|
11
|
+
assert.match(arm,/legacyOwnerPolicySha256/,'legacy compatibility path must remain explicit');
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
test('R186 protected broker and consent dependencies are exact-byte pinned by controller',async()=>{
|
|
15
|
+
const source=await readFile(new URL('../controller/deadbyte-controller.ps1',import.meta.url),'utf8');
|
|
16
|
+
for(const file of [
|
|
17
|
+
'scripts\\r186-owner-grant-cli.mjs','src\\r186-owner-grant-broker.mjs',
|
|
18
|
+
'src\\r186-owner-consent-preflight.mjs','src\\r70-owner-approval.mjs'
|
|
19
|
+
])assert.match(source,new RegExp(file.replace(/[.*+?^${}()|[\]\\]/g,'\\$&')));
|
|
20
|
+
});
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {createHash,generateKeyPairSync,sign} from 'node:crypto';
|
|
4
|
+
import {mkdtemp,rm,realpath} from 'node:fs/promises';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import os from 'node:os';
|
|
7
|
+
import {canonicalJson} from '../src/canonical-json.mjs';
|
|
8
|
+
import {verifyR186OwnerConsent,verifyR186PolicyRootBinding} from '../src/r186-owner-consent-preflight.mjs';
|
|
9
|
+
|
|
10
|
+
const sha=b=>createHash('sha256').update(b).digest('hex');
|
|
11
|
+
const domain=Buffer.from('DEADBYTE-R70-PROJECT-OWNER-APPROVAL\0','utf8');
|
|
12
|
+
const H=c=>c.repeat(64);
|
|
13
|
+
async function fixture(){
|
|
14
|
+
const dir=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r186-consent-'));
|
|
15
|
+
const real=await realpath(dir);
|
|
16
|
+
const {privateKey,publicKey}=generateKeyPairSync('ed25519');
|
|
17
|
+
const owner_key_id=sha(publicKey.export({type:'spki',format:'der'}));
|
|
18
|
+
const now=new Date();
|
|
19
|
+
const issued=new Date(now.getTime()-1000).toISOString();
|
|
20
|
+
const expires=new Date(now.getTime()+120000).toISOString();
|
|
21
|
+
const expected={rootId:'core',projectRoot:real,codingPolicySha256:H('a'),
|
|
22
|
+
autonomousPolicySha256:H('b'),reviewedArtifactSha256:H('c')};
|
|
23
|
+
const subject={schema:'deadbyte.project-authority-subject.r70.v1',scope:'coding_autonomous_native_diff',
|
|
24
|
+
root_id:expected.rootId,project_root:real,coding_policy_sha256:expected.codingPolicySha256,
|
|
25
|
+
autonomous_policy_sha256:expected.autonomousPolicySha256,reviewed_artifact_sha256:expected.reviewedArtifactSha256,
|
|
26
|
+
issued_at_utc:issued,expires_at_utc:expires};
|
|
27
|
+
const signed={schema:'deadbyte.project-owner-approval.r70.v1',subject_sha256:sha(Buffer.from(canonicalJson(subject))),owner_key_id};
|
|
28
|
+
const approval={...signed,subject,signature_base64:sign(null,Buffer.concat([domain,Buffer.from(canonicalJson(signed))]),privateKey).toString('base64')};
|
|
29
|
+
const trust={schema:'deadbyte.r186-owner-anchor.v1',owner_key_id};
|
|
30
|
+
return {dir,publicKeyPem:publicKey.export({type:'spki',format:'pem'}),expected,approval,trust,now};
|
|
31
|
+
}
|
|
32
|
+
test('R186 independently pinned Ed25519 owner anchor validates the reviewed artifact and produces no grant',async()=>{
|
|
33
|
+
const f=await fixture();try{
|
|
34
|
+
const d=await verifyR186OwnerConsent({ownerTrust:f.trust,ownerPublicKeyPem:f.publicKeyPem,
|
|
35
|
+
approval:f.approval,expected:f.expected,now:f.now});
|
|
36
|
+
assert.equal(d.status,'verified_readonly');
|
|
37
|
+
assert.equal(d.authority_granted,false);
|
|
38
|
+
assert.equal(d.root_id,'core');
|
|
39
|
+
assert.equal(d.owner_key_id,f.trust.owner_key_id);
|
|
40
|
+
}finally{await rm(f.dir,{recursive:true,force:true});}
|
|
41
|
+
});
|
|
42
|
+
test('R186 cannot self-enroll an alternate key or swap in a signer-provided key anchor',async()=>{
|
|
43
|
+
const f=await fixture();try{
|
|
44
|
+
const rogue=generateKeyPairSync('ed25519');
|
|
45
|
+
await assert.rejects(verifyR186OwnerConsent({ownerTrust:f.trust,
|
|
46
|
+
ownerPublicKeyPem:rogue.publicKey.export({type:'spki',format:'pem'}),
|
|
47
|
+
approval:f.approval,expected:f.expected,now:f.now}),/owner anchor|owner key/i);
|
|
48
|
+
await assert.rejects(verifyR186OwnerConsent({ownerTrust:{...f.trust,override:true},
|
|
49
|
+
ownerPublicKeyPem:f.publicKeyPem,approval:f.approval,expected:f.expected,now:f.now}),/owner anchor/i);
|
|
50
|
+
}finally{await rm(f.dir,{recursive:true,force:true});}
|
|
51
|
+
});
|
|
52
|
+
test('R186 fail closed on artifact, policy, root or expiry drift without granting authority',async()=>{
|
|
53
|
+
const f=await fixture();try{
|
|
54
|
+
for(const changed of [
|
|
55
|
+
{reviewedArtifactSha256:H('d')},{codingPolicySha256:H('e')},
|
|
56
|
+
{autonomousPolicySha256:H('f')},{rootId:'projects'}
|
|
57
|
+
]){
|
|
58
|
+
await assert.rejects(verifyR186OwnerConsent({ownerTrust:f.trust,
|
|
59
|
+
ownerPublicKeyPem:f.publicKeyPem,approval:f.approval,
|
|
60
|
+
expected:{...f.expected,...changed},now:f.now}),/owner approval/i);
|
|
61
|
+
}
|
|
62
|
+
await assert.rejects(verifyR186OwnerConsent({ownerTrust:f.trust,
|
|
63
|
+
ownerPublicKeyPem:f.publicKeyPem,approval:f.approval,
|
|
64
|
+
expected:f.expected,now:new Date(f.now.getTime()+180000)}),/expired/i);
|
|
65
|
+
}finally{await rm(f.dir,{recursive:true,force:true});}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('R186 CLI has no usable enrollment or grant path from writable source',async()=>{
|
|
69
|
+
const {spawnSync}=await import('node:child_process');
|
|
70
|
+
const {fileURLToPath}=await import('node:url');
|
|
71
|
+
const result=spawnSync(process.execPath,[fileURLToPath(new URL('../scripts/r186-owner-consent-check.mjs',import.meta.url)),
|
|
72
|
+
'C:\\approval.json','core','D:\\Projects\\deadbyte-mcp','C:\\coding.json','C:\\autonomous.json'],{
|
|
73
|
+
cwd:process.cwd(),encoding:'utf8',windowsHide:true,timeout:18000
|
|
74
|
+
});
|
|
75
|
+
// A read-only preview is not a privileged grant, including on CLI failure.
|
|
76
|
+
assert.equal(result.status,2,'R186 CLI must execute and reject, not fail to resolve its module');
|
|
77
|
+
assert.match(String(result.stderr),/R186_OWNER_PREFLIGHT_BLOCKED/);
|
|
78
|
+
assert.doesNotMatch(String(result.stderr),/ERR_MODULE_NOT_FOUND/);
|
|
79
|
+
assert.doesNotMatch(String(result.stdout),/authority_granted":true/);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('R186 validates actual Coding root map and Autonomous root vector before owner approval',()=>{
|
|
83
|
+
const root='D:\\Projects\\deadbyte-mcp';
|
|
84
|
+
const coding={schema:'deadbyte.coding-policy.v1',enabled:true,
|
|
85
|
+
roots:{core:{path:root,read:true,write:true,exec:true}}};
|
|
86
|
+
const autonomous={schema:'deadbyte.autonomous-policy.v1',enabled:true,
|
|
87
|
+
root_ids:['core'],coding_policy_sha256:H('a')};
|
|
88
|
+
const input={codingPolicy:coding,autonomousPolicy:autonomous,rootId:'core',projectRoot:root,
|
|
89
|
+
codingPolicySha256:H('a')};
|
|
90
|
+
assert.equal(verifyR186PolicyRootBinding(input).root_id,'core');
|
|
91
|
+
assert.throws(()=>verifyR186PolicyRootBinding({...input,projectRoot:'D:\\Other'}),/R186/i);
|
|
92
|
+
assert.throws(()=>verifyR186PolicyRootBinding({...input,
|
|
93
|
+
autonomousPolicy:{...autonomous,root_ids:['core','projects']}}),/R186/i);
|
|
94
|
+
assert.throws(()=>verifyR186PolicyRootBinding({...input,
|
|
95
|
+
autonomousPolicy:{...autonomous,coding_policy_sha256:H('b')}}),/R186/i);
|
|
96
|
+
assert.throws(()=>verifyR186PolicyRootBinding({...input,
|
|
97
|
+
codingPolicy:{...coding,roots:{core:{...coding.roots.core,path:'C:\\'}}}}),/R186/i);
|
|
98
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {generateKeyPairSync} from 'node:crypto';
|
|
4
|
+
import {createR186OwnerAnchor} from '../src/r186-owner-anchor.mjs';
|
|
5
|
+
|
|
6
|
+
test('R186 enrollment material contains only an Ed25519 public identity',()=>{
|
|
7
|
+
const pair=generateKeyPairSync('ed25519');
|
|
8
|
+
const out=createR186OwnerAnchor(pair.publicKey.export({type:'spki',format:'pem'}));
|
|
9
|
+
assert.equal(out.anchor.schema,'deadbyte.r186-owner-anchor.v1');
|
|
10
|
+
assert.match(out.anchor.owner_key_id,/^[0-9a-f]{64}$/);
|
|
11
|
+
assert.match(out.public_key_pem,/BEGIN PUBLIC KEY/);
|
|
12
|
+
assert.doesNotMatch(JSON.stringify(out),/PRIVATE KEY/);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test('R186 enrollment rejects non-Ed25519 and private-key input',()=>{
|
|
16
|
+
const rsa=generateKeyPairSync('rsa',{modulusLength:2048});
|
|
17
|
+
assert.throws(()=>createR186OwnerAnchor(rsa.publicKey.export({type:'spki',format:'pem'})),/Ed25519/i);
|
|
18
|
+
const ed=generateKeyPairSync('ed25519');
|
|
19
|
+
assert.throws(()=>createR186OwnerAnchor(ed.privateKey.export({type:'pkcs8',format:'pem'})),/public/i);
|
|
20
|
+
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {createHash,generateKeyPairSync,sign} from 'node:crypto';
|
|
4
|
+
import {mkdtemp,mkdir,readFile,rm,writeFile} from 'node:fs/promises';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import os from 'node:os';
|
|
7
|
+
import {canonicalJson} from '../src/canonical-json.mjs';
|
|
8
|
+
import {issueR186AutonomousGrant} from '../src/r186-owner-grant-broker.mjs';
|
|
9
|
+
|
|
10
|
+
const sha=b=>createHash('sha256').update(b).digest('hex');
|
|
11
|
+
const domain=Buffer.from('DEADBYTE-R70-PROJECT-OWNER-APPROVAL\0','utf8');
|
|
12
|
+
async function fixture(){
|
|
13
|
+
const state=await mkdtemp(path.join(os.tmpdir(),'deadbyte-r186-broker-'));
|
|
14
|
+
const project=path.join(state,'project');await mkdir(project);
|
|
15
|
+
const grantFile=path.join(state,'runtime','autonomous-grant.json');
|
|
16
|
+
const codingPolicy={schema:'deadbyte.coding-policy.v1',enabled:true,
|
|
17
|
+
roots:{core:{path:project,read:true,write:true,exec:true,deny_paths:[]}}};
|
|
18
|
+
const codingBytes=Buffer.from(JSON.stringify(codingPolicy));
|
|
19
|
+
const autonomousPolicy={schema:'deadbyte.autonomous-policy.v1',enabled:true,
|
|
20
|
+
lease_file:grantFile,evidence_dir:path.join(state,'evidence'),coding_policy_sha256:sha(codingBytes),
|
|
21
|
+
root_ids:['core'],planner:{},limits:{},profiles:{}};
|
|
22
|
+
const autonomousBytes=Buffer.from(JSON.stringify(autonomousPolicy));
|
|
23
|
+
const manifestBytes=Buffer.from('manifest fixture');
|
|
24
|
+
const {privateKey,publicKey}=generateKeyPairSync('ed25519');
|
|
25
|
+
const machine=generateKeyPairSync('ed25519');
|
|
26
|
+
await mkdir(path.join(state,'trust'));
|
|
27
|
+
await writeFile(path.join(state,'trust','ed25519-private.pem'),machine.privateKey.export({type:'pkcs8',format:'pem'}));
|
|
28
|
+
await writeFile(path.join(state,'trust','ed25519-public.pem'),machine.publicKey.export({type:'spki',format:'pem'}));
|
|
29
|
+
const owner_key_id=sha(publicKey.export({type:'spki',format:'der'}));
|
|
30
|
+
const now=new Date('2026-10-28T00:00:00.000Z');
|
|
31
|
+
const subject={schema:'deadbyte.project-authority-subject.r70.v1',scope:'coding_autonomous_native_diff',
|
|
32
|
+
root_id:'core',project_root:project,coding_policy_sha256:sha(codingBytes),
|
|
33
|
+
autonomous_policy_sha256:sha(autonomousBytes),reviewed_artifact_sha256:sha(manifestBytes),
|
|
34
|
+
issued_at_utc:now.toISOString(),expires_at_utc:new Date(now.getTime()+300000).toISOString()};
|
|
35
|
+
const subject_sha256=sha(Buffer.from(canonicalJson(subject)));
|
|
36
|
+
const signed={schema:'deadbyte.project-owner-approval.r70.v1',subject_sha256,owner_key_id};
|
|
37
|
+
const approval={...signed,subject,signature_base64:sign(null,Buffer.concat([domain,Buffer.from(canonicalJson(signed))]),privateKey).toString('base64')};
|
|
38
|
+
const trust={schema:'deadbyte.r186-owner-anchor.v1',owner_key_id};
|
|
39
|
+
const publicPem=publicKey.export({type:'spki',format:'pem'});
|
|
40
|
+
const epoch={head:null,calls:0,async readHead(){return this.head;},async append({grantBytes,grantInstanceNonce}){
|
|
41
|
+
this.calls++;this.head={seq:1,action:'grant',event_sha256:'e'.repeat(64),grant_sha256:sha(grantBytes),grant_instance_nonce:grantInstanceNonce};return this.head;
|
|
42
|
+
},async verifyGrant({grantBytes,grantInstanceNonce}){return {trusted:this.head?.grant_sha256===sha(grantBytes)&&this.head?.grant_instance_nonce===grantInstanceNonce,reason:'epoch-bound-standing-grant',event_sha256:this.head?.event_sha256,seq:1,action:'grant'};}};
|
|
43
|
+
return {state,project,grantFile,codingPolicy,codingBytes,autonomousPolicy,autonomousBytes,manifestBytes,
|
|
44
|
+
trust,publicPem,approval,now,epoch};
|
|
45
|
+
}
|
|
46
|
+
const run=(f,extra={})=>issueR186AutonomousGrant({runtimeDir:path.join(f.state,'runtime'),grantFile:f.grantFile,
|
|
47
|
+
codingPolicy:f.codingPolicy,codingPolicyBytes:f.codingBytes,autonomousPolicy:f.autonomousPolicy,
|
|
48
|
+
autonomousPolicyBytes:f.autonomousBytes,releaseManifestBytes:f.manifestBytes,ownerTrust:f.trust,
|
|
49
|
+
ownerPublicKeyPem:f.publicPem,approval:f.approval,now:f.now,epochStore:f.epoch,...extra});
|
|
50
|
+
|
|
51
|
+
test('R186 broker consumes exact owner approval once and verifies grant plus signed epoch',async()=>{
|
|
52
|
+
const f=await fixture();try{const out=await run(f);
|
|
53
|
+
assert.equal(out.status,'GRANTED');assert.equal(out.authority_granted,true);assert.equal(f.epoch.calls,1);
|
|
54
|
+
const grant=JSON.parse(await readFile(f.grantFile,'utf8'));
|
|
55
|
+
assert.deepEqual(grant.root_ids,['core']);assert.equal(grant.policy_sha256,sha(f.autonomousBytes));
|
|
56
|
+
assert.equal(out.epoch_event_sha256,'e'.repeat(64));
|
|
57
|
+
const terminal=JSON.parse(await readFile(out.terminal_path,'utf8'));
|
|
58
|
+
assert.equal(terminal.attestation.domain,'DEADBYTE-R186-OWNER-GRANT-TERMINAL');
|
|
59
|
+
assert.match(terminal.receipt_sha256,/^[0-9a-f]{64}$/);
|
|
60
|
+
await assert.rejects(run(f),/already consumed|replay/i);assert.equal(f.epoch.calls,1);
|
|
61
|
+
}finally{await rm(f.state,{recursive:true,force:true});}
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('R186 broker fails closed before mutation on missing enrollment, drift and expiry',async()=>{
|
|
65
|
+
const f=await fixture();try{
|
|
66
|
+
for(const override of [
|
|
67
|
+
{ownerTrust:null},
|
|
68
|
+
{releaseManifestBytes:Buffer.from('changed')},
|
|
69
|
+
{now:new Date(f.now.getTime()+600000)}
|
|
70
|
+
]){
|
|
71
|
+
const isolated=await fixture();try{await assert.rejects(run(isolated,override),/R186|owner|artifact|expired|anchor/i);
|
|
72
|
+
await assert.rejects(readFile(isolated.grantFile),{code:'ENOENT'});assert.equal(isolated.epoch.calls,0);
|
|
73
|
+
}finally{await rm(isolated.state,{recursive:true,force:true});}
|
|
74
|
+
}
|
|
75
|
+
}finally{await rm(f.state,{recursive:true,force:true});}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('R186 concurrent broker attempts produce one grant and one epoch mutation',async()=>{
|
|
79
|
+
const f=await fixture();try{
|
|
80
|
+
const results=await Promise.allSettled([run(f),run(f)]);
|
|
81
|
+
assert.equal(results.filter(x=>x.status==='fulfilled').length,1);
|
|
82
|
+
assert.equal(results.filter(x=>x.status==='rejected').length,1);
|
|
83
|
+
assert.equal(f.epoch.calls,1);
|
|
84
|
+
}finally{await rm(f.state,{recursive:true,force:true});}
|
|
85
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {createHash,generateKeyPairSync} from 'node:crypto';
|
|
4
|
+
import {canonicalJson} from '../src/canonical-json.mjs';
|
|
5
|
+
import {createR186OwnerApproval} from '../src/r186-owner-local-signing.mjs';
|
|
6
|
+
import {verifyR186OwnerConsent} from '../src/r186-owner-consent-preflight.mjs';
|
|
7
|
+
|
|
8
|
+
test('R186 local owner signer signs only an exact review request and verifies with public identity',async()=>{
|
|
9
|
+
const pair=generateKeyPairSync('ed25519');
|
|
10
|
+
const now=new Date();
|
|
11
|
+
const subject={schema:'deadbyte.project-authority-subject.r70.v1',scope:'coding_autonomous_native_diff',root_id:'core',
|
|
12
|
+
project_root:process.cwd(),coding_policy_sha256:'a'.repeat(64),autonomous_policy_sha256:'b'.repeat(64),
|
|
13
|
+
reviewed_artifact_sha256:'c'.repeat(64),issued_at_utc:now.toISOString(),expires_at_utc:new Date(now.getTime()+300000).toISOString()};
|
|
14
|
+
const request={schema:'deadbyte.r186-owner-review-request.v1',subject,
|
|
15
|
+
subject_sha256:createHash('sha256').update(Buffer.from(canonicalJson(subject))).digest('hex'),owner_signature_present:false,authority_granted:false};
|
|
16
|
+
const privatePem=pair.privateKey.export({type:'pkcs8',format:'pem'});
|
|
17
|
+
const approval=createR186OwnerApproval({request,privateKeyPem:privatePem});
|
|
18
|
+
const publicPem=pair.publicKey.export({type:'spki',format:'pem'});
|
|
19
|
+
const keyId=approval.owner_key_id;
|
|
20
|
+
const checked=await verifyR186OwnerConsent({ownerTrust:{schema:'deadbyte.r186-owner-anchor.v1',owner_key_id:keyId},
|
|
21
|
+
ownerPublicKeyPem:publicPem,approval,now,expected:{rootId:'core',projectRoot:process.cwd(),
|
|
22
|
+
codingPolicySha256:'a'.repeat(64),autonomousPolicySha256:'b'.repeat(64),reviewedArtifactSha256:'c'.repeat(64)}});
|
|
23
|
+
assert.equal(checked.subject_sha256,approval.subject_sha256);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('R186 local owner signer rejects private-key material embedded in request',()=>{
|
|
27
|
+
const pair=generateKeyPairSync('ed25519');
|
|
28
|
+
assert.throws(()=>createR186OwnerApproval({request:{private_key:'forbidden'},
|
|
29
|
+
privateKeyPem:pair.privateKey.export({type:'pkcs8',format:'pem'})}),/request/i);
|
|
30
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {createR186OwnerReviewRequest} from '../src/r186-owner-request.mjs';
|
|
4
|
+
const H=c=>c.repeat(64);
|
|
5
|
+
const parameters={rootId:'core',projectRoot:'D:\\Projects\\deadbyte-mcp',
|
|
6
|
+
codingPolicySha256:H('a'),autonomousPolicySha256:H('b'),reviewedArtifactSha256:H('c'),
|
|
7
|
+
now:new Date('2026-10-28T01:00:00.000Z'),durationMs:600000};
|
|
8
|
+
test('R186 request previews a bounded exact R70 subject, never an approval or grant',()=>{
|
|
9
|
+
const request=createR186OwnerReviewRequest(parameters);
|
|
10
|
+
assert.equal(request.schema,'deadbyte.r186-owner-review-request.v1');
|
|
11
|
+
assert.equal(request.authority_granted,false);
|
|
12
|
+
assert.equal(request.owner_signature_present,false);
|
|
13
|
+
assert.equal(request.subject.schema,'deadbyte.project-authority-subject.r70.v1');
|
|
14
|
+
assert.equal(request.subject.expires_at_utc,'2026-10-28T01:10:00.000Z');
|
|
15
|
+
assert.equal(request.subject.coding_policy_sha256,H('a'));
|
|
16
|
+
assert.equal(request.subject.autonomous_policy_sha256,H('b'));
|
|
17
|
+
assert.equal(request.subject.reviewed_artifact_sha256,H('c'));
|
|
18
|
+
assert.match(request.subject_sha256,/^[0-9a-f]{64}$/);
|
|
19
|
+
});
|
|
20
|
+
test('R186 request rejects excessive duration, bad roots and fabricated policy digests',()=>{
|
|
21
|
+
for(const changed of [
|
|
22
|
+
{durationMs:0},{durationMs:3600001},{durationMs:-1},
|
|
23
|
+
{rootId:'../core'},{projectRoot:'relative'},
|
|
24
|
+
{codingPolicySha256:H('x')},
|
|
25
|
+
{autonomousPolicySha256:'0'.repeat(63)}]){
|
|
26
|
+
assert.throws(()=>createR186OwnerReviewRequest({...parameters,...changed}),/R186/);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {mkdtemp,readFile,symlink,writeFile} from 'node:fs/promises';
|
|
4
|
+
import {tmpdir} from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import {createHash} from 'node:crypto';
|
|
7
|
+
import {writeR186OwnerReviewRequest} from '../src/r186-owner-review-export.mjs';
|
|
8
|
+
|
|
9
|
+
const sha=bytes=>createHash('sha256').update(bytes).digest('hex');
|
|
10
|
+
|
|
11
|
+
async function fixture(){
|
|
12
|
+
const root=await mkdtemp(path.join(tmpdir(),'deadbyte-r186-review-'));
|
|
13
|
+
const project='D:\\Projects\\deadbyte-mcp';
|
|
14
|
+
const codingPath=path.join(root,'coding.json');
|
|
15
|
+
const autonomousPath=path.join(root,'autonomous.json');
|
|
16
|
+
const manifestPath=path.join(root,'MANIFEST.SHA256');
|
|
17
|
+
const outputPath=path.join(root,'review-request.json');
|
|
18
|
+
const codingBytes=Buffer.from(JSON.stringify({schema:'deadbyte.coding-policy.v1',enabled:true,
|
|
19
|
+
roots:{core:{path:project,read:true,write:true,exec:false}}}));
|
|
20
|
+
await writeFile(codingPath,codingBytes);
|
|
21
|
+
await writeFile(autonomousPath,JSON.stringify({schema:'deadbyte.autonomous-policy.v1',enabled:true,
|
|
22
|
+
root_ids:['core'],coding_policy_sha256:sha(codingBytes),lease_file:path.join(root,'grant.json')}));
|
|
23
|
+
await writeFile(manifestPath,'a'.repeat(64)+' package.json\n');
|
|
24
|
+
return {root,projectRoot:project,codingPolicyPath:codingPath,
|
|
25
|
+
autonomousPolicyPath:autonomousPath,manifestPath,outputPath};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
test('R186 owner review export writes one exact bounded request without granting authority',async()=>{
|
|
29
|
+
const f=await fixture();
|
|
30
|
+
const result=await writeR186OwnerReviewRequest({...f,rootId:'core',
|
|
31
|
+
now:new Date('2026-10-28T04:00:00.000Z'),durationMs:300000});
|
|
32
|
+
const saved=JSON.parse(await readFile(f.outputPath,'utf8'));
|
|
33
|
+
assert.equal(saved.subject_sha256,result.subject_sha256);
|
|
34
|
+
assert.equal(saved.subject.project_root,f.projectRoot);
|
|
35
|
+
assert.equal(saved.subject.expires_at_utc,'2026-10-28T04:05:00.000Z');
|
|
36
|
+
assert.equal(saved.authority_granted,false);
|
|
37
|
+
assert.equal(saved.owner_signature_present,false);
|
|
38
|
+
await assert.rejects(()=>writeR186OwnerReviewRequest({...f,rootId:'core'}),/exists|EEXIST/i);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('R186 owner review export rejects policy drift and reparse inputs',async()=>{
|
|
42
|
+
const f=await fixture();
|
|
43
|
+
const autonomous=JSON.parse(await readFile(f.autonomousPolicyPath,'utf8'));
|
|
44
|
+
autonomous.coding_policy_sha256='b'.repeat(64);
|
|
45
|
+
await writeFile(f.autonomousPolicyPath,JSON.stringify(autonomous));
|
|
46
|
+
await assert.rejects(()=>writeR186OwnerReviewRequest({...f,rootId:'core'}),/digest binding/i);
|
|
47
|
+
|
|
48
|
+
const clean=await fixture();
|
|
49
|
+
const link=path.join(path.dirname(clean.root),'coding-policy-junction-'+path.basename(clean.root));
|
|
50
|
+
await symlink(clean.root,link,'junction');
|
|
51
|
+
await assert.rejects(()=>writeR186OwnerReviewRequest({...clean,rootId:'core',codingPolicyPath:link}),/reparse|symlink/i);
|
|
52
|
+
});
|
|
@@ -2,6 +2,7 @@ import test from 'node:test';
|
|
|
2
2
|
import assert from 'node:assert/strict';
|
|
3
3
|
import { readFile } from 'node:fs/promises';
|
|
4
4
|
import { DEADBYTE_VERSION } from '../src/version.mjs';
|
|
5
|
+
import { readReleaseLock } from './helpers/release-lock.mjs';
|
|
5
6
|
|
|
6
7
|
const root = new URL('../', import.meta.url);
|
|
7
8
|
const read = rel => readFile(new URL(rel, root), 'utf8');
|
|
@@ -9,7 +10,7 @@ const json = rel => read(rel).then(raw => JSON.parse(raw.replace(/^\uFEFF/,'')))
|
|
|
9
10
|
|
|
10
11
|
test('R48 compatibility release contract remains coherent in the current successor', async () => {
|
|
11
12
|
const [pkg, lock, release, provenance, matrix] = await Promise.all([
|
|
12
|
-
json('package.json'),
|
|
13
|
+
json('package.json'), readReleaseLock(new URL('.', root)), json('RELEASE.json'),
|
|
13
14
|
json('PROVENANCE.json'), json('TEST-MATRIX.json')
|
|
14
15
|
]);
|
|
15
16
|
|
|
@@ -50,10 +50,12 @@ test('R51 bare remote wires coherence before live generation binding and deploy
|
|
|
50
50
|
});
|
|
51
51
|
|
|
52
52
|
|
|
53
|
-
test('
|
|
53
|
+
test('R175 observation-only foreground retains legacy cleanup only for legacy markers, never owned grants',async()=>{
|
|
54
54
|
const {readFile}=await import('node:fs/promises');
|
|
55
55
|
const cli=await readFile(new URL('../src/deadbyte-cli.mjs',import.meta.url),'utf8');
|
|
56
|
-
assert.match(cli,/
|
|
56
|
+
assert.match(cli,/if\(marker!==null\)/);
|
|
57
|
+
assert.match(cli,/R175_INVALID_OBSERVE_ONLY_MARKER/);
|
|
58
|
+
assert.match(cli,/assertRuntimeReadyObservation\(observedAuthority/);
|
|
57
59
|
assert.match(cli,/SESSION_CLEANUP=.*disarmdesktop.*disarmhost/s);
|
|
58
60
|
assert.match(cli,/authorityBanner\(authority\)/);
|
|
59
61
|
});
|
|
@@ -6,7 +6,7 @@ import path from 'node:path';
|
|
|
6
6
|
import { spawn } from 'node:child_process';
|
|
7
7
|
import { setTimeout as delay } from 'node:timers/promises';
|
|
8
8
|
|
|
9
|
-
test('
|
|
9
|
+
test('R175 elevated foreground rejection cannot create cleanup ownership evidence', {timeout:60000}, async () => {
|
|
10
10
|
const base='D:/Projects/deadbyte-mcp/testing/0.15.35/fixtures';
|
|
11
11
|
await mkdir(base,{recursive:true});
|
|
12
12
|
const root=await mkdtemp(path.join(base,'cleanup-'));
|
|
@@ -34,18 +34,11 @@ exit 0
|
|
|
34
34
|
child.stdout.on('data',b=>{stdout+=b;});child.stderr.on('data',b=>{stderr+=b;});
|
|
35
35
|
const closed=new Promise(resolve=>child.once('close',code=>resolve(code)));
|
|
36
36
|
try {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
assert.
|
|
40
|
-
assert.match(stdout,/\[AUTH\] process ARMED \| coding ARMED \| autonomous ARMED \| desktop DISARMED \| host DISARMED/,stderr);
|
|
41
|
-
child.stdin.end();
|
|
42
|
-
assert.notEqual(await closed,0,'cleanup failure must not report a successful foreground exit');
|
|
43
|
-
assert.match(stderr,/cleanup disarmprocess exit=9/);
|
|
44
|
-
const marker=JSON.parse(await readFile(path.join(root,'runtime','remote-session.json'),'utf8'));
|
|
45
|
-
assert.equal(marker.owner_pid,child.pid);
|
|
46
|
-
assert.match(marker.session_nonce,/^[0-9a-f]{64}$/);
|
|
37
|
+
assert.notEqual(await closed,0,'elevated foreground must fail closed');
|
|
38
|
+
assert.match(stderr,/R175_FOREGROUND_OWNER_BOUNDARY/);
|
|
39
|
+
await assert.rejects(()=>readFile(path.join(root,'runtime','remote-session.json'),'utf8'),{code:'ENOENT'});
|
|
47
40
|
} finally {
|
|
48
41
|
if(child.exitCode===null) child.kill();
|
|
49
|
-
//
|
|
42
|
+
// Rejection occurs before a session claim or controller mutation.
|
|
50
43
|
}
|
|
51
44
|
});
|
|
@@ -5,6 +5,9 @@ import path from 'node:path';
|
|
|
5
5
|
import {fileURLToPath} from 'node:url';
|
|
6
6
|
import {spawn} from 'node:child_process';
|
|
7
7
|
import {setTimeout as delay} from 'node:timers/promises';
|
|
8
|
+
import {installExactActivePointer} from './helpers/r175-active-pointer.mjs';
|
|
9
|
+
|
|
10
|
+
const packageRoot=path.resolve(fileURLToPath(new URL('..',import.meta.url)));
|
|
8
11
|
|
|
9
12
|
test('R75 simultaneous owner-unverified recovery attempts both retain authority and marker', {timeout:30000},async()=>{
|
|
10
13
|
const base='D:/Projects/deadbyte-mcp/testing/0.15.35/fixtures';await mkdir(base,{recursive:true});
|
|
@@ -41,6 +44,7 @@ switch($Command){
|
|
|
41
44
|
}
|
|
42
45
|
exit 0
|
|
43
46
|
`);
|
|
47
|
+
await installExactActivePointer({stateRoot:root,packageRoot,launcherPath:path.join(root,'bin','DEADBYTE-MCP.ps1')});
|
|
44
48
|
const children=[],outputs=[];
|
|
45
49
|
try{
|
|
46
50
|
for(let i=0;i<2;i++){
|
|
@@ -52,7 +56,7 @@ exit 0
|
|
|
52
56
|
const commands=await readFile(trace,'utf8').catch(error=>error.code==='ENOENT'?'':Promise.reject(error));
|
|
53
57
|
await writeFile(path.join(root,'observation.json'),JSON.stringify({commands,exits,outputs:outputs.map(o=>({stdout:o.stdout,stderr:o.stderr})),simulation:'concurrent owner-unverified stale marker'}),{flag:'wx'});
|
|
54
58
|
assert.ok(exits.every(code=>code!==0));
|
|
55
|
-
for(const output of outputs)assert.match(output.stderr,/
|
|
59
|
+
for(const output of outputs)assert.match(output.stderr,/R179_LEGACY_FOREGROUND_RECOVERY_REQUIRES_EXPLICIT_OWNER_COMMAND|already claimed \(startup\/recovery lock\)/i);
|
|
56
60
|
assert.equal(commands,'','neither attempt may touch authority');
|
|
57
61
|
assert.equal(JSON.parse(await readFile(markerPath,'utf8')).pid,2147483000);
|
|
58
62
|
}finally{
|
|
@@ -5,12 +5,16 @@ import path from 'node:path';
|
|
|
5
5
|
import {fileURLToPath} from 'node:url';
|
|
6
6
|
import {spawn} from 'node:child_process';
|
|
7
7
|
import {queryForegroundIdentity} from '../src/foreground-process-identity.mjs';
|
|
8
|
+
import {installExactActivePointer} from './helpers/r175-active-pointer.mjs';
|
|
9
|
+
|
|
10
|
+
const packageRoot=path.resolve(fileURLToPath(new URL('..',import.meta.url)));
|
|
8
11
|
|
|
9
12
|
test('R58 inconsistent owner PID binding cannot trigger stale-session authority cleanup', {timeout:20000}, async()=>{
|
|
10
13
|
const base='D:/Projects/deadbyte-mcp/testing/0.15.35/fixtures';await mkdir(base,{recursive:true});
|
|
11
14
|
const root=await mkdtemp(path.join(base,'owner-binding-'));await mkdir(path.join(root,'runtime'));await mkdir(path.join(root,'bin'));
|
|
12
15
|
const trace=path.join(root,'trace.txt');
|
|
13
16
|
await writeFile(path.join(root,'bin','DEADBYTE-MCP.ps1'),`param([string]$Command)\n[IO.File]::AppendAllText('${trace.replaceAll("'","''")}',$Command+[Environment]::NewLine)\nexit 0\n`);
|
|
17
|
+
await installExactActivePointer({stateRoot:root,packageRoot,launcherPath:path.join(root,'bin','DEADBYTE-MCP.ps1')});
|
|
14
18
|
const identity=queryForegroundIdentity(process.pid);
|
|
15
19
|
const marker={schema:'deadbyte.remote-session.v2',owner_pid:process.pid,guardian_pid:0,session_nonce:'c'.repeat(64),owner_identity:{...identity,pid:process.pid+1}};
|
|
16
20
|
const markerPath=path.join(root,'runtime','remote-session.json');const bytes=JSON.stringify(marker);await writeFile(markerPath,bytes);
|
|
@@ -18,7 +22,7 @@ test('R58 inconsistent owner PID binding cannot trigger stale-session authority
|
|
|
18
22
|
let stderr='';child.stderr.on('data',b=>{stderr+=b;});child.stdout.resume();
|
|
19
23
|
const code=await new Promise(resolve=>child.once('close',resolve));
|
|
20
24
|
await writeFile(path.join(root,'observation.json'),JSON.stringify({code,stderr}),{flag:'wx'});
|
|
21
|
-
assert.notEqual(code,0);assert.match(stderr,/
|
|
25
|
+
assert.notEqual(code,0);assert.match(stderr,/R179_LEGACY_FOREGROUND_RECOVERY_REQUIRES_EXPLICIT_OWNER_COMMAND/i);
|
|
22
26
|
assert.equal(await readFile(trace,'utf8').catch(e=>e.code==='ENOENT'?'':Promise.reject(e)),'','inconsistent marker must cause no controller actions');
|
|
23
27
|
assert.equal(await readFile(markerPath,'utf8'),bytes,'retain ambiguous ownership evidence');
|
|
24
28
|
});
|
|
@@ -29,6 +33,7 @@ test('R58 live unrelated PID is not accepted as a remote foreground owner', {tim
|
|
|
29
33
|
await mkdir(path.join(root,'runtime'));await mkdir(path.join(root,'bin'));
|
|
30
34
|
const trace=path.join(root,'trace.txt');
|
|
31
35
|
await writeFile(path.join(root,'bin','DEADBYTE-MCP.ps1'),`param([string]$Command)\n[IO.File]::AppendAllText('${trace.replaceAll("'","''")}',$Command+[Environment]::NewLine)\nexit 0\n`);
|
|
36
|
+
await installExactActivePointer({stateRoot:root,packageRoot,launcherPath:path.join(root,'bin','DEADBYTE-MCP.ps1')});
|
|
32
37
|
// The test runner is alive but neither a remote CLI nor a guardian. This
|
|
33
38
|
// represents a stale numeric PID now occupied by an unrelated process.
|
|
34
39
|
const marker={schema:'deadbyte.remote-session.v2',owner_pid:process.pid,guardian_pid:process.pid,session_nonce:'a'.repeat(64),started_at_utc:'2000-01-01T00:00:00.000Z'};
|
|
@@ -41,7 +46,7 @@ test('R58 live unrelated PID is not accepted as a remote foreground owner', {tim
|
|
|
41
46
|
// Fail-closed ambiguity is acceptable; do not kill the unrelated process or
|
|
42
47
|
// mutate authority based on unverified ownership. Automatic convergence still
|
|
43
48
|
// requires separately observed exact owner/guardian identity evidence.
|
|
44
|
-
assert.match(stderr,/
|
|
49
|
+
assert.match(stderr,/R179_LEGACY_FOREGROUND_RECOVERY_REQUIRES_EXPLICIT_OWNER_COMMAND/i);
|
|
45
50
|
assert.deepEqual(JSON.parse(await readFile(path.join(root,'runtime','remote-session.json'),'utf8')),marker);
|
|
46
51
|
assert.notEqual(code,0);
|
|
47
52
|
});
|
|
@@ -52,6 +57,7 @@ test('R75 bound stale birth identity retains marker without touching a reused PI
|
|
|
52
57
|
await mkdir(path.join(root,'runtime'));await mkdir(path.join(root,'bin'));
|
|
53
58
|
const trace=path.join(root,'trace.txt');
|
|
54
59
|
await writeFile(path.join(root,'bin','DEADBYTE-MCP.ps1'),`param([string]$Command,[switch]$Json)\n[IO.File]::AppendAllText('${trace.replaceAll("'","''")}',$Command+[Environment]::NewLine)\nif ($Command -eq 'status' -and $Json) { $off=@{configured=$true;armed=$false;reason='disarmed'}; @{schema='deadbyte.controller-observation.v1';state='OFFLINE';authority=@{process=$off;coding=$off;autonomous=$off;desktop=$off;host=$off}} | ConvertTo-Json -Depth 5 -Compress }\nexit 0\n`);
|
|
60
|
+
await installExactActivePointer({stateRoot:root,packageRoot,launcherPath:path.join(root,'bin','DEADBYTE-MCP.ps1')});
|
|
55
61
|
const current=queryForegroundIdentity(process.pid);
|
|
56
62
|
const stale={...current,creation_utc:'2000-01-01T00:00:00.000Z'};
|
|
57
63
|
const marker={schema:'deadbyte.remote-session.v2',owner_pid:process.pid,guardian_pid:process.pid,session_nonce:'b'.repeat(64),owner_identity:stale,guardian_identity:stale};
|
|
@@ -62,7 +68,7 @@ test('R75 bound stale birth identity retains marker without touching a reused PI
|
|
|
62
68
|
const code=await new Promise(resolve=>child.once('close',resolve));
|
|
63
69
|
await writeFile(path.join(root,'observation.json'),JSON.stringify({code,stdout,stderr,simulation:'birth identity drift at a live unrelated numeric PID'}),{flag:'wx'});
|
|
64
70
|
assert.notEqual(code,0);
|
|
65
|
-
assert.match(stderr,/
|
|
71
|
+
assert.match(stderr,/R179_LEGACY_FOREGROUND_RECOVERY_REQUIRES_EXPLICIT_OWNER_COMMAND/i);
|
|
66
72
|
assert.equal(await readFile(trace,'utf8').catch(error=>error.code==='ENOENT'?'':Promise.reject(error)),'','identity drift must cause no authority commands');
|
|
67
73
|
assert.deepEqual(JSON.parse(await readFile(markerPath,'utf8')),marker);
|
|
68
74
|
assert.equal(foregroundAlive(),true,'unrelated live process must not be killed');
|
|
@@ -83,7 +83,7 @@ test('R72 compact baseline permits only R64 read offset and authorized coding-on
|
|
|
83
83
|
assert.deepEqual(diff.added,[]);
|
|
84
84
|
assert.deepEqual(diff.removed,[]);
|
|
85
85
|
const packageVersion=JSON.parse(await readFile(path.join(repoRoot,'package.json'),'utf8')).version;
|
|
86
|
-
if(['0.15.41','0.15.42','0.15.43','0.15.44','0.15.45','0.15.46','0.15.47','0.15.48','0.15.49','0.15.50','0.15.51','0.15.52','0.15.53','0.15.54','0.15.55','0.15.56','0.15.57','0.15.58','0.15.59','0.15.60','0.15.61','0.15.62','0.15.63','0.15.64','0.15.65','0.15.66','0.15.67','0.15.68'].includes(packageVersion)){
|
|
86
|
+
if(['0.15.41','0.15.42','0.15.43','0.15.44','0.15.45','0.15.46','0.15.47','0.15.48','0.15.49','0.15.50','0.15.51','0.15.52','0.15.53','0.15.54','0.15.55','0.15.56','0.15.57','0.15.58','0.15.59','0.15.60','0.15.61','0.15.62','0.15.63','0.15.64','0.15.65','0.15.66','0.15.67','0.15.68','0.15.69'].includes(packageVersion)){
|
|
87
87
|
assert.deepEqual([...diff.schema_changed].sort(),['machine_fs','native_diff_preview']);
|
|
88
88
|
assert.equal(diff.unchanged.length,41);
|
|
89
89
|
const prior=baseline.tools.find(tool=>tool.name==='machine_fs');
|
|
@@ -73,8 +73,8 @@ test('R40 CLI exposes explicit verbose mode while keeping default live mode comp
|
|
|
73
73
|
const src=await readFile(new URL('../src/deadbyte-cli.mjs',import.meta.url),'utf8');
|
|
74
74
|
assert.match(src,/readLiveRootPaths\(ctx\.root\)/);
|
|
75
75
|
assert.match(src,/createLiveTimelineTracker\(\{verbose,displayMode,rootPaths,trace,\.\.\.defaultForegroundTimelineOptions\(\{verbose\}\)\}\)/);
|
|
76
|
-
assert.deepEqual(live.defaultForegroundTimelineOptions({verbose:false}),{commanderStyle:
|
|
77
|
-
assert.deepEqual(live.defaultForegroundTimelineOptions({verbose:true}),{commanderStyle:false,foldHeartbeats:false});
|
|
76
|
+
assert.deepEqual(live.defaultForegroundTimelineOptions({verbose:false}),{commanderStyle:false,foldHeartbeats:true,prettyStyle:true});
|
|
77
|
+
assert.deepEqual(live.defaultForegroundTimelineOptions({verbose:true}),{commanderStyle:false,foldHeartbeats:false,prettyStyle:false});
|
|
78
78
|
assert.match(src,/createLiveTimelineFilter\(filterExpression\)/);
|
|
79
79
|
assert.match(src,/createLiveTimelineWriter\(\{stream:process\.stdout,displayMode,rewrite:false\}\)/);
|
|
80
80
|
assert.match(src,/arg==='--verbose'\|\|arg==='-v'/);
|