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
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
param(
|
|
2
|
+
[Parameter(Mandatory=$true)][string]$NodePath,
|
|
3
|
+
[Parameter(Mandatory=$true)][string]$DeployScript,
|
|
4
|
+
[Parameter(Mandatory=$true)][string]$StateRoot,
|
|
5
|
+
[Parameter(Mandatory=$true)][string]$StdoutPath,
|
|
6
|
+
[Parameter(Mandatory=$true)][string]$StderrPath,
|
|
7
|
+
[Parameter(Mandatory=$true)][string]$MarkerReceiptPath,
|
|
8
|
+
[Parameter(Mandatory=$true)][ValidatePattern('^[0-9a-f]{64}$')][string]$MarkerReceiptSha256,
|
|
9
|
+
[ValidatePattern('^[0-9a-f]{64}$')][string]$PreflightSubjectSha256,
|
|
10
|
+
[switch]$Elevated
|
|
11
|
+
)
|
|
12
|
+
$ErrorActionPreference = 'Stop'
|
|
13
|
+
Set-StrictMode -Version Latest
|
|
14
|
+
function Require-AbsoluteFile([string]$Value,[string]$Label) {
|
|
15
|
+
if (-not [IO.Path]::IsPathRooted($Value)) { throw "$Label must be absolute" }
|
|
16
|
+
$item = Get-Item -LiteralPath $Value -Force
|
|
17
|
+
if ($item.PSIsContainer -or (($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0)) {
|
|
18
|
+
throw "$Label must be a regular non-reparse file"
|
|
19
|
+
}
|
|
20
|
+
return $item.FullName
|
|
21
|
+
}
|
|
22
|
+
function Full([string]$Value) { [IO.Path]::GetFullPath($Value).TrimEnd('\') }
|
|
23
|
+
$NodePath = Require-AbsoluteFile $NodePath 'Node executable'
|
|
24
|
+
$DeployScript = Require-AbsoluteFile $DeployScript 'Deploy script'
|
|
25
|
+
$MarkerReceiptPath = Require-AbsoluteFile $MarkerReceiptPath 'Marker reconciliation receipt'
|
|
26
|
+
$helperDir = Split-Path -Parent $PSCommandPath
|
|
27
|
+
$expectedDeploy = Join-Path $helperDir 'deploy-r48.mjs'
|
|
28
|
+
$issuerScript = Require-AbsoluteFile (Join-Path $helperDir 'issue-uac-migration-r153.mjs') 'Protected UAC issuer'
|
|
29
|
+
if ((Full $DeployScript) -ine (Full $expectedDeploy)) { throw 'UAC deploy path is not sibling of helper' }
|
|
30
|
+
$expectedNode = Join-Path $env:ProgramFiles 'nodejs\node.exe'
|
|
31
|
+
if ((Full $NodePath) -ine (Full $expectedNode)) { throw 'UAC Node.js path is not pinned to Program Files' }
|
|
32
|
+
if (-not (Full $helperDir).StartsWith(((Full $env:ProgramFiles) + '\'),[StringComparison]::OrdinalIgnoreCase)) {
|
|
33
|
+
throw 'UAC helper must be in protected Program Files installation'
|
|
34
|
+
}
|
|
35
|
+
if (-not [IO.Path]::IsPathRooted($StateRoot)) { throw 'State root must be absolute' }
|
|
36
|
+
if (-not [IO.Path]::IsPathRooted($StdoutPath) -or -not [IO.Path]::IsPathRooted($StderrPath)) {
|
|
37
|
+
throw 'Evidence paths must be absolute'
|
|
38
|
+
}
|
|
39
|
+
# An elevated UAC helper must not become an arbitrary file-writing primitive.
|
|
40
|
+
# Constrain both evidence files to one unique, caller-owned state directory.
|
|
41
|
+
$expectedStateRoot = Join-Path $env:USERPROFILE '.deadbyte-mcp'
|
|
42
|
+
if ((Full $StateRoot) -ine (Full $expectedStateRoot)) {
|
|
43
|
+
throw 'R153 UAC requires the owning user state root'
|
|
44
|
+
}
|
|
45
|
+
$evidenceDirectory = Join-Path $StateRoot 'evidence\legacy-migration'
|
|
46
|
+
if ((Full (Split-Path -Parent $StdoutPath)) -ine (Full $evidenceDirectory) -or
|
|
47
|
+
(Full (Split-Path -Parent $StderrPath)) -ine (Full $evidenceDirectory)) {
|
|
48
|
+
throw 'UAC evidence paths must remain inside state legacy-migration evidence'
|
|
49
|
+
}
|
|
50
|
+
$stdoutName = [IO.Path]::GetFileName($StdoutPath)
|
|
51
|
+
$stderrName = [IO.Path]::GetFileName($StderrPath)
|
|
52
|
+
if ($stdoutName -cnotmatch '^r153-(\d{13}-[0-9a-f]{16})\.stdout\.log$') {
|
|
53
|
+
throw 'UAC stdout evidence name invalid'
|
|
54
|
+
}
|
|
55
|
+
$id = $Matches[1]
|
|
56
|
+
if ($stderrName -cne ('r153-' + $id + '.stderr.log')) {
|
|
57
|
+
throw 'UAC stderr evidence identity mismatch'
|
|
58
|
+
}
|
|
59
|
+
$ancestor = [IO.Path]::GetFullPath($evidenceDirectory)
|
|
60
|
+
while ($true) {
|
|
61
|
+
$item = Get-Item -LiteralPath $ancestor -Force -ErrorAction Stop
|
|
62
|
+
if (($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) {
|
|
63
|
+
throw 'UAC evidence directory ancestry contains reparse point'
|
|
64
|
+
}
|
|
65
|
+
$parent = [IO.Directory]::GetParent($ancestor)
|
|
66
|
+
if ($null -eq $parent) { break }
|
|
67
|
+
$ancestor = $parent.FullName
|
|
68
|
+
}
|
|
69
|
+
if ((Test-Path -LiteralPath $StdoutPath) -or (Test-Path -LiteralPath $StderrPath)) {
|
|
70
|
+
throw 'UAC evidence files must not pre-exist'
|
|
71
|
+
}
|
|
72
|
+
function Write-NoClobberEvidence([string]$File,[string]$Content) {
|
|
73
|
+
# CreateNew also rejects an attacker-precreated file or leaf symlink.
|
|
74
|
+
$utf8 = [Text.UTF8Encoding]::new($false)
|
|
75
|
+
$bytes = $utf8.GetBytes($Content)
|
|
76
|
+
if ($bytes.Length -gt 131072) { throw 'UAC evidence output exceeds 128 KiB bound' }
|
|
77
|
+
$stream = [IO.FileStream]::new($File,[IO.FileMode]::CreateNew,[IO.FileAccess]::Write,[IO.FileShare]::None)
|
|
78
|
+
try { $stream.Write($bytes,0,$bytes.Length); $stream.Flush($true) }
|
|
79
|
+
finally { $stream.Dispose() }
|
|
80
|
+
}
|
|
81
|
+
if (-not $Elevated) {
|
|
82
|
+
# Dry run cannot issue approval or consume nonce. It returns the live preflight identity.
|
|
83
|
+
$preflightOutput = & $NodePath $DeployScript '--preflight'
|
|
84
|
+
if ($LASTEXITCODE -ne 0) { throw 'Unprivileged activation preflight failed' }
|
|
85
|
+
$preflight = ($preflightOutput -join "`n") | ConvertFrom-Json
|
|
86
|
+
if ($preflight.state -ne 'AUTHORITY_UNVERIFIED' -or
|
|
87
|
+
$preflight.subject_sha256 -notmatch '^[0-9a-f]{64}$') {
|
|
88
|
+
throw "UAC migration not applicable: $($preflight.state)"
|
|
89
|
+
}
|
|
90
|
+
$PreflightSubjectSha256 = $preflight.subject_sha256
|
|
91
|
+
$powershell = Join-Path $env:SystemRoot 'System32\WindowsPowerShell\v1.0\powershell.exe'
|
|
92
|
+
$arguments = @(
|
|
93
|
+
'-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',('"' + $PSCommandPath + '"'),
|
|
94
|
+
'-Elevated','-NodePath',('"' + $NodePath + '"'),'-DeployScript',('"' + $DeployScript + '"'),
|
|
95
|
+
'-StateRoot',('"' + $StateRoot + '"'),'-StdoutPath',('"' + $StdoutPath + '"'),'-StderrPath',('"' + $StderrPath + '"'),
|
|
96
|
+
'-MarkerReceiptPath',('"' + $MarkerReceiptPath + '"'),'-MarkerReceiptSha256',$MarkerReceiptSha256,
|
|
97
|
+
'-PreflightSubjectSha256',$PreflightSubjectSha256
|
|
98
|
+
)
|
|
99
|
+
# -Verb RunAs is the UseShellExecute parameter set. PowerShell 5.1 rejects
|
|
100
|
+
# -RedirectStandardOutput/-RedirectStandardError in that same Start-Process.
|
|
101
|
+
# The elevated child writes bounded, no-clobber evidence instead.
|
|
102
|
+
$child = Start-Process -FilePath $powershell -ArgumentList $arguments -Verb RunAs -Wait -PassThru -WindowStyle Hidden
|
|
103
|
+
if (Test-Path -LiteralPath $StdoutPath) { [Console]::Out.Write([IO.File]::ReadAllText($StdoutPath)) }
|
|
104
|
+
if (Test-Path -LiteralPath $StderrPath) { [Console]::Error.Write([IO.File]::ReadAllText($StderrPath)) }
|
|
105
|
+
exit $child.ExitCode
|
|
106
|
+
}
|
|
107
|
+
$principal = [Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()
|
|
108
|
+
if (-not $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)) {
|
|
109
|
+
throw 'UAC elevation was not established'
|
|
110
|
+
}
|
|
111
|
+
if ($PreflightSubjectSha256 -notmatch '^[0-9a-f]{64}$') { throw 'UAC preflight subject missing' }
|
|
112
|
+
$env:DEADBYTE_STATE_ROOT = [IO.Path]::GetFullPath($StateRoot)
|
|
113
|
+
$exitCode = 2
|
|
114
|
+
$stdoutText = ''
|
|
115
|
+
$stderrText = ''
|
|
116
|
+
try {
|
|
117
|
+
# Run native children in the already-elevated process. Merged output is
|
|
118
|
+
# bounded before durable logging; no shell redirection uses elevated paths.
|
|
119
|
+
$priorErrorAction = $ErrorActionPreference
|
|
120
|
+
$ErrorActionPreference = 'Continue'
|
|
121
|
+
try {
|
|
122
|
+
$issuerOutput = @(& $NodePath $issuerScript '--state-root' $env:DEADBYTE_STATE_ROOT `
|
|
123
|
+
'--marker-receipt' $MarkerReceiptPath '--marker-receipt-sha256' $MarkerReceiptSha256 `
|
|
124
|
+
'--preflight-subject-sha256' $PreflightSubjectSha256 2>&1)
|
|
125
|
+
$issuerExit = $LASTEXITCODE
|
|
126
|
+
} finally { $ErrorActionPreference = $priorErrorAction }
|
|
127
|
+
$issuerText = (@($issuerOutput) | ForEach-Object { [string]$_ }) -join "`n"
|
|
128
|
+
if ($issuerExit -ne 0) { throw "R153 UAC issuer failed exit=$issuerExit : $issuerText" }
|
|
129
|
+
$issued = $issuerText | ConvertFrom-Json
|
|
130
|
+
if ($issued.schema -ne 'deadbyte.r153-uac-issuer-result.v1' -or
|
|
131
|
+
$issued.nonce -notmatch '^[0-9a-f]{64}$' -or
|
|
132
|
+
$issued.receipt_sha256 -notmatch '^[0-9a-f]{64}$') {
|
|
133
|
+
throw 'R153 UAC issuer result invalid'
|
|
134
|
+
}
|
|
135
|
+
$priorErrorAction = $ErrorActionPreference
|
|
136
|
+
$ErrorActionPreference = 'Continue'
|
|
137
|
+
try {
|
|
138
|
+
$deployOutput = @(& $NodePath $DeployScript '--uac-legacy-migration' `
|
|
139
|
+
'--marker-reconciliation-receipt' $MarkerReceiptPath `
|
|
140
|
+
'--marker-reconciliation-receipt-sha256' $MarkerReceiptSha256 `
|
|
141
|
+
'--migration-nonce' $issued.nonce `
|
|
142
|
+
'--migration-issued-at' $issued.issued_at_utc `
|
|
143
|
+
'--migration-expires-at' $issued.expires_at_utc `
|
|
144
|
+
'--uac-approval-receipt' $issued.receipt_path `
|
|
145
|
+
'--uac-approval-receipt-sha256' $issued.receipt_sha256 2>&1)
|
|
146
|
+
$deployExit = $LASTEXITCODE
|
|
147
|
+
} finally { $ErrorActionPreference = $priorErrorAction }
|
|
148
|
+
$deployText = (@($deployOutput) | ForEach-Object { [string]$_ }) -join "`n"
|
|
149
|
+
if ($deployExit -ne 0) {
|
|
150
|
+
$stderrText = "R153 activation rejected exit=$deployExit : $deployText"
|
|
151
|
+
$exitCode = 2
|
|
152
|
+
} else {
|
|
153
|
+
$stdoutText = $deployText
|
|
154
|
+
$exitCode = 0
|
|
155
|
+
}
|
|
156
|
+
} catch {
|
|
157
|
+
$stderrText = 'R153 UAC failed: ' + [string]$_.Exception.Message
|
|
158
|
+
$exitCode = 2
|
|
159
|
+
} finally {
|
|
160
|
+
# A successful issuer cannot be mistaken for a successful activation:
|
|
161
|
+
# success is reported only after the deploy process exited with code zero.
|
|
162
|
+
Write-NoClobberEvidence $StdoutPath $stdoutText
|
|
163
|
+
Write-NoClobberEvidence $StderrPath $stderrText
|
|
164
|
+
}
|
|
165
|
+
exit $exitCode
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// First candidate JavaScript executed by the elevated R153 bootstrap.
|
|
2
|
+
// Package bytes have already been independently checked by PowerShell; this
|
|
3
|
+
// adds the repository's R89 OS ACL verdict before any activation code runs.
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import {fileURLToPath} from 'node:url';
|
|
6
|
+
import {inspectTrustedInstallation,privilegedEpochInstallFiles} from '../src/privileged-install-trust-r89.mjs';
|
|
7
|
+
|
|
8
|
+
const root=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');
|
|
9
|
+
const files=[...privilegedEpochInstallFiles(root,process.execPath),
|
|
10
|
+
fileURLToPath(import.meta.url),
|
|
11
|
+
path.join(root,'scripts','deploy-r48.mjs'),
|
|
12
|
+
path.join(root,'scripts','request-uac-migration-r153.ps1'),
|
|
13
|
+
path.join(root,'scripts','issue-uac-migration-r153.mjs'),
|
|
14
|
+
path.join(root,'package.json'),path.join(root,'MANIFEST.SHA256'),path.join(root,'RELEASE.json'),
|
|
15
|
+
path.join(root,'src','uac-approval-contract-r153.mjs'),
|
|
16
|
+
path.join(root,'src','clean-activation-recovery-r153.mjs'),
|
|
17
|
+
path.join(root,'src','legacy-offline-policy-proof-r146.mjs')];
|
|
18
|
+
const verdict=await inspectTrustedInstallation(files);
|
|
19
|
+
if(verdict.trusted!==true)throw new Error('R153 protected stage trust rejected');
|
|
20
|
+
process.stdout.write(JSON.stringify({schema:'deadbyte.r153-protected-stage-verdict.v1',trusted:true,reason:verdict.reason})+'\n');
|
|
@@ -94,7 +94,8 @@ function validateLegacyOfflineRecoveryProof(proof,{preflightSubjectSha256,candid
|
|
|
94
94
|
if(!proof||proof.state!=='VERIFIED_OFFLINE_LEGACY_SCOPE_REPAIR'||
|
|
95
95
|
proof.preflight_subject_sha256!==preflightSubjectSha256||
|
|
96
96
|
proof.candidate_manifest_sha256!==candidate.manifest_sha256||
|
|
97
|
-
proof.owner_approval_verified!==true
|
|
97
|
+
(proof.owner_approval_verified!==true&&proof.migration_authorization_verified!==true)||
|
|
98
|
+
!HEX64.test(String(proof.subject_sha256??'')))
|
|
98
99
|
throw new Error('legacy offline scope recovery proof invalid or unbound');
|
|
99
100
|
return proof;
|
|
100
101
|
}
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import {createHash} from 'node:crypto';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import {canonicalJson} from './canonical-json.mjs';
|
|
4
|
+
|
|
5
|
+
const PLANES=Object.freeze(['process','coding','autonomous','desktop','host']);
|
|
6
|
+
const HEX64=/^[0-9a-f]{64}$/;
|
|
7
|
+
const sha=value=>createHash('sha256').update(Buffer.from(canonicalJson(value),'utf8')).digest('hex');
|
|
8
|
+
const allDisarmed=Object.freeze(Object.fromEntries(PLANES.map(plane=>[plane,false])));
|
|
9
|
+
|
|
10
|
+
function reject(reason,subject=null){
|
|
11
|
+
return Object.freeze({state:'MIGRATION_REJECTED',safe_to_migrate:false,reason,
|
|
12
|
+
subject_sha256:subject?sha(subject):null});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function migrationSubject(input){
|
|
16
|
+
return {
|
|
17
|
+
schema:'deadbyte.r153-legacy-migration-preflight.v1',
|
|
18
|
+
action:'narrow_legacy_policy_and_activate',
|
|
19
|
+
state_root:path.win32.resolve(input.stateRoot),
|
|
20
|
+
predecessor:input.pointer,
|
|
21
|
+
candidate:input.candidate,
|
|
22
|
+
predecessor_policy_sha256:input.policySha256,
|
|
23
|
+
marker_sha256:input.marker?.sha256??null,
|
|
24
|
+
allowed_authority_after:allDisarmed
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function classifyLegacyMigration(input){
|
|
29
|
+
if(!input||typeof input!=='object'||typeof input.stateRoot!=='string'||!path.win32.isAbsolute(input.stateRoot))
|
|
30
|
+
return reject('state root invalid');
|
|
31
|
+
const subject=migrationSubject(input);
|
|
32
|
+
if(!input.pointer||input.pointer.version!=='0.15.45'||
|
|
33
|
+
!HEX64.test(String(input.pointer.manifest_sha256??''))||!HEX64.test(String(input.pointer.generation_manifest_sha256??'')))
|
|
34
|
+
return reject('predecessor identity invalid',subject);
|
|
35
|
+
if(!input.candidate||input.candidate.version!=='0.15.59'||
|
|
36
|
+
!HEX64.test(String(input.candidate.manifest_sha256??''))||!HEX64.test(String(input.candidate.tarball_sha256??'')))
|
|
37
|
+
return reject('candidate identity invalid',subject);
|
|
38
|
+
if(PLANES.some(plane=>!HEX64.test(String(input.policySha256?.[plane]??''))))
|
|
39
|
+
return reject('policy identity incomplete',subject);
|
|
40
|
+
const observed=input.observation;
|
|
41
|
+
if(observed?.state!=='OFFLINE'||observed.local_ready!==false||
|
|
42
|
+
observed.bridge?.alive!==false||observed.core?.alive!==false||observed.cloud?.alive!==false||
|
|
43
|
+
observed.supervisor?.running!==false)
|
|
44
|
+
return reject('runtime is not cleanly offline',subject);
|
|
45
|
+
for(const plane of PLANES){
|
|
46
|
+
const entry=observed.authority?.[plane];
|
|
47
|
+
if(entry?.configured!==true||entry.armed!==false)
|
|
48
|
+
return reject(`${plane} authority is not physically inactive`,subject);
|
|
49
|
+
if(input.grantPresence?.[plane]!==false)
|
|
50
|
+
return reject(`${plane} grant absence unverified`,subject);
|
|
51
|
+
}
|
|
52
|
+
if(input.marker&&(!HEX64.test(String(input.marker.sha256??''))||
|
|
53
|
+
input.marker.owner_live!==false||input.marker.guardian_live!==false))
|
|
54
|
+
return reject('foreground ownership is live or unverified',subject);
|
|
55
|
+
const roots=Array.isArray(input.codingRootPaths)?input.codingRootPaths:[];
|
|
56
|
+
const drives=new Set(roots.map(value=>path.win32.normalize(String(value)).toLowerCase())
|
|
57
|
+
.filter(value=>/^[a-z]:\\$/.test(value)));
|
|
58
|
+
if(!drives.has('c:\\')||!drives.has('d:\\'))
|
|
59
|
+
return reject('legacy broad-root signature not present',subject);
|
|
60
|
+
return Object.freeze({state:'MIGRATION_CONSENT_REQUIRED',safe_to_migrate:true,
|
|
61
|
+
subject_sha256:sha(subject),subject:Object.freeze(subject)});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function parseInstant(value,label){
|
|
65
|
+
if(typeof value!=='string'||new Date(value).toISOString()!==value)throw new Error(`${label} invalid`);
|
|
66
|
+
return Date.parse(value);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function createLegacyMigrationSubject({classification,nonce,issuedAt,expiresAt,...input}){
|
|
70
|
+
if(classification?.state!=='MIGRATION_CONSENT_REQUIRED'||classification.safe_to_migrate!==true)
|
|
71
|
+
throw new Error('migration classification is not consentable');
|
|
72
|
+
if(!HEX64.test(String(nonce??'')))throw new Error('migration nonce invalid');
|
|
73
|
+
const issued=parseInstant(issuedAt,'migration issued time');
|
|
74
|
+
const expires=parseInstant(expiresAt,'migration expiry');
|
|
75
|
+
if(expires<=issued||expires-issued>600_000)throw new Error('migration lifetime invalid');
|
|
76
|
+
const base=migrationSubject(input);
|
|
77
|
+
if(sha(base)!==classification.subject_sha256)throw new Error('migration preflight subject changed');
|
|
78
|
+
return Object.freeze({...base,schema:'deadbyte.r153-uac-migration-subject.v1',
|
|
79
|
+
preflight_subject_sha256:classification.subject_sha256,nonce,issued_at_utc:issuedAt,
|
|
80
|
+
expires_at_utc:expiresAt});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function authorizeElevatedLegacyMigration({subject,isElevated,now=new Date()}){
|
|
84
|
+
if(isElevated!==true)throw new Error('legacy migration requires verified Windows elevation');
|
|
85
|
+
if(subject?.schema!=='deadbyte.r153-uac-migration-subject.v1'||
|
|
86
|
+
subject.action!=='narrow_legacy_policy_and_activate'||!HEX64.test(String(subject.nonce??''))||
|
|
87
|
+
!HEX64.test(String(subject.preflight_subject_sha256??'')))throw new Error('legacy migration subject invalid');
|
|
88
|
+
if(PLANES.some(plane=>subject.allowed_authority_after?.[plane]!==false))
|
|
89
|
+
throw new Error('legacy migration may not grant authority');
|
|
90
|
+
const issued=parseInstant(subject.issued_at_utc,'migration issued time');
|
|
91
|
+
const expires=parseInstant(subject.expires_at_utc,'migration expiry');
|
|
92
|
+
const observed=now instanceof Date?now.getTime():NaN;
|
|
93
|
+
if(!Number.isFinite(observed)||expires<=issued||expires-issued>600_000||observed<issued||observed>=expires)
|
|
94
|
+
throw new Error('legacy migration authorization expired or invalid');
|
|
95
|
+
return Object.freeze({state:'MIGRATION_APPROVED',authorization:'windows_uac_elevated',
|
|
96
|
+
subject_sha256:sha(subject),preflight_subject_sha256:subject.preflight_subject_sha256,
|
|
97
|
+
nonce:subject.nonce,expires_at_utc:subject.expires_at_utc,allowed_authority_after:allDisarmed});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export async function reconcileStaleMarker(options){
|
|
101
|
+
const {deleteIfHash,publishIntent,publishTerminal,revalidate,...input}=options??{};
|
|
102
|
+
if(typeof deleteIfHash!=='function')throw new Error('marker CAS deletion unavailable');
|
|
103
|
+
if(typeof publishIntent!=='function'||typeof publishTerminal!=='function')
|
|
104
|
+
throw new Error('marker reconciliation durable publication unavailable');
|
|
105
|
+
if(typeof revalidate!=='function')throw new Error('marker reconciliation revalidation unavailable');
|
|
106
|
+
const observed=input.observation;
|
|
107
|
+
if(!input.pointer||!HEX64.test(String(input.pointer.manifest_sha256??''))||
|
|
108
|
+
!HEX64.test(String(input.pointer.generation_manifest_sha256??'')))
|
|
109
|
+
throw new Error('stale marker predecessor identity invalid');
|
|
110
|
+
if(PLANES.some(plane=>!HEX64.test(String(input.policySha256?.[plane]??''))))
|
|
111
|
+
throw new Error('stale marker policy identity incomplete');
|
|
112
|
+
if(observed?.state!=='OFFLINE'||observed.local_ready!==false||
|
|
113
|
+
observed.bridge?.alive!==false||observed.core?.alive!==false||observed.cloud?.alive!==false||
|
|
114
|
+
observed.supervisor?.running!==false)
|
|
115
|
+
throw new Error('stale marker runtime is not cleanly offline');
|
|
116
|
+
for(const plane of PLANES){
|
|
117
|
+
if(observed.authority?.[plane]?.configured!==true||observed.authority?.[plane]?.armed!==false)
|
|
118
|
+
throw new Error(`stale marker ${plane} authority is not physically inactive`);
|
|
119
|
+
if(input.grantPresence?.[plane]!==false)throw new Error(`stale marker ${plane} grant absence unverified`);
|
|
120
|
+
}
|
|
121
|
+
if(!input.marker||!HEX64.test(String(input.marker.sha256??'')))throw new Error('stale marker snapshot missing');
|
|
122
|
+
if(input.marker.owner_live!==false||input.marker.guardian_live!==false)
|
|
123
|
+
throw new Error('stale marker ownership is live or unverified');
|
|
124
|
+
const proofSha256=sha({pointer:input.pointer,policy_sha256:input.policySha256,
|
|
125
|
+
marker_sha256:input.marker.sha256,authority:Object.fromEntries(PLANES.map(plane=>[plane,false]))});
|
|
126
|
+
const intent=Object.freeze({schema:'deadbyte.r153-marker-reconciliation-intent.v1',
|
|
127
|
+
state:'RECONCILIATION_CLAIMED',predecessor:input.pointer,
|
|
128
|
+
predecessor_policy_sha256:input.policySha256,marker_sha256:input.marker.sha256,
|
|
129
|
+
proof_sha256:proofSha256,authority:allDisarmed,
|
|
130
|
+
subject_sha256:sha({predecessor:input.pointer,predecessor_policy_sha256:input.policySha256,
|
|
131
|
+
marker_sha256:input.marker.sha256,proof_sha256:proofSha256,authority:allDisarmed})});
|
|
132
|
+
const intentEvidence=await publishIntent(intent);
|
|
133
|
+
if(!intentEvidence||!HEX64.test(String(intentEvidence.sha256??'')))
|
|
134
|
+
throw new Error('marker reconciliation intent publication unverified');
|
|
135
|
+
await revalidate('before_delete',input);
|
|
136
|
+
const result=await deleteIfHash(input.marker.sha256);
|
|
137
|
+
if(result==='CHANGED')throw new Error('stale marker changed before CAS deletion');
|
|
138
|
+
// An observed marker must be removed by this CAS operation. Treat MISSING as
|
|
139
|
+
// an ownership race, not as evidence that the expected bytes were deleted.
|
|
140
|
+
if(result!=='REMOVED')throw new Error('stale marker CAS did not remove observed marker');
|
|
141
|
+
await revalidate('after_delete',input);
|
|
142
|
+
const terminal=createMarkerReconciliationReceipt({pointer:input.pointer,policySha256:input.policySha256,
|
|
143
|
+
markerSha256:input.marker.sha256,proofSha256,observedAtUtc:new Date().toISOString(),
|
|
144
|
+
intentSha256:intentEvidence.sha256});
|
|
145
|
+
const terminalEvidence=await publishTerminal(terminal);
|
|
146
|
+
if(!terminalEvidence||!HEX64.test(String(terminalEvidence.sha256??'')))
|
|
147
|
+
throw new Error('marker reconciliation terminal publication unverified');
|
|
148
|
+
return Object.freeze({...terminal,receipt_sha256:terminalEvidence.sha256});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// A crash after the content-CAS delete but before terminal publication leaves
|
|
152
|
+
// an immutable intent. Resume only from that exact intent after independently
|
|
153
|
+
// proving the marker is absent and the same predecessor remains OFFLINE with
|
|
154
|
+
// every grant absent. This closes the crash boundary without replaying deletion.
|
|
155
|
+
export async function resumeMarkerReconciliation(options){
|
|
156
|
+
const {intent,intentSha256,markerPresent,publishTerminal,...input}=options??{};
|
|
157
|
+
if(typeof publishTerminal!=='function')throw new Error('marker reconciliation terminal publication unavailable');
|
|
158
|
+
if(!HEX64.test(String(intentSha256??'')))throw new Error('marker reconciliation intent identity invalid');
|
|
159
|
+
if(markerPresent!==false)throw new Error('stale marker is present; CAS reconciliation required');
|
|
160
|
+
if(intent?.schema!=='deadbyte.r153-marker-reconciliation-intent.v1'||
|
|
161
|
+
intent.state!=='RECONCILIATION_CLAIMED'||!HEX64.test(String(intent.marker_sha256??''))||
|
|
162
|
+
!HEX64.test(String(intent.proof_sha256??''))||!HEX64.test(String(intent.subject_sha256??'')))
|
|
163
|
+
throw new Error('marker reconciliation intent invalid');
|
|
164
|
+
const observed=input.observation;
|
|
165
|
+
if(observed?.state!=='OFFLINE'||observed.local_ready!==false||
|
|
166
|
+
observed.bridge?.alive!==false||observed.core?.alive!==false||observed.cloud?.alive!==false||
|
|
167
|
+
observed.supervisor?.running!==false)throw new Error('marker reconciliation runtime is not cleanly offline');
|
|
168
|
+
for(const plane of PLANES){
|
|
169
|
+
if(observed.authority?.[plane]?.configured!==true||observed.authority?.[plane]?.armed!==false)
|
|
170
|
+
throw new Error(`marker reconciliation ${plane} authority is not physically inactive`);
|
|
171
|
+
if(input.grantPresence?.[plane]!==false)throw new Error(`marker reconciliation ${plane} grant absence unverified`);
|
|
172
|
+
}
|
|
173
|
+
const expectedProof=sha({pointer:input.pointer,policy_sha256:input.policySha256,
|
|
174
|
+
marker_sha256:intent.marker_sha256,authority:Object.fromEntries(PLANES.map(plane=>[plane,false]))});
|
|
175
|
+
const expectedSubject=sha({predecessor:input.pointer,predecessor_policy_sha256:input.policySha256,
|
|
176
|
+
marker_sha256:intent.marker_sha256,proof_sha256:expectedProof,authority:allDisarmed});
|
|
177
|
+
if(canonicalJson(intent.predecessor)!==canonicalJson(input.pointer)||
|
|
178
|
+
canonicalJson(intent.predecessor_policy_sha256)!==canonicalJson(input.policySha256)||
|
|
179
|
+
canonicalJson(intent.authority)!==canonicalJson(allDisarmed)||
|
|
180
|
+
intent.proof_sha256!==expectedProof||intent.subject_sha256!==expectedSubject)
|
|
181
|
+
throw new Error('marker reconciliation intent proof or predecessor drift');
|
|
182
|
+
const terminal=createMarkerReconciliationReceipt({pointer:input.pointer,policySha256:input.policySha256,
|
|
183
|
+
markerSha256:intent.marker_sha256,proofSha256:expectedProof,observedAtUtc:new Date().toISOString(),intentSha256});
|
|
184
|
+
const terminalEvidence=await publishTerminal(terminal);
|
|
185
|
+
if(!terminalEvidence||!HEX64.test(String(terminalEvidence.sha256??'')))
|
|
186
|
+
throw new Error('marker reconciliation terminal publication unverified');
|
|
187
|
+
return Object.freeze({...terminal,receipt_sha256:terminalEvidence.sha256});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function markerReceiptSubject({pointer,policySha256,markerSha256,proofSha256,observedAtUtc,intentSha256}){
|
|
191
|
+
if(!pointer||!HEX64.test(String(pointer.manifest_sha256??''))||
|
|
192
|
+
!HEX64.test(String(pointer.generation_manifest_sha256??'')))throw new Error('marker receipt predecessor identity invalid');
|
|
193
|
+
if(PLANES.some(plane=>!HEX64.test(String(policySha256?.[plane]??''))))throw new Error('marker receipt policy identity incomplete');
|
|
194
|
+
if(!HEX64.test(String(markerSha256??'')))throw new Error('marker receipt marker identity invalid');
|
|
195
|
+
if(!HEX64.test(String(proofSha256??'')))throw new Error('marker receipt proof identity invalid');
|
|
196
|
+
if(!HEX64.test(String(intentSha256??'')))throw new Error('marker receipt intent identity invalid');
|
|
197
|
+
parseInstant(observedAtUtc,'marker receipt observed time');
|
|
198
|
+
return Object.freeze({schema:'deadbyte.r153-marker-reconciliation.v1',state:'MARKER_RECONCILED',
|
|
199
|
+
predecessor:pointer,predecessor_policy_sha256:policySha256,marker_sha256:markerSha256,
|
|
200
|
+
proof_sha256:proofSha256,intent_sha256:intentSha256,observed_at_utc:observedAtUtc,
|
|
201
|
+
authority:Object.freeze({...allDisarmed})});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function createMarkerReconciliationReceipt(input){
|
|
205
|
+
const subject=markerReceiptSubject(input);
|
|
206
|
+
return Object.freeze({...subject,subject_sha256:sha(subject)});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function verifyMarkerReconciliationReceipt(receipt,{pointer,policySha256,markerSha256}){
|
|
210
|
+
if(receipt?.schema!=='deadbyte.r153-marker-reconciliation.v1'||receipt.state!=='MARKER_RECONCILED')
|
|
211
|
+
throw new Error('marker reconciliation receipt schema invalid');
|
|
212
|
+
if(receipt.marker_sha256!==markerSha256)throw new Error('marker reconciliation receipt marker mismatch');
|
|
213
|
+
const expectedProof=sha({pointer,policy_sha256:policySha256,marker_sha256:markerSha256,
|
|
214
|
+
authority:Object.fromEntries(PLANES.map(plane=>[plane,false]))});
|
|
215
|
+
if(receipt.proof_sha256!==expectedProof)
|
|
216
|
+
throw new Error('marker reconciliation receipt proof binding mismatch');
|
|
217
|
+
const expected=markerReceiptSubject({pointer,policySha256,markerSha256,
|
|
218
|
+
proofSha256:receipt.proof_sha256,intentSha256:receipt.intent_sha256,
|
|
219
|
+
observedAtUtc:receipt.observed_at_utc});
|
|
220
|
+
if(canonicalJson(receipt.predecessor)!==canonicalJson(pointer)||
|
|
221
|
+
canonicalJson(receipt.predecessor_policy_sha256)!==canonicalJson(policySha256))
|
|
222
|
+
throw new Error('marker reconciliation receipt predecessor drift');
|
|
223
|
+
const digest=sha(expected);
|
|
224
|
+
if(receipt.subject_sha256!==digest)throw new Error('marker reconciliation receipt subject mismatch');
|
|
225
|
+
return digest;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export function recoverPublishedMarkerTerminal(options){
|
|
229
|
+
const {intent,intentSha256,terminal,terminalSha256,markerPresent,...input}=options??{};
|
|
230
|
+
if(markerPresent!==false)throw new Error('stale marker is present; published terminal cannot be adopted');
|
|
231
|
+
if(!HEX64.test(String(intentSha256??''))||!HEX64.test(String(terminalSha256??'')))
|
|
232
|
+
throw new Error('published marker terminal evidence identity invalid');
|
|
233
|
+
if(intent?.schema!=='deadbyte.r153-marker-reconciliation-intent.v1'||
|
|
234
|
+
intent.state!=='RECONCILIATION_CLAIMED'||intent.marker_sha256!==terminal?.marker_sha256||
|
|
235
|
+
terminal?.intent_sha256!==intentSha256)throw new Error('published marker terminal intent binding mismatch');
|
|
236
|
+
const observed=input.observation;
|
|
237
|
+
if(observed?.state!=='OFFLINE'||observed.local_ready!==false||
|
|
238
|
+
observed.bridge?.alive!==false||observed.core?.alive!==false||observed.cloud?.alive!==false||
|
|
239
|
+
observed.supervisor?.running!==false)throw new Error('published marker terminal runtime is not cleanly offline');
|
|
240
|
+
for(const plane of PLANES){
|
|
241
|
+
if(observed.authority?.[plane]?.configured!==true||observed.authority?.[plane]?.armed!==false)
|
|
242
|
+
throw new Error(`published marker terminal ${plane} authority is not physically inactive`);
|
|
243
|
+
if(input.grantPresence?.[plane]!==false)
|
|
244
|
+
throw new Error(`published marker terminal ${plane} grant absence unverified`);
|
|
245
|
+
}
|
|
246
|
+
verifyMarkerReconciliationReceipt(terminal,{pointer:input.pointer,policySha256:input.policySha256,
|
|
247
|
+
markerSha256:intent.marker_sha256});
|
|
248
|
+
return Object.freeze({...terminal,receipt_sha256:terminalSha256});
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export function verifyNarrowedPolicyGeneration({codingPolicy,codingPolicyBytes,autonomousPolicy}){
|
|
252
|
+
if(!codingPolicy||codingPolicy.schema!=='deadbyte.coding-policy.v1'||!Buffer.isBuffer(codingPolicyBytes)||
|
|
253
|
+
!autonomousPolicy||autonomousPolicy.schema!=='deadbyte.autonomous-policy.v1')
|
|
254
|
+
throw new Error('narrowed policy generation inputs invalid');
|
|
255
|
+
for(const [id,root] of Object.entries(codingPolicy.roots??{})){
|
|
256
|
+
if(typeof root?.path!=='string'||!path.win32.isAbsolute(root.path))
|
|
257
|
+
throw new Error(`narrowed policy root invalid: ${id}`);
|
|
258
|
+
const normalized=path.win32.normalize(root.path),drive=path.win32.parse(normalized).root;
|
|
259
|
+
if(/^[a-z]:\\$/i.test(drive)&&normalized.toLowerCase()===drive.toLowerCase())
|
|
260
|
+
throw new Error(`narrowed policy contains whole-drive root: ${id}`);
|
|
261
|
+
}
|
|
262
|
+
const codingSha256=createHash('sha256').update(codingPolicyBytes).digest('hex');
|
|
263
|
+
if(autonomousPolicy.coding_policy_sha256!==codingSha256)
|
|
264
|
+
throw new Error('Autonomous coding policy digest mismatch after narrowing');
|
|
265
|
+
return Object.freeze({coding_policy_sha256:codingSha256,root_count:Object.keys(codingPolicy.roots??{}).length});
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// The activation claim is acquired before one-time nonce consumption. If
|
|
269
|
+
// consumption fails, release only the claim we just acquired and abort.
|
|
270
|
+
export async function claimWithOneTimeMigration({claim,subjectSha256,consume=null}){
|
|
271
|
+
if(typeof claim!=='function'||!HEX64.test(String(subjectSha256??'')))
|
|
272
|
+
throw new Error('activation claim callback or subject invalid');
|
|
273
|
+
if(consume!==null&&typeof consume!=='function')
|
|
274
|
+
throw new Error('migration nonce consumer invalid');
|
|
275
|
+
const held=await claim(subjectSha256);
|
|
276
|
+
if(!held||typeof held.operation_id!=='string'||typeof held.release!=='function')
|
|
277
|
+
throw new Error('activation claim result invalid');
|
|
278
|
+
try{if(consume)await consume();}
|
|
279
|
+
catch(error){
|
|
280
|
+
try{await held.release();}
|
|
281
|
+
catch(releaseError){throw new AggregateError([error,releaseError],
|
|
282
|
+
'migration nonce consumption failed and activation claim cleanup failed');}
|
|
283
|
+
throw error;
|
|
284
|
+
}
|
|
285
|
+
return held;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export async function runRemoteStartupSequence({recover,synchronize,claim,start}){
|
|
289
|
+
if([recover,synchronize,claim,start].some(fn=>typeof fn!=='function'))throw new Error('remote startup callback missing');
|
|
290
|
+
await recover();
|
|
291
|
+
const coherence=await synchronize();
|
|
292
|
+
const marker=await claim();
|
|
293
|
+
try{return Object.freeze({coherence,marker,result:await start(marker)});}
|
|
294
|
+
catch(error){throw error;}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export const R153_AUTHORITY_AFTER_MIGRATION=allDisarmed;
|