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.
Files changed (38) hide show
  1. package/MANIFEST.SHA256 +36 -23
  2. package/PROVENANCE.json +11 -11
  3. package/README.txt +18 -18
  4. package/RELEASE.json +24 -8
  5. package/SBOM.cdx.json +2 -2
  6. package/TEST-MATRIX.json +2 -2
  7. package/bridge/package.json +1 -1
  8. package/package.json +1 -1
  9. package/scripts/bootstrap-uac-migration-r153.ps1 +232 -0
  10. package/scripts/deploy-r48.mjs +133 -11
  11. package/scripts/issue-uac-migration-r153.mjs +107 -0
  12. package/scripts/r153-prepublish-identity.mjs +57 -0
  13. package/scripts/request-uac-migration-r153.ps1 +165 -0
  14. package/scripts/verify-protected-stage-r153.mjs +20 -0
  15. package/src/activation-preflight-r65.mjs +2 -1
  16. package/src/clean-activation-recovery-r153.mjs +297 -0
  17. package/src/deadbyte-cli.mjs +925 -720
  18. package/src/foreground-process-identity.mjs +31 -0
  19. package/src/legacy-offline-policy-proof-r146.mjs +31 -10
  20. package/src/runtime-update.mjs +19 -4
  21. package/src/uac-approval-contract-r153.mjs +104 -0
  22. package/src/version.mjs +1 -1
  23. package/test/commander-protocol-r47.test.mjs +6 -1
  24. package/test/r130-release-security-contract.test.mjs +8 -7
  25. package/test/r132-release-identity.test.mjs +8 -7
  26. package/test/r146-legacy-offline-policy-proof.test.mjs +31 -1
  27. package/test/r153-clean-activation-recovery.test.mjs +213 -0
  28. package/test/r153-prepublish-identity.test.mjs +40 -0
  29. package/test/r153-restricted-reused-pid.test.mjs +31 -0
  30. package/test/r153-staging-leaf-acl.test.mjs +47 -0
  31. package/test/r153-uac-approval-contract.test.mjs +92 -0
  32. package/test/r153-uac-installed-manifest-contract.test.mjs +19 -0
  33. package/test/r153-uac-launch-parameter-contract.test.mjs +59 -0
  34. package/test/r58-concurrent-recovery.test.mjs +1 -1
  35. package/test/r59-tool-surface-compat.test.mjs +1 -1
  36. package/test/r65-activation-orchestration.test.mjs +20 -0
  37. package/test/remote-session-cli.test.mjs +1 -1
  38. 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
- const approval=await verifyMigrationApproval({approvalPath,approvalSha256,ownerPublicKeyPath,now,expected:{
186
- predecessor_release_id:pointer.release_id,predecessor_version:pointer.version,
187
- predecessor_manifest_sha256:pointer.manifest_sha256,
188
- predecessor_generation_manifest_sha256:pointer.generation_manifest_sha256,
189
- candidate_version:candidate.version,candidate_manifest_sha256:candidate.manifest_sha256,
190
- predecessor_policy_sha256:policySha256,preflight_subject_sha256:preflightSubjectSha256
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,owner_approval_subject_sha256:approval.subject_sha256,
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:true,owner_key_id:approval.owner_key_id,
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
  });
@@ -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 mutationBegan=false;
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:mutationBegan?'MUTATION_OUTCOME_REQUIRES_RECONCILIATION':'PRECONDITION_REJECTED',
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,mutationBegan?'failed_receipted':'precondition_rejected',{failure_receipt_path:failurePath,failure_receipt_sha256:failureReceipt.receipt_sha256});}
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
@@ -1,4 +1,4 @@
1
- export const DEADBYTE_VERSION = '0.15.55';
1
+ export const DEADBYTE_VERSION = '0.15.59';
2
2
  export const MCP_PROTOCOL_VERSION = '2026-07-28';
3
3
  export const RECEIPT_SCHEMA_V1 = 'deadbyte.receipt.v1';
4
4
  export const RECEIPT_SCHEMA_V2 = 'deadbyte.receipt.v2';
@@ -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',repoRoot,'-BridgeRoot',repoRoot,'-PolicyPath',policyPath],{windowsHide:true,env});
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.55');
17
- assert.equal(release.train,'R152');
18
- assert.equal(matrix.train,'R146');
19
- assert.equal(release.source_predecessor.version,'0.15.54');
20
- assert.match(release.source_predecessor.source_path,/0\.15\.54/);
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.source_predecessor_version,'0.15.54');
23
- assert.equal(provenance.source_predecessor_manifest_file_sha256,'e87712aee4fdad8f450cce2351b87675aa1c112811f2aa8e363cbe808cd49b9f');
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,'R152');
19
- assert.equal(matrix.train,'R146');
20
- assert.equal(release.source_predecessor.version,'0.15.54');
21
- assert.match(release.source_predecessor.source_path,/0\.15\.54/);
22
- assert.equal(prov.source_predecessor_version,'0.15.54');
23
- assert.equal(prov.source_predecessor_manifest_file_sha256,'e87712aee4fdad8f450cce2351b87675aa1c112811f2aa8e363cbe808cd49b9f');
24
- assert.equal(prov.parent_tarball_sha256,'bedb4be86f4c22d4c98d661a08738d5ef51027a87240a1770c8cc0b0a4885081');
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
+ });