deadbyte-mcp 0.15.55 → 0.15.59
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 +36 -23
- package/PROVENANCE.json +11 -11
- package/README.txt +18 -18
- package/RELEASE.json +24 -8
- package/SBOM.cdx.json +2 -2
- package/TEST-MATRIX.json +2 -2
- package/bridge/package.json +1 -1
- package/package.json +1 -1
- package/scripts/bootstrap-uac-migration-r153.ps1 +232 -0
- package/scripts/deploy-r48.mjs +133 -11
- package/scripts/issue-uac-migration-r153.mjs +107 -0
- package/scripts/r153-prepublish-identity.mjs +57 -0
- package/scripts/request-uac-migration-r153.ps1 +165 -0
- package/scripts/verify-protected-stage-r153.mjs +20 -0
- package/src/activation-preflight-r65.mjs +2 -1
- package/src/clean-activation-recovery-r153.mjs +297 -0
- package/src/deadbyte-cli.mjs +925 -720
- package/src/foreground-process-identity.mjs +31 -0
- package/src/legacy-offline-policy-proof-r146.mjs +31 -10
- package/src/runtime-update.mjs +19 -4
- package/src/uac-approval-contract-r153.mjs +104 -0
- package/src/version.mjs +1 -1
- package/test/commander-protocol-r47.test.mjs +6 -1
- package/test/r130-release-security-contract.test.mjs +8 -7
- package/test/r132-release-identity.test.mjs +8 -7
- package/test/r146-legacy-offline-policy-proof.test.mjs +31 -1
- package/test/r153-clean-activation-recovery.test.mjs +213 -0
- package/test/r153-prepublish-identity.test.mjs +40 -0
- package/test/r153-restricted-reused-pid.test.mjs +31 -0
- package/test/r153-staging-leaf-acl.test.mjs +47 -0
- package/test/r153-uac-approval-contract.test.mjs +92 -0
- package/test/r153-uac-installed-manifest-contract.test.mjs +19 -0
- package/test/r153-uac-launch-parameter-contract.test.mjs +59 -0
- package/test/r58-concurrent-recovery.test.mjs +1 -1
- package/test/r59-tool-surface-compat.test.mjs +1 -1
- package/test/r65-activation-orchestration.test.mjs +20 -0
- package/test/remote-session-cli.test.mjs +1 -1
- package/node_modules/which/CHANGELOG.md +0 -166
|
@@ -25,3 +25,34 @@ export function queryForegroundIdentity(pid){
|
|
|
25
25
|
if(!validForegroundIdentity(identity)||identity.pid!==pid)throw new Error('foreground process identity incomplete');
|
|
26
26
|
return identity;
|
|
27
27
|
}
|
|
28
|
+
|
|
29
|
+
export function classifyRestrictedReusedPid(expected,observed){
|
|
30
|
+
if(!validForegroundIdentity(expected)||path.win32.basename(expected.executable_path).toLowerCase()!=='node.exe')
|
|
31
|
+
throw new Error('R153 restricted PID proof requires the exact original Node owner identity');
|
|
32
|
+
if(!observed||!Number.isSafeInteger(observed.pid)||observed.pid!==expected.pid)
|
|
33
|
+
throw new Error('R153 restricted PID evidence PID mismatch or unverified');
|
|
34
|
+
if(String(observed.name).toLowerCase()!=='svchost.exe')
|
|
35
|
+
throw new Error('R153 restricted PID evidence not a protected Windows service');
|
|
36
|
+
if(typeof observed.creation_utc!=='string'||!/^\d{4}-\d\d-\d\dT.*Z$/.test(observed.creation_utc))
|
|
37
|
+
throw new Error('R153 restricted PID birth invalid');
|
|
38
|
+
const previous=Date.parse(expected.creation_utc),current=Date.parse(observed.creation_utc);
|
|
39
|
+
if(!Number.isFinite(previous)||!Number.isFinite(current))
|
|
40
|
+
throw new Error('R153 restricted PID birth invalid');
|
|
41
|
+
if(current<=previous)throw new Error('R153 restricted PID birth unchanged or ambiguous');
|
|
42
|
+
return 'REUSED';
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function proveRestrictedPidReuse(expected,cause){
|
|
46
|
+
if(cause?.message!=='foreground process command identity unavailable')throw cause;
|
|
47
|
+
if(!validForegroundIdentity(expected))throw new Error('R153 restricted PID owner identity unverified');
|
|
48
|
+
const powershell='C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe';
|
|
49
|
+
const command=`$p=Get-CimInstance Win32_Process -Filter 'ProcessId=${expected.pid}'; if ($null -eq $p) {throw 'PID not positively observed'}; [pscustomobject]@{pid=$p.ProcessId;name=$p.Name;creation_utc=$p.CreationDate.ToUniversalTime().ToString('o')} | ConvertTo-Json -Compress`;
|
|
50
|
+
const result=spawnSync(powershell,['-NoLogo','-NoProfile','-Command',command],{
|
|
51
|
+
encoding:'utf8',windowsHide:true,timeout:15000,maxBuffer:1024*1024,
|
|
52
|
+
env:windowsPowerShellEnvironment({...process.env,SystemRoot:'C:\\Windows',WINDIR:'C:\\Windows'})
|
|
53
|
+
});
|
|
54
|
+
if(result.error||result.status!==0)throw new Error('R153 restricted PID system observation failed');
|
|
55
|
+
let observed;
|
|
56
|
+
try{observed=JSON.parse(result.stdout);}catch{throw new Error('R153 restricted PID system observation malformed');}
|
|
57
|
+
return classifyRestrictedReusedPid(expected,observed)==='REUSED';
|
|
58
|
+
}
|
|
@@ -3,6 +3,7 @@ import {lstat,open,readFile} from 'node:fs/promises';
|
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import {verifyGenerationBundle} from './release-generation.mjs';
|
|
5
5
|
import {canonicalJson} from './canonical-json.mjs';
|
|
6
|
+
import {authorizeElevatedLegacyMigration,verifyMarkerReconciliationReceipt} from './clean-activation-recovery-r153.mjs';
|
|
6
7
|
|
|
7
8
|
const PLANES=['process','coding','autonomous','desktop','host'];
|
|
8
9
|
const GRANT_FIELDS={process:'grant_file',coding:'lease_file',autonomous:'lease_file',desktop:'grant_file',host:'arm_file'};
|
|
@@ -114,7 +115,7 @@ async function verifyMigrationApproval({approvalPath,approvalSha256,ownerPublicK
|
|
|
114
115
|
export async function proveLegacyOfflineScopeRepair({
|
|
115
116
|
stateRoot,pointer,expectedParent,candidate,expectedCandidate,preflightSubjectSha256,
|
|
116
117
|
observation,policyPaths,policySha256,approvalPath,approvalSha256,ownerPublicKeyPath,
|
|
117
|
-
now=new Date()
|
|
118
|
+
now=new Date(),uacAuthorization=null
|
|
118
119
|
}){
|
|
119
120
|
if(!stateRoot||!path.isAbsolute(stateRoot))throw new Error('state root invalid');
|
|
120
121
|
if(!pointer||!expectedParent||!observation)throw new Error('legacy scope proof subject missing');
|
|
@@ -182,22 +183,42 @@ export async function proveLegacyOfflineScopeRepair({
|
|
|
182
183
|
if(!Array.isArray(autonomous.root_ids)||autonomous.root_ids.length===0||
|
|
183
184
|
autonomous.root_ids.some(id=>id==='local_c'||id==='local_d'||!Object.hasOwn(coding.roots,id)))
|
|
184
185
|
throw new Error('legacy scope proof autonomous roots unsafe');
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
186
|
+
let approval,authorization='owner_ed25519';
|
|
187
|
+
if(uacAuthorization){
|
|
188
|
+
const markerReceipt=uacAuthorization.markerReceipt;
|
|
189
|
+
if(!markerReceipt)throw new Error('legacy migration marker reconciliation receipt missing');
|
|
190
|
+
verifyMarkerReconciliationReceipt(markerReceipt,{pointer,policySha256,
|
|
191
|
+
markerSha256:markerReceipt.marker_sha256});
|
|
192
|
+
const uacSubject={
|
|
193
|
+
schema:'deadbyte.r153-uac-migration-subject.v1',action:'narrow_legacy_policy_and_activate',
|
|
194
|
+
state_root:path.win32.resolve(stateRoot),predecessor:pointer,candidate:{...candidate,tarball_sha256:uacAuthorization.tarballSha256},
|
|
195
|
+
predecessor_policy_sha256:policySha256,marker_sha256:markerReceipt.marker_sha256,
|
|
196
|
+
allowed_authority_after:Object.fromEntries(PLANES.map(plane=>[plane,false])),
|
|
197
|
+
preflight_subject_sha256:preflightSubjectSha256,nonce:uacAuthorization.nonce,
|
|
198
|
+
issued_at_utc:uacAuthorization.issuedAt,expires_at_utc:uacAuthorization.expiresAt
|
|
199
|
+
};
|
|
200
|
+
approval=authorizeElevatedLegacyMigration({subject:uacSubject,isElevated:uacAuthorization.isElevated,now});
|
|
201
|
+
authorization=approval.authorization;
|
|
202
|
+
}else{
|
|
203
|
+
approval=await verifyMigrationApproval({approvalPath,approvalSha256,ownerPublicKeyPath,now,expected:{
|
|
204
|
+
predecessor_release_id:pointer.release_id,predecessor_version:pointer.version,
|
|
205
|
+
predecessor_manifest_sha256:pointer.manifest_sha256,
|
|
206
|
+
predecessor_generation_manifest_sha256:pointer.generation_manifest_sha256,
|
|
207
|
+
candidate_version:candidate.version,candidate_manifest_sha256:candidate.manifest_sha256,
|
|
208
|
+
predecessor_policy_sha256:policySha256,preflight_subject_sha256:preflightSubjectSha256
|
|
209
|
+
}});
|
|
210
|
+
}
|
|
192
211
|
const subject={release_id:pointer.release_id,version:pointer.version,
|
|
193
212
|
generation_manifest_sha256:pointer.generation_manifest_sha256,
|
|
194
213
|
coding_policy_sha256:policySha256.coding,autonomous_policy_sha256:policySha256.autonomous,
|
|
195
214
|
candidate_version:candidate.version,candidate_manifest_sha256:candidate.manifest_sha256,
|
|
196
|
-
preflight_subject_sha256:preflightSubjectSha256,
|
|
215
|
+
preflight_subject_sha256:preflightSubjectSha256,migration_authorization_subject_sha256:approval.subject_sha256,
|
|
216
|
+
owner_approval_subject_sha256:authorization==='owner_ed25519'?approval.subject_sha256:null,
|
|
197
217
|
grant_absent_planes:PLANES};
|
|
198
218
|
return Object.freeze({
|
|
199
219
|
state:'VERIFIED_OFFLINE_LEGACY_SCOPE_REPAIR',
|
|
200
|
-
...subject,owner_approval_verified:
|
|
220
|
+
...subject,owner_approval_verified:authorization==='owner_ed25519',migration_authorization_verified:true,
|
|
221
|
+
migration_authorization:authorization,owner_key_id:approval.owner_key_id??null,
|
|
201
222
|
approval_expires_at_utc:approval.expires_at_utc,
|
|
202
223
|
subject_sha256:sha(Buffer.from(canonicalJson(subject),'utf8'))
|
|
203
224
|
});
|
package/src/runtime-update.mjs
CHANGED
|
@@ -22,6 +22,7 @@ import {canonicalLauncherPaths,assertCanonicalLaunchers,renderStateBoundLauncher
|
|
|
22
22
|
import {assertStageOwnedPath,assertStagingBootstrapInputs,assertStagingControlConfig} from './staging-input-isolation.mjs';
|
|
23
23
|
import { verifyProjectOwnerApproval } from './r70-owner-approval.mjs';
|
|
24
24
|
import {windowsPowerShellEnvironment} from './windows-powershell-environment.mjs';
|
|
25
|
+
import {verifyNarrowedPolicyGeneration} from './clean-activation-recovery-r153.mjs';
|
|
25
26
|
|
|
26
27
|
export {windowsPowerShellEnvironment};
|
|
27
28
|
|
|
@@ -1847,7 +1848,7 @@ export async function performSlotUpdate({stage,developmentRoot,expectedVersion,e
|
|
|
1847
1848
|
await writeState(statePath,journal,'initial');
|
|
1848
1849
|
let pointerInfo=null,control=null,predecessor=null,oldRuntime=null,priorStatus=null,authorityPreparation=null,workspace=null,launchers=null,newPointer=null;
|
|
1849
1850
|
let stableMcpUrl=null,mcpUrlSnapshot=null;
|
|
1850
|
-
let
|
|
1851
|
+
let activationDispatchBegan=false;
|
|
1851
1852
|
try{
|
|
1852
1853
|
pointerInfo=await loadPointerIfPresent(state);
|
|
1853
1854
|
const stageVerified=await verifyPackageRoot(stageRoot,expectedVersion,expectedManifest,'stage');
|
|
@@ -1869,7 +1870,6 @@ export async function performSlotUpdate({stage,developmentRoot,expectedVersion,e
|
|
|
1869
1870
|
// Read-only predecessor authority and process identity must be safe before
|
|
1870
1871
|
// materializing a slot or changing any stable runtime file.
|
|
1871
1872
|
await capturePredecessor(control.ctx,recoverOfflinePredecessor,{allowHostDisarm:false});
|
|
1872
|
-
mutationBegan=true;
|
|
1873
1873
|
const slot=await materializeReleaseSlot({source:stageRoot,stateRoot:state,version:expectedVersion,manifestSha256:expectedManifest});
|
|
1874
1874
|
const hostBootstrap=await prepareHostPolicyBootstrap(hostPolicyBootstrap);if(hostBootstrap) control.hostPolicy=hostBootstrap.policy;
|
|
1875
1875
|
if(codingBootstrap) control.codingPolicy=codingBootstrap.policy;
|
|
@@ -1910,6 +1910,17 @@ export async function performSlotUpdate({stage,developmentRoot,expectedVersion,e
|
|
|
1910
1910
|
}
|
|
1911
1911
|
workspace=await workspacePlan(control.workspaceRoot,state);launchers=await launcherPlan(state);
|
|
1912
1912
|
const generation=await createGenerationBundle({stateRoot:state,releaseRoot:slot.release_root,developmentRoot:development,workspaceRoot:workspace.target,releaseId:slot.release_id,currentConfig:control.config,codingPolicy:control.codingPolicy,autonomousPolicy:control.autonomousPolicy,hostPolicy:control.hostPolicy,processPolicy:control.processPolicy,desktopPolicy:control.desktopPolicy});
|
|
1913
|
+
// A newly materialized generation is still pre-pointer-switch. Prove that
|
|
1914
|
+
// legacy whole-drive roots were narrowed here, while a failure is still a
|
|
1915
|
+
// deterministic candidate-preparation rejection rather than an ambiguous
|
|
1916
|
+
// activation outcome.
|
|
1917
|
+
const generatedCodingBytes=await readFile(generation.coding_policy_path);
|
|
1918
|
+
const generatedAutonomousBytes=await readFile(generation.autonomous_policy_path);
|
|
1919
|
+
verifyNarrowedPolicyGeneration({
|
|
1920
|
+
codingPolicy:JSON.parse(generatedCodingBytes.toString('utf8').replace(/^\uFEFF/,'')),
|
|
1921
|
+
codingPolicyBytes:generatedCodingBytes,
|
|
1922
|
+
autonomousPolicy:JSON.parse(generatedAutonomousBytes.toString('utf8').replace(/^\uFEFF/,''))
|
|
1923
|
+
});
|
|
1913
1924
|
newPointer={schema:'deadbyte.active-release.v1',release_id:slot.release_id,version:expectedVersion,manifest_sha256:expectedManifest,release_root:slot.release_root,generation_root:generation.generation_root,generation_manifest_sha256:generation.generation_manifest_sha256,launcher_sha256:launchers.launcher_sha256,config_path:generation.config_path};
|
|
1914
1925
|
parseActivePointer(JSON.stringify(newPointer));
|
|
1915
1926
|
await writeState(statePath,journal,'candidate_prepared',{prior_pointer:publicPointer(pointerInfo.pointer),stage:stageVerified,development:developmentVerified,
|
|
@@ -1925,17 +1936,21 @@ export async function performSlotUpdate({stage,developmentRoot,expectedVersion,e
|
|
|
1925
1936
|
await writeState(statePath,journal,'old_runtime_verified',{predecessor_mode:predecessor.mode,old_runtime:generationTuple(oldRuntime),
|
|
1926
1937
|
prior_authority:{process_armed:priorStatus.process_armed,coding_armed:priorStatus.coding_armed,autonomous_armed:priorStatus.autonomous_armed,desktop_armed:priorStatus.desktop_armed,host_armed:priorStatus.host_armed}});
|
|
1927
1938
|
const handoff=await writeHandoff({stateRoot:state,state:journal,journal,statePath,evidenceDir,key,pointerInfo,newPointer,predecessor,priorStatus,workspace,launchers,stableMcpUrl,mcpUrlSnapshot,controlCtx:control.ctx});
|
|
1939
|
+
// From this point a detached child may observe the handoff even if the
|
|
1940
|
+
// parent loses its acknowledgement. Earlier failures are deterministic
|
|
1941
|
+
// preparation rejections and must not be reported as unknown activation.
|
|
1942
|
+
activationDispatchBegan=true;
|
|
1928
1943
|
const supervisor=await spawnDetachedSupervisor({script:handoff.body.supervisor_script,handoffPath:handoff.path,cwd:control.ctx.core_root});
|
|
1929
1944
|
return {ok:true,handed_off:true,completed:false,update_id:updateId,state_path:statePath,handoff_path:handoff.path,handoff_receipt_sha256:handoff.receipt.receipt_sha256,supervisor_pid:supervisor.pid,pointer:newPointer};
|
|
1930
1945
|
}catch(error){
|
|
1931
1946
|
const failure={schema:FAILURE_SCHEMA,update_id:updateId,release_version:expectedVersion,candidate_manifest_sha256:expectedManifest,failed_at_utc:new Date().toISOString(),
|
|
1932
|
-
result:
|
|
1947
|
+
result:activationDispatchBegan?'MUTATION_OUTCOME_REQUIRES_RECONCILIATION':'PRECONDITION_REJECTED',
|
|
1933
1948
|
phase:journal.phase??'unknown',error:{name:error?.name??'Error',code:error?.code??null,message:String(error?.message??error)},
|
|
1934
1949
|
predecessor_mode:predecessor?.mode??null,prior_pointer:publicPointer(pointerInfo?.pointer??null),attempted_pointer:publicPointer(newPointer),old_runtime:generationTuple(oldRuntime),
|
|
1935
1950
|
rollback:{attempted:false,new_runtime_stopped:false,pointer_restored:false,launchers_restored:false,workspace_restored:false,mcp_url_restored:false,old_runtime_restarted:false,authority_restored:false,errors:[]}};
|
|
1936
1951
|
await writeState(statePath,journal,'failed',{failure}).catch(()=>{});
|
|
1937
1952
|
let failureReceipt=null;
|
|
1938
|
-
try{ failureReceipt=await writeEnvelope(failurePath,failure,key);await writeState(statePath,journal,
|
|
1953
|
+
try{ failureReceipt=await writeEnvelope(failurePath,failure,key);await writeState(statePath,journal,activationDispatchBegan?'failed_receipted':'precondition_rejected',{failure_receipt_path:failurePath,failure_receipt_sha256:failureReceipt.receipt_sha256});}
|
|
1939
1954
|
catch(receiptError){ await writeState(statePath,journal,'failed_unreceipted',{failure_receipt_error:String(receiptError?.message??receiptError)}).catch(()=>{}); }
|
|
1940
1955
|
const wrapped=new Error('R27 slot update handoff failed: '+(error?.message??error));wrapped.cause=error;wrapped.statePath=statePath;wrapped.failureReceiptPath=failureReceipt?failurePath:null;throw wrapped;
|
|
1941
1956
|
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// R153 consent is narrowly scoped to a one-time, authority-reducing migration.
|
|
2
|
+
// This JSON record is evidence of UAC confirmation, NOT a signature or owner grant.
|
|
3
|
+
// Verify it only when running elevated from an independently R89-verified install.
|
|
4
|
+
import {createHash} from 'node:crypto';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
|
|
7
|
+
const HEX64=/^[0-9a-f]{64}$/;
|
|
8
|
+
const PLANES=['process','coding','autonomous','desktop','host'];
|
|
9
|
+
const ALL_DISARMED=Object.freeze(Object.fromEntries(PLANES.map(p=>[p,false])));
|
|
10
|
+
const TIME=/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d\.\d{3}Z$/;
|
|
11
|
+
const FIELDS=['schema','action','state_root','candidate_root','candidate_version','candidate_manifest_sha256',
|
|
12
|
+
'tarball_sha256','marker_receipt_sha256','predecessor_pointer_sha256','predecessor_policy_sha256',
|
|
13
|
+
'preflight_subject_sha256','node_sha256','deploy_script_sha256','nonce','issued_at_utc',
|
|
14
|
+
'expires_at_utc','allowed_authority_after','subject_sha256'];
|
|
15
|
+
function canonical(value){
|
|
16
|
+
if(value===null)return 'null';
|
|
17
|
+
if(typeof value==='string'||typeof value==='boolean')return JSON.stringify(value);
|
|
18
|
+
if(typeof value==='number'&&Number.isFinite(value))return JSON.stringify(value);
|
|
19
|
+
if(Array.isArray(value))return '['+value.map(canonical).join(',')+']';
|
|
20
|
+
if(typeof value==='object'&&Object.getPrototypeOf(value)===Object.prototype)
|
|
21
|
+
return '{'+Object.keys(value).sort().map(k=>JSON.stringify(k)+':'+canonical(value[k])).join(',')+'}';
|
|
22
|
+
throw new Error('UAC approval subject has noncanonical field');
|
|
23
|
+
}
|
|
24
|
+
const digest=v=>createHash('sha256').update(canonical(v),'utf8').digest('hex');
|
|
25
|
+
export function uacPointerSha256(pointer){return digest(pointer);}
|
|
26
|
+
|
|
27
|
+
// Protected one-time approval evidence must not mutate the exact npm package
|
|
28
|
+
// tree. Store it as an adjacent protected sidecar and require a fresh R89 ACL
|
|
29
|
+
// verdict before the elevated deploy reads it.
|
|
30
|
+
export function protectedUacReceiptPath(candidateRoot,nonce){
|
|
31
|
+
const candidate=windowsAbsolute(candidateRoot,'candidate root');
|
|
32
|
+
if(!candidate.startsWith('c:\\program files\\')||candidate==='c:\\program files\\')
|
|
33
|
+
throw new Error('UAC approval requires a protected candidate root');
|
|
34
|
+
if(!HEX64.test(String(nonce??'')))throw new Error('UAC approval nonce invalid');
|
|
35
|
+
const basename=path.win32.basename(candidate);
|
|
36
|
+
if(!basename||basename==='.'||basename==='..')throw new Error('UAC candidate path invalid');
|
|
37
|
+
return path.win32.join(path.win32.dirname(candidate),`${basename}.r153-uac-approval-${nonce}.json`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function time(value,label){
|
|
41
|
+
const ms=Date.parse(value);
|
|
42
|
+
if(typeof value!=='string'||!TIME.test(value)||!Number.isFinite(ms)||new Date(ms).toISOString()!==value)
|
|
43
|
+
throw new Error('UAC '+label+' invalid');
|
|
44
|
+
return ms;
|
|
45
|
+
}
|
|
46
|
+
export function validateApprovalWindow(issuedAt,expiresAt,now=new Date().toISOString()){
|
|
47
|
+
const issued=time(issuedAt,'issue time'),expires=time(expiresAt,'expiry time'),current=time(now,'current time');
|
|
48
|
+
if(expires<=issued||expires-issued>600_000)throw new Error('UAC consent lifetime invalid');
|
|
49
|
+
if(current<issued||current>=expires)throw new Error('UAC consent expired or not yet valid');
|
|
50
|
+
}
|
|
51
|
+
function windowsAbsolute(value,label){
|
|
52
|
+
if(typeof value!=='string'||!path.win32.isAbsolute(value)||!(/^[A-Za-z]:\\/.test(value))||value.includes('\0'))
|
|
53
|
+
throw new Error(`UAC ${label} path invalid`);
|
|
54
|
+
return path.win32.normalize(value).toLowerCase();
|
|
55
|
+
}
|
|
56
|
+
export function uacReceiptSubject(input){
|
|
57
|
+
const root=windowsAbsolute(input.stateRoot,'state root');
|
|
58
|
+
const candidate=windowsAbsolute(input.candidateRoot,'candidate root');
|
|
59
|
+
if(!candidate.startsWith('c:\\program files\\')||candidate==='c:\\program files\\')
|
|
60
|
+
throw new Error('UAC migration requires a protected candidate installation');
|
|
61
|
+
if(input.candidateVersion!=='0.15.59')throw new Error('UAC migration candidate identity unsupported');
|
|
62
|
+
const hashes=['candidateManifestSha256','tarballSha256','markerReceiptSha256','predecessorPointerSha256',
|
|
63
|
+
'preflightSubjectSha256','nodeSha256','deployScriptSha256','nonce'];
|
|
64
|
+
for(const key of hashes)if(!HEX64.test(String(input[key]??'')))throw new Error('UAC approval hash/nonce invalid: '+key);
|
|
65
|
+
const policies=input.predecessorPolicySha256;
|
|
66
|
+
if(!policies||typeof policies!=='object'||Array.isArray(policies)||
|
|
67
|
+
Object.keys(policies).length!==PLANES.length||PLANES.some(p=>!HEX64.test(String(policies[p]??''))))
|
|
68
|
+
throw new Error('UAC predecessor policy identity incomplete');
|
|
69
|
+
const issued=time(input.issuedAt,'issue time'),expires=time(input.expiresAt,'expiry time');
|
|
70
|
+
if(expires<=issued||expires-issued>600_000)throw new Error('UAC consent lifetime invalid');
|
|
71
|
+
return Object.freeze({schema:'deadbyte.r153-uac-approval.v1',action:'narrow_legacy_policy_and_activate',
|
|
72
|
+
state_root:root,candidate_root:candidate,candidate_version:input.candidateVersion,
|
|
73
|
+
candidate_manifest_sha256:input.candidateManifestSha256,tarball_sha256:input.tarballSha256,
|
|
74
|
+
marker_receipt_sha256:input.markerReceiptSha256,
|
|
75
|
+
predecessor_pointer_sha256:input.predecessorPointerSha256,
|
|
76
|
+
predecessor_policy_sha256:Object.freeze(Object.fromEntries(PLANES.map(p=>[p,policies[p]]))),
|
|
77
|
+
preflight_subject_sha256:input.preflightSubjectSha256,node_sha256:input.nodeSha256,
|
|
78
|
+
deploy_script_sha256:input.deployScriptSha256,nonce:input.nonce,
|
|
79
|
+
issued_at_utc:input.issuedAt,expires_at_utc:input.expiresAt,
|
|
80
|
+
allowed_authority_after:Object.freeze({...ALL_DISARMED})});
|
|
81
|
+
}
|
|
82
|
+
export function createUacApproval(input){
|
|
83
|
+
const subject=uacReceiptSubject(input);
|
|
84
|
+
return Object.freeze({...subject,subject_sha256:digest(subject)});
|
|
85
|
+
}
|
|
86
|
+
export function verifyUacApproval(receipt,input,{now=new Date().toISOString()}={}){
|
|
87
|
+
if(!receipt||typeof receipt!=='object'||Array.isArray(receipt)||
|
|
88
|
+
Object.keys(receipt).length!==FIELDS.length||FIELDS.some(f=>!Object.hasOwn(receipt,f)))
|
|
89
|
+
throw new Error('UAC approval receipt field shape invalid');
|
|
90
|
+
const expected=createUacApproval(input);
|
|
91
|
+
if(canonical(receipt)!==canonical(expected))throw new Error('UAC approval receipt binding mismatch');
|
|
92
|
+
validateApprovalWindow(expected.issued_at_utc,expected.expires_at_utc,now);
|
|
93
|
+
return receipt;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// UAC is forbidden unless R89 has positively attested the installed bytes and ACLs.
|
|
97
|
+
// inspectTrustedInstallation may report a failure as data rather than throw.
|
|
98
|
+
export function requireTrustedUacInstallation(result){
|
|
99
|
+
if(result?.trusted!==true){
|
|
100
|
+
const reason=typeof result?.reason==='string'?result.reason.slice(0,128):'unverified';
|
|
101
|
+
throw new Error('R89 protected installation trust required: '+reason);
|
|
102
|
+
}
|
|
103
|
+
return result;
|
|
104
|
+
}
|
package/src/version.mjs
CHANGED
|
@@ -17,11 +17,16 @@ const execFileAsync=promisify(execFile);
|
|
|
17
17
|
|
|
18
18
|
async function materializeTestHostPolicy(root){
|
|
19
19
|
const policyPath=path.join(root,'deadbyte-host-policy.json');
|
|
20
|
+
// The policy initializer creates controller/runtime under CoreRoot. Keep that
|
|
21
|
+
// mutable fixture out of the immutable or protected installation tree.
|
|
22
|
+
const fixtureCore=path.join(root,'core');
|
|
23
|
+
await mkdir(path.join(fixtureCore,'src'),{recursive:true});
|
|
24
|
+
await writeFile(path.join(fixtureCore,'src','mcp-server.mjs'),'// test-only CoreRoot identity; never executed\n');
|
|
20
25
|
const script=path.join(repoRoot,'scripts','init-host-policy.ps1');
|
|
21
26
|
// Windows PowerShell must derive its own module path, not inherit incompatible
|
|
22
27
|
// PowerShell 7 modules from the verifier's parent process.
|
|
23
28
|
const env={...process.env};delete env.PSModulePath;
|
|
24
|
-
await execFileAsync('powershell.exe',['-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',script,'-CoreRoot',
|
|
29
|
+
await execFileAsync('powershell.exe',['-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',script,'-CoreRoot',fixtureCore,'-BridgeRoot',repoRoot,'-PolicyPath',policyPath],{windowsHide:true,env});
|
|
25
30
|
return policyPath;
|
|
26
31
|
}
|
|
27
32
|
|
|
@@ -13,14 +13,15 @@ test('R130 security patch has a new immutable package identity and truthful sour
|
|
|
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.match(release.source_predecessor.source_path,/0\.15\.
|
|
16
|
+
assert.equal(actual,'0.15.59');
|
|
17
|
+
assert.equal(release.train,'R153');
|
|
18
|
+
assert.equal(matrix.train,'R153');
|
|
19
|
+
assert.equal(release.source_predecessor.version,'0.15.58');
|
|
20
|
+
assert.match(release.source_predecessor.source_path,/0\.15\.58/);
|
|
21
21
|
assert.equal(release.runtime_predecessor.version,'0.15.45');
|
|
22
|
-
assert.equal(provenance.
|
|
23
|
-
assert.equal(provenance.
|
|
22
|
+
assert.equal(provenance.source_predecessor_tarball_sha256,'4bc53cfa6b3890b5749066cb507e8773b36d6c255192a8973dadb8546a39822e');
|
|
23
|
+
assert.equal(provenance.source_predecessor_version,'0.15.58');
|
|
24
|
+
assert.equal(provenance.source_predecessor_manifest_file_sha256,'ec621a8a16f264f8824aa4644c914aca8f6ce57cb3578f5bce8acf2ac77fcc18');
|
|
24
25
|
assert.equal(pkg.dependencies['@modelcontextprotocol/client'],'2.3.1');
|
|
25
26
|
assert.equal(pkg.dependencies['@modelcontextprotocol/server'],'2.3.1');
|
|
26
27
|
for(const dep of ['@modelcontextprotocol/client','@modelcontextprotocol/server'])
|
|
@@ -15,13 +15,14 @@ test('R144 package identities, R132 historic evidence and published predecessor
|
|
|
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.match(release.source_predecessor.source_path,/0\.15\.
|
|
22
|
-
assert.equal(prov.source_predecessor_version,'0.15.
|
|
23
|
-
assert.equal(prov.source_predecessor_manifest_file_sha256,'
|
|
24
|
-
assert.equal(prov.parent_tarball_sha256,'
|
|
18
|
+
assert.equal(release.train,'R153');
|
|
19
|
+
assert.equal(matrix.train,'R153');
|
|
20
|
+
assert.equal(release.source_predecessor.version,'0.15.58');
|
|
21
|
+
assert.match(release.source_predecessor.source_path,/0\.15\.58/);
|
|
22
|
+
assert.equal(prov.source_predecessor_version,'0.15.58');
|
|
23
|
+
assert.equal(prov.source_predecessor_manifest_file_sha256,'ec621a8a16f264f8824aa4644c914aca8f6ce57cb3578f5bce8acf2ac77fcc18');
|
|
24
|
+
assert.equal(prov.parent_tarball_sha256,'4bc53cfa6b3890b5749066cb507e8773b36d6c255192a8973dadb8546a39822e');
|
|
25
|
+
assert.equal(prov.parent_registry_tarball_sha1,'04cc1a1e80bcb7062ac39eee6b7bab70edd5d8c3');
|
|
25
26
|
assert.equal(release.runtime_predecessor.version,'0.15.45');
|
|
26
27
|
assert.equal(prov.runtime_predecessor.release_id,release.runtime_predecessor.release_id);
|
|
27
28
|
assert.equal(prov.runtime_predecessor.manifest_sha256,release.runtime_predecessor.manifest_sha256);
|
|
@@ -6,6 +6,7 @@ import os from 'node:os';
|
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
import {proveLegacyOfflineScopeRepair} from '../src/legacy-offline-policy-proof-r146.mjs';
|
|
8
8
|
import {canonicalJson} from '../src/canonical-json.mjs';
|
|
9
|
+
import {createMarkerReconciliationReceipt} from '../src/clean-activation-recovery-r153.mjs';
|
|
9
10
|
|
|
10
11
|
const sha=x=>createHash('sha256').update(x).digest('hex');
|
|
11
12
|
const PLANES=['process','coding','autonomous','desktop','host'];
|
|
@@ -82,11 +83,18 @@ async function fixture({thirdDrive=false,badBinding=false}={}){
|
|
|
82
83
|
const approval={...signed,subject,signature_base64:sign(null,Buffer.concat([APPROVAL_DOMAIN,Buffer.from(canonicalJson(signed),'utf8')]),privateKey).toString('base64')};
|
|
83
84
|
const approvalPath=path.join(base,'approval.json'),approvalBytes=Buffer.from(JSON.stringify(approval));
|
|
84
85
|
await writeFile(approvalPath,approvalBytes);
|
|
86
|
+
const markerSha256='d'.repeat(64);
|
|
87
|
+
const markerProofSha256=sha(Buffer.from(canonicalJson({pointer,policy_sha256:policySha256,
|
|
88
|
+
marker_sha256:markerSha256,
|
|
89
|
+
authority:Object.fromEntries(PLANES.map(plane=>[plane,false]))}),'utf8'));
|
|
90
|
+
const markerReceipt=createMarkerReconciliationReceipt({pointer,policySha256,
|
|
91
|
+
markerSha256,proofSha256:markerProofSha256,intentSha256:'f'.repeat(64),
|
|
92
|
+
observedAtUtc:'2026-10-23T00:00:00.000Z'});
|
|
85
93
|
return {
|
|
86
94
|
root:base,stateRoot,pointer,expectedParent:parent,observation,policyPaths,policySha256,
|
|
87
95
|
runtime,policies,candidate,expectedCandidate:candidate,preflightSubjectSha256,
|
|
88
96
|
approvalPath,approvalSha256:sha(approvalBytes),ownerPublicKeyPath,
|
|
89
|
-
now:new Date('2026-10-23T00:05:00.000Z'),async close(){await rm(base,{recursive:true,force:true});}
|
|
97
|
+
markerReceipt,now:new Date('2026-10-23T00:05:00.000Z'),async close(){await rm(base,{recursive:true,force:true});}
|
|
90
98
|
};
|
|
91
99
|
}
|
|
92
100
|
const invoke=x=>proveLegacyOfflineScopeRepair(x);
|
|
@@ -103,6 +111,28 @@ test('R146 verifies exact inactive legacy scope only with manifest, direct grant
|
|
|
103
111
|
assert.match(got.subject_sha256,/^[0-9a-f]{64}$/);
|
|
104
112
|
}finally{await x.close();}
|
|
105
113
|
});
|
|
114
|
+
test('R153 elevated one-time migration proof narrows authority without an owner signing key',async()=>{
|
|
115
|
+
const x=await fixture();
|
|
116
|
+
try{
|
|
117
|
+
const got=await invoke({...x,approvalPath:null,approvalSha256:null,ownerPublicKeyPath:null,
|
|
118
|
+
uacAuthorization:{isElevated:true,nonce:'b'.repeat(64),issuedAt:'2026-10-23T00:00:00.000Z',
|
|
119
|
+
expiresAt:'2026-10-23T00:10:00.000Z',tarballSha256:'c'.repeat(64),markerReceipt:x.markerReceipt}});
|
|
120
|
+
assert.equal(got.state,'VERIFIED_OFFLINE_LEGACY_SCOPE_REPAIR');
|
|
121
|
+
assert.equal(got.migration_authorization_verified,true);
|
|
122
|
+
assert.equal(got.migration_authorization,'windows_uac_elevated');
|
|
123
|
+
assert.equal(got.owner_approval_verified,false);
|
|
124
|
+
assert.equal(got.owner_key_id,null);
|
|
125
|
+
assert.equal(got.owner_approval_subject_sha256,null);
|
|
126
|
+
}finally{await x.close();}
|
|
127
|
+
});
|
|
128
|
+
test('R153 non-elevated migration proof is rejected without falling back to owner approval',async()=>{
|
|
129
|
+
const x=await fixture();
|
|
130
|
+
try{
|
|
131
|
+
await assert.rejects(invoke({...x,approvalPath:null,approvalSha256:null,ownerPublicKeyPath:null,
|
|
132
|
+
uacAuthorization:{isElevated:false,nonce:'b'.repeat(64),issuedAt:'2026-10-23T00:00:00.000Z',
|
|
133
|
+
expiresAt:'2026-10-23T00:10:00.000Z',tarballSha256:'c'.repeat(64),markerReceipt:x.markerReceipt}}),/elevation/i);
|
|
134
|
+
}finally{await x.close();}
|
|
135
|
+
});
|
|
106
136
|
test('R146 proof rejects candidate or approval subject drift',async()=>{
|
|
107
137
|
const x=await fixture();
|
|
108
138
|
try{
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {readFileSync} from 'node:fs';
|
|
4
|
+
import {fileURLToPath} from 'node:url';
|
|
5
|
+
import {createHash} from 'node:crypto';
|
|
6
|
+
import {canonicalJson} from '../src/canonical-json.mjs';
|
|
7
|
+
import {
|
|
8
|
+
classifyLegacyMigration,
|
|
9
|
+
createLegacyMigrationSubject,
|
|
10
|
+
authorizeElevatedLegacyMigration,
|
|
11
|
+
createMarkerReconciliationReceipt,
|
|
12
|
+
verifyMarkerReconciliationReceipt,
|
|
13
|
+
reconcileStaleMarker,
|
|
14
|
+
resumeMarkerReconciliation,
|
|
15
|
+
recoverPublishedMarkerTerminal,
|
|
16
|
+
verifyNarrowedPolicyGeneration,
|
|
17
|
+
runRemoteStartupSequence,claimWithOneTimeMigration
|
|
18
|
+
} from '../src/clean-activation-recovery-r153.mjs';
|
|
19
|
+
|
|
20
|
+
const HEX='a'.repeat(64);
|
|
21
|
+
const planes=['process','coding','autonomous','desktop','host'];
|
|
22
|
+
const base=()=>({
|
|
23
|
+
stateRoot:'C:\\Users\\owner\\.deadbyte-mcp',
|
|
24
|
+
pointer:{release_id:'v0.15.45-parent',version:'0.15.45',manifest_sha256:'1'.repeat(64),generation_manifest_sha256:'2'.repeat(64)},
|
|
25
|
+
candidate:{version:'0.15.59',manifest_sha256:'3'.repeat(64),tarball_sha256:'4'.repeat(64)},
|
|
26
|
+
observation:{state:'OFFLINE',local_ready:false,bridge:{alive:false},core:{alive:false},cloud:{alive:false},supervisor:{running:false},
|
|
27
|
+
authority:Object.fromEntries(planes.map(plane=>[plane,{configured:true,armed:false,reason:plane==='autonomous'?'unverified':'disarmed'}]))},
|
|
28
|
+
policySha256:Object.fromEntries(planes.map((plane,index)=>[plane,String(index+5).repeat(64)])),
|
|
29
|
+
grantPresence:Object.fromEntries(planes.map(plane=>[plane,false])),
|
|
30
|
+
codingRootPaths:['C:\\','D:\\','D:\\Projects\\deadbyte-mcp'],
|
|
31
|
+
marker:{sha256:HEX,owner_live:false,guardian_live:false}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
test('R153 RED: exact offline legacy broad-root state requires one-time narrowing consent',()=>{
|
|
35
|
+
const result=classifyLegacyMigration(base());
|
|
36
|
+
assert.equal(result.state,'MIGRATION_CONSENT_REQUIRED');
|
|
37
|
+
assert.equal(result.safe_to_migrate,true);
|
|
38
|
+
assert.match(result.subject_sha256,/^[0-9a-f]{64}$/);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('R153 RED: migration never proceeds with a grant, live runtime, live owner, or non-narrowing candidate',()=>{
|
|
42
|
+
const grant=base();grant.grantPresence.coding=true;
|
|
43
|
+
assert.equal(classifyLegacyMigration(grant).safe_to_migrate,false);
|
|
44
|
+
const online=base();online.observation.state='ONLINE';
|
|
45
|
+
assert.equal(classifyLegacyMigration(online).safe_to_migrate,false);
|
|
46
|
+
const live=base();live.marker.owner_live=true;
|
|
47
|
+
assert.equal(classifyLegacyMigration(live).safe_to_migrate,false);
|
|
48
|
+
const noLegacy=base();noLegacy.codingRootPaths=['D:\\Projects\\deadbyte-mcp'];
|
|
49
|
+
assert.equal(classifyLegacyMigration(noLegacy).safe_to_migrate,false);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('R153 RED: UAC subject binds candidate, predecessor, policy, marker, state root, nonce, and expiry',()=>{
|
|
53
|
+
const classified=classifyLegacyMigration(base());
|
|
54
|
+
const subject=createLegacyMigrationSubject({...base(),classification:classified,nonce:'b'.repeat(64),
|
|
55
|
+
issuedAt:'2026-10-23T16:00:00.000Z',expiresAt:'2026-10-23T16:10:00.000Z'});
|
|
56
|
+
assert.equal(subject.action,'narrow_legacy_policy_and_activate');
|
|
57
|
+
assert.equal(subject.marker_sha256,HEX);
|
|
58
|
+
assert.equal(subject.candidate.version,'0.15.59');
|
|
59
|
+
assert.equal(subject.nonce,'b'.repeat(64));
|
|
60
|
+
assert.deepEqual(subject.allowed_authority_after,Object.fromEntries(planes.map(plane=>[plane,false])));
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test('R153 RED: UAC authorization is accepted only from an elevated process and cannot carry authority',()=>{
|
|
64
|
+
const input=base(),classification=classifyLegacyMigration(input);
|
|
65
|
+
const subject=createLegacyMigrationSubject({...input,classification,nonce:'b'.repeat(64),
|
|
66
|
+
issuedAt:'2026-10-23T16:00:00.000Z',expiresAt:'2026-10-23T16:10:00.000Z'});
|
|
67
|
+
assert.equal(authorizeElevatedLegacyMigration({subject,isElevated:true,now:new Date('2026-10-23T16:05:00.000Z')}).state,'MIGRATION_APPROVED');
|
|
68
|
+
assert.throws(()=>authorizeElevatedLegacyMigration({subject,isElevated:false,now:new Date('2026-10-23T16:05:00.000Z')}),/elevation/i);
|
|
69
|
+
assert.throws(()=>authorizeElevatedLegacyMigration({subject:{...subject,allowed_authority_after:{...subject.allowed_authority_after,coding:true}},isElevated:true,now:new Date('2026-10-23T16:05:00.000Z')}),/authority/i);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('R153 RED: remote synchronization happens before a foreground marker is claimed',async()=>{
|
|
73
|
+
const calls=[];
|
|
74
|
+
await runRemoteStartupSequence({
|
|
75
|
+
recover:async()=>calls.push('recover'),
|
|
76
|
+
synchronize:async()=>calls.push('synchronize'),
|
|
77
|
+
claim:async()=>{calls.push('claim');return {id:'marker'};},
|
|
78
|
+
start:async()=>calls.push('start')
|
|
79
|
+
});
|
|
80
|
+
assert.deepEqual(calls,['recover','synchronize','claim','start']);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('R153 RED: stale marker deletion is content-CAS and requires complete offline/grant proof',async()=>{
|
|
84
|
+
const input=base();let deleted=false;
|
|
85
|
+
const evidence={publishIntent:async()=>({sha256:'b'.repeat(64)}),
|
|
86
|
+
publishTerminal:async()=>({sha256:'c'.repeat(64)}),revalidate:async()=>{}};
|
|
87
|
+
const result=await reconcileStaleMarker({...input,...evidence,deleteIfHash:async sha=>{assert.equal(sha,HEX);deleted=true;return 'REMOVED';}});
|
|
88
|
+
assert.equal(result.state,'MARKER_RECONCILED');
|
|
89
|
+
assert.equal(deleted,true);
|
|
90
|
+
await assert.rejects(reconcileStaleMarker({...input,...evidence,deleteIfHash:async()=> 'CHANGED'}),/changed/i);
|
|
91
|
+
await assert.rejects(reconcileStaleMarker({...input,...evidence,deleteIfHash:async()=> 'MISSING'}),/did not remove observed marker/i);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('R153 RED: UAC migration consumes a subject-bound terminal marker reconciliation receipt',()=>{
|
|
95
|
+
const input=base();
|
|
96
|
+
const proofSha256=createHash('sha256').update(canonicalJson({
|
|
97
|
+
pointer:input.pointer,policy_sha256:input.policySha256,marker_sha256:HEX,
|
|
98
|
+
authority:Object.fromEntries(planes.map(plane=>[plane,false]))
|
|
99
|
+
})).digest('hex');
|
|
100
|
+
const receipt=createMarkerReconciliationReceipt({
|
|
101
|
+
pointer:input.pointer,policySha256:input.policySha256,markerSha256:HEX,
|
|
102
|
+
proofSha256,intentSha256:'e'.repeat(64),observedAtUtc:'2026-10-23T16:00:00.000Z'
|
|
103
|
+
});
|
|
104
|
+
assert.equal(receipt.schema,'deadbyte.r153-marker-reconciliation.v1');
|
|
105
|
+
assert.equal(receipt.state,'MARKER_RECONCILED');
|
|
106
|
+
assert.equal(receipt.marker_sha256,HEX);
|
|
107
|
+
assert.match(receipt.subject_sha256,/^[0-9a-f]{64}$/);
|
|
108
|
+
assert.equal(verifyMarkerReconciliationReceipt(receipt,{pointer:input.pointer,
|
|
109
|
+
policySha256:input.policySha256,markerSha256:HEX}),receipt.subject_sha256);
|
|
110
|
+
assert.throws(()=>verifyMarkerReconciliationReceipt({...receipt,proof_sha256:'c'.repeat(64)},
|
|
111
|
+
{pointer:input.pointer,policySha256:input.policySha256,markerSha256:HEX}),/proof binding/i);
|
|
112
|
+
assert.throws(()=>verifyMarkerReconciliationReceipt({...receipt,marker_sha256:'d'.repeat(64)},
|
|
113
|
+
{pointer:input.pointer,policySha256:input.policySha256,markerSha256:HEX}),/marker/i);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test('R153: dry preflight does not consume a one-time approval; activation claim does',async()=>{
|
|
117
|
+
const events=[];
|
|
118
|
+
const held=await claimWithOneTimeMigration({subjectSha256:HEX,
|
|
119
|
+
claim:async value=>{assert.equal(value,HEX);events.push('claim');return {operation_id:'c'.repeat(32),release:async()=>events.push('release')};},
|
|
120
|
+
consume:async()=>events.push('consume')});
|
|
121
|
+
assert.deepEqual(events,['claim','consume']);
|
|
122
|
+
await held.release();
|
|
123
|
+
assert.deepEqual(events,['claim','consume','release']);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test('R153: failed or replayed nonce aborts the operation and releases the exact claim',async()=>{
|
|
127
|
+
const events=[];
|
|
128
|
+
await assert.rejects(claimWithOneTimeMigration({subjectSha256:HEX,
|
|
129
|
+
claim:async()=>({operation_id:'c'.repeat(32),release:async()=>events.push('release')}),
|
|
130
|
+
consume:async()=>{events.push('consume');throw Object.assign(new Error('replay'),{code:'EEXIST'});}
|
|
131
|
+
}),/replay/);
|
|
132
|
+
assert.deepEqual(events,['consume','release']);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test('R153 RED: an intent survives owner crash after marker deletion and completes exactly once',async()=>{
|
|
136
|
+
const input=base();
|
|
137
|
+
const proofSha256=createHash('sha256').update(canonicalJson({
|
|
138
|
+
pointer:input.pointer,policy_sha256:input.policySha256,marker_sha256:HEX,
|
|
139
|
+
authority:Object.fromEntries(planes.map(plane=>[plane,false]))
|
|
140
|
+
})).digest('hex');
|
|
141
|
+
const intent={schema:'deadbyte.r153-marker-reconciliation-intent.v1',
|
|
142
|
+
state:'RECONCILIATION_CLAIMED',predecessor:input.pointer,
|
|
143
|
+
predecessor_policy_sha256:input.policySha256,marker_sha256:HEX,
|
|
144
|
+
proof_sha256:proofSha256,authority:Object.fromEntries(planes.map(plane=>[plane,false]))};
|
|
145
|
+
intent.subject_sha256=createHash('sha256').update(canonicalJson({predecessor:intent.predecessor,
|
|
146
|
+
predecessor_policy_sha256:intent.predecessor_policy_sha256,marker_sha256:intent.marker_sha256,
|
|
147
|
+
proof_sha256:intent.proof_sha256,authority:intent.authority})).digest('hex');
|
|
148
|
+
let publications=0;
|
|
149
|
+
const result=await resumeMarkerReconciliation({...input,intent,intentSha256:'f'.repeat(64),markerPresent:false,
|
|
150
|
+
publishTerminal:async receipt=>{publications+=1;assert.equal(receipt.intent_sha256,'f'.repeat(64));return {sha256:'c'.repeat(64)};}});
|
|
151
|
+
assert.equal(result.state,'MARKER_RECONCILED');
|
|
152
|
+
assert.equal(result.marker_sha256,HEX);
|
|
153
|
+
assert.equal(publications,1);
|
|
154
|
+
await assert.rejects(resumeMarkerReconciliation({...input,intent,intentSha256:'f'.repeat(64),markerPresent:true,
|
|
155
|
+
publishTerminal:async()=>({sha256:'c'.repeat(64)})}),/marker.*present/i);
|
|
156
|
+
await assert.rejects(resumeMarkerReconciliation({...input,intent:{...intent,proof_sha256:'0'.repeat(64)},
|
|
157
|
+
intentSha256:'f'.repeat(64),markerPresent:false,publishTerminal:async()=>({sha256:'c'.repeat(64)})}),/proof|intent/i);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test('R153 RED: a published terminal survives crash before locator publication',()=>{
|
|
161
|
+
const input=base();
|
|
162
|
+
const proofSha256=createHash('sha256').update(canonicalJson({pointer:input.pointer,
|
|
163
|
+
policy_sha256:input.policySha256,marker_sha256:HEX,
|
|
164
|
+
authority:Object.fromEntries(planes.map(plane=>[plane,false]))})).digest('hex');
|
|
165
|
+
const intent={schema:'deadbyte.r153-marker-reconciliation-intent.v1',state:'RECONCILIATION_CLAIMED',
|
|
166
|
+
predecessor:input.pointer,predecessor_policy_sha256:input.policySha256,marker_sha256:HEX,
|
|
167
|
+
proof_sha256:proofSha256,authority:Object.fromEntries(planes.map(plane=>[plane,false]))};
|
|
168
|
+
intent.subject_sha256=createHash('sha256').update(canonicalJson({predecessor:intent.predecessor,
|
|
169
|
+
predecessor_policy_sha256:intent.predecessor_policy_sha256,marker_sha256:HEX,
|
|
170
|
+
proof_sha256:proofSha256,authority:intent.authority})).digest('hex');
|
|
171
|
+
const intentSha256='f'.repeat(64);
|
|
172
|
+
const terminal=createMarkerReconciliationReceipt({pointer:input.pointer,policySha256:input.policySha256,
|
|
173
|
+
markerSha256:HEX,proofSha256,intentSha256,observedAtUtc:'2026-10-24T01:00:00.000Z'});
|
|
174
|
+
const result=recoverPublishedMarkerTerminal({...input,intent,intentSha256,terminal,
|
|
175
|
+
terminalSha256:'c'.repeat(64),markerPresent:false});
|
|
176
|
+
assert.equal(result.receipt_sha256,'c'.repeat(64));
|
|
177
|
+
assert.equal(result.state,'MARKER_RECONCILED');
|
|
178
|
+
assert.throws(()=>recoverPublishedMarkerTerminal({...input,intent,intentSha256,terminal,
|
|
179
|
+
terminalSha256:'c'.repeat(64),markerPresent:true}),/marker.*present/i);
|
|
180
|
+
assert.throws(()=>recoverPublishedMarkerTerminal({...input,intent,intentSha256:'0'.repeat(64),terminal,
|
|
181
|
+
terminalSha256:'c'.repeat(64),markerPresent:false}),/intent/i);
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
test('R153 RED: UAC activation accepts only a scoped Coding generation bound to Autonomous',()=>{
|
|
185
|
+
const coding={schema:'deadbyte.coding-policy.v1',roots:{core:{path:'D:\\Projects\\deadbyte-mcp',read:true,write:true}}};
|
|
186
|
+
const codingBytes=Buffer.from(JSON.stringify(coding)+'\n');
|
|
187
|
+
const autonomous={schema:'deadbyte.autonomous-policy.v1',coding_policy_sha256:createHash('sha256').update(codingBytes).digest('hex')};
|
|
188
|
+
assert.equal(verifyNarrowedPolicyGeneration({codingPolicy:coding,codingPolicyBytes:codingBytes,autonomousPolicy:autonomous}).coding_policy_sha256,
|
|
189
|
+
autonomous.coding_policy_sha256);
|
|
190
|
+
assert.throws(()=>verifyNarrowedPolicyGeneration({codingPolicy:{...coding,roots:{...coding.roots,local_c:{path:'C:\\'}}},
|
|
191
|
+
codingPolicyBytes:codingBytes,autonomousPolicy:autonomous}),/whole-drive/i);
|
|
192
|
+
assert.throws(()=>verifyNarrowedPolicyGeneration({codingPolicy:coding,codingPolicyBytes:codingBytes,
|
|
193
|
+
autonomousPolicy:{...autonomous,coding_policy_sha256:'0'.repeat(64)}}),/digest/i);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test('R153: generated policy narrowing is proven before an active pointer can be constructed',()=>{
|
|
197
|
+
const source=readFileSync(fileURLToPath(new URL('../src/runtime-update.mjs',import.meta.url)),'utf8');
|
|
198
|
+
const generation=source.indexOf('const generation=await createGenerationBundle');
|
|
199
|
+
const narrowing=source.indexOf('verifyNarrowedPolicyGeneration({',generation);
|
|
200
|
+
const pointer=source.indexOf('newPointer={schema:',generation);
|
|
201
|
+
assert.ok(generation>=0&&narrowing>generation&&pointer>narrowing,
|
|
202
|
+
'narrowing must reject the prepared generation before pointer construction or handoff');
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test('R153: only detached activation dispatch can create an unknown mutation outcome',()=>{
|
|
206
|
+
const source=readFileSync(fileURLToPath(new URL('../src/runtime-update.mjs',import.meta.url)),'utf8');
|
|
207
|
+
const handoff=source.indexOf('const handoff=await writeHandoff');
|
|
208
|
+
const boundary=source.indexOf('activationDispatchBegan=true',handoff);
|
|
209
|
+
const spawn=source.indexOf('await spawnDetachedSupervisor',boundary);
|
|
210
|
+
const failure=source.indexOf("result:activationDispatchBegan?'MUTATION_OUTCOME_REQUIRES_RECONCILIATION'",spawn);
|
|
211
|
+
assert.ok(handoff>=0&&boundary>handoff&&spawn>boundary&&failure>spawn);
|
|
212
|
+
assert.doesNotMatch(source,/result:mutationBegan\?'MUTATION_OUTCOME_REQUIRES_RECONCILIATION'/);
|
|
213
|
+
});
|