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
@@ -0,0 +1,232 @@
1
+ param(
2
+ [Parameter(Mandatory=$true)][ValidateSet('0.15.59')][string]$ExpectedVersion,
3
+ [Parameter(Mandatory=$true)][string]$StateRoot,
4
+ [Parameter(Mandatory=$true)][string]$TarballPath,
5
+ [Parameter(Mandatory=$true)][ValidatePattern('^[0-9a-f]{64}$')][string]$ExpectedTarballSha256,
6
+ [Parameter(Mandatory=$true)][ValidatePattern('^[0-9a-f]{64}$')][string]$ExpectedManifestSha256,
7
+ [Parameter(Mandatory=$true)][ValidatePattern('^[0-9a-f]{64}$')][string]$ExpectedBootstrapSha256,
8
+ [Parameter(Mandatory=$true)][string]$StdoutPath,
9
+ [Parameter(Mandatory=$true)][string]$StderrPath,
10
+ [Parameter(Mandatory=$true)][string]$MarkerReceiptPath,
11
+ [Parameter(Mandatory=$true)][ValidatePattern('^[0-9a-f]{64}$')][string]$MarkerReceiptSha256,
12
+ [switch]$Bootstrap
13
+ )
14
+ $ErrorActionPreference = 'Stop'
15
+ Set-StrictMode -Version Latest
16
+
17
+ function Full([string]$Value) { [IO.Path]::GetFullPath($Value).TrimEnd('\') }
18
+ function Require-Absolute([string]$Value,[string]$Label) {
19
+ if (-not [IO.Path]::IsPathRooted($Value)) { throw "$Label must be absolute" }
20
+ return [IO.Path]::GetFullPath($Value)
21
+ }
22
+ function Quote([string]$Value) { '"' + $Value.Replace('"','\"') + '"' }
23
+
24
+ $StateRoot = Require-Absolute $StateRoot 'State root'
25
+ $TarballPath = Require-Absolute $TarballPath 'Exact tarball'
26
+ $StdoutPath = Require-Absolute $StdoutPath 'Stdout evidence'
27
+ $StderrPath = Require-Absolute $StderrPath 'Stderr evidence'
28
+ $MarkerReceiptPath = Require-Absolute $MarkerReceiptPath 'Marker receipt'
29
+ $expectedStateRoot = Join-Path $env:USERPROFILE '.deadbyte-mcp'
30
+ if ((Full $StateRoot) -ine (Full $expectedStateRoot)) { throw 'R153 bootstrap requires the owning user state root' }
31
+ $evidenceDirectory = Join-Path $StateRoot 'evidence\legacy-migration'
32
+ foreach ($file in @($StdoutPath,$StderrPath,$MarkerReceiptPath)) {
33
+ if ((Full (Split-Path -Parent $file)) -ine (Full $evidenceDirectory)) {
34
+ throw 'R153 bootstrap evidence path escapes legacy-migration root'
35
+ }
36
+ }
37
+ $marker = Get-Item -LiteralPath $MarkerReceiptPath -Force
38
+ if ($marker.PSIsContainer -or (($marker.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0)) {
39
+ throw 'R153 bootstrap marker receipt must be a regular file'
40
+ }
41
+ $tarball = Get-Item -LiteralPath $TarballPath -Force
42
+ if ($tarball.PSIsContainer -or (($tarball.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0)) {
43
+ throw 'R153 bootstrap tarball must be a regular file'
44
+ }
45
+ if ((Get-FileHash -LiteralPath $TarballPath -Algorithm SHA256).Hash.ToLowerInvariant() -cne $ExpectedTarballSha256) {
46
+ throw 'R153 bootstrap exact tarball digest mismatch'
47
+ }
48
+ if ((Get-FileHash -LiteralPath $PSCommandPath -Algorithm SHA256).Hash.ToLowerInvariant() -cne $ExpectedBootstrapSha256) {
49
+ throw 'R153 bootstrap script differs from the approved tarball manifest'
50
+ }
51
+ if ((Get-FileHash -LiteralPath $MarkerReceiptPath -Algorithm SHA256).Hash.ToLowerInvariant() -cne $MarkerReceiptSha256) {
52
+ throw 'R153 bootstrap marker receipt digest mismatch'
53
+ }
54
+ if ((Test-Path -LiteralPath $StdoutPath) -or (Test-Path -LiteralPath $StderrPath)) {
55
+ throw 'R153 bootstrap evidence files must not pre-exist'
56
+ }
57
+
58
+ $principal = [Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()
59
+ $isAdmin = $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
60
+ if (-not $Bootstrap) {
61
+ $powershell = Join-Path $env:SystemRoot 'System32\WindowsPowerShell\v1.0\powershell.exe'
62
+ $arguments = @('-NoLogo','-NoProfile','-ExecutionPolicy','Bypass','-File',(Quote $PSCommandPath),
63
+ '-Bootstrap','-ExpectedVersion',$ExpectedVersion,'-StateRoot',(Quote $StateRoot),
64
+ '-TarballPath',(Quote $TarballPath),'-ExpectedTarballSha256',$ExpectedTarballSha256,
65
+ '-ExpectedManifestSha256',$ExpectedManifestSha256,'-ExpectedBootstrapSha256',$ExpectedBootstrapSha256,
66
+ '-StdoutPath',(Quote $StdoutPath),'-StderrPath',(Quote $StderrPath),
67
+ '-MarkerReceiptPath',(Quote $MarkerReceiptPath),'-MarkerReceiptSha256',$MarkerReceiptSha256)
68
+ $child = Start-Process -FilePath $powershell -ArgumentList $arguments -Verb RunAs -Wait -PassThru -WindowStyle Hidden
69
+ if (Test-Path -LiteralPath $StdoutPath) { [Console]::Out.Write([IO.File]::ReadAllText($StdoutPath)) }
70
+ if (Test-Path -LiteralPath $StderrPath) { [Console]::Error.Write([IO.File]::ReadAllText($StderrPath)) }
71
+ exit $child.ExitCode
72
+ }
73
+ if (-not $isAdmin) { throw 'R153 protected bootstrap requires Windows UAC elevation' }
74
+
75
+ # The elevated bootstrap has one fixed job: independently fetch the exact npm
76
+ # registry artifact, extract it into a fresh protected Program Files directory,
77
+ # verify every manifest byte with PowerShell/.NET (no candidate JavaScript yet),
78
+ # protect the ACL, obtain the R89 trust verdict, then transfer control to the
79
+ # protected sibling helper. It accepts no package name, registry, executable,
80
+ # command, destination, or authority vector.
81
+ $node = Join-Path $env:ProgramFiles 'nodejs\node.exe'
82
+ $systemTar = Join-Path $env:SystemRoot 'System32\tar.exe'
83
+ foreach ($file in @($node,$systemTar)) {
84
+ $item = Get-Item -LiteralPath $file -Force
85
+ if ($item.PSIsContainer -or (($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0)) {
86
+ throw 'R153 protected bootstrap executable identity invalid'
87
+ }
88
+ }
89
+ $stageNonce = [Guid]::NewGuid().ToString('N')
90
+ $prefix = Join-Path $env:ProgramFiles ('DEADBYTE-R153-Stage-' + $stageNonce)
91
+ $candidate = Join-Path $prefix 'package'
92
+ if (Test-Path -LiteralPath $prefix) { throw 'R153 fresh protected staging path collision' }
93
+ $existingStages = @(Get-ChildItem -LiteralPath $env:ProgramFiles -Directory -Force -Filter 'DEADBYTE-R153-Stage-*' -ErrorAction Stop)
94
+ if ($existingStages.Count -ge 4) { throw 'R153 protected staging residue limit reached; operator reconciliation required' }
95
+ New-Item -ItemType Directory -Path $prefix -ErrorAction Stop | Out-Null
96
+ $icacls = Join-Path $env:SystemRoot 'System32\icacls.exe'
97
+ & $icacls $prefix '/inheritance:r' '/grant:r' '*S-1-5-32-544:(OI)(CI)(F)' '*S-1-5-18:(OI)(CI)(F)' '/T' '/C' | Out-Null
98
+ if ($LASTEXITCODE -ne 0) { throw 'R153 protected staging initial ACL normalization failed' }
99
+ & $icacls $prefix '/setowner' '*S-1-5-32-544' '/T' '/C' | Out-Null
100
+ if ($LASTEXITCODE -ne 0) { throw 'R153 protected staging initial owner normalization failed' }
101
+
102
+ $metadataUri = 'https://registry.npmjs.org/deadbyte-mcp/0.15.59'
103
+ $tagsUri = 'https://registry.npmjs.org/-/package/deadbyte-mcp/dist-tags'
104
+ [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
105
+ $metadata = Invoke-RestMethod -UseBasicParsing -Uri $metadataUri -Method Get -TimeoutSec 30
106
+ $tags = Invoke-RestMethod -UseBasicParsing -Uri $tagsUri -Method Get -TimeoutSec 30
107
+ if ([string]$metadata.name -cne 'deadbyte-mcp' -or [string]$metadata.version -cne $ExpectedVersion) {
108
+ throw 'R153 npm registry package identity mismatch'
109
+ }
110
+ if ([string]$tags.latest -cne $ExpectedVersion) { throw 'R153 npm latest tag mismatch' }
111
+ $registryTarballUri = [Uri]([string]$metadata.dist.tarball)
112
+ if ($registryTarballUri.Scheme -cne 'https' -or $registryTarballUri.Host -cne 'registry.npmjs.org') {
113
+ throw 'R153 npm registry tarball origin invalid'
114
+ }
115
+ $registryTarball = Join-Path $prefix 'registry.tgz'
116
+ Invoke-WebRequest -UseBasicParsing -Uri $registryTarballUri.AbsoluteUri -OutFile $registryTarball -TimeoutSec 60
117
+ if ((Get-FileHash -LiteralPath $registryTarball -Algorithm SHA256).Hash.ToLowerInvariant() -cne $ExpectedTarballSha256) {
118
+ throw 'R153 live npm registry tarball SHA-256 mismatch'
119
+ }
120
+ if ((Get-FileHash -LiteralPath $registryTarball -Algorithm SHA1).Hash.ToLowerInvariant() -cne ([string]$metadata.dist.shasum).ToLowerInvariant()) {
121
+ throw 'R153 live npm registry tarball shasum mismatch'
122
+ }
123
+ $integrity = [string]$metadata.dist.integrity
124
+ if ($integrity -cnotmatch '^sha512-(.+)$') { throw 'R153 npm registry integrity missing or unsupported' }
125
+ $expectedIntegrityBase64 = $integrity.Substring(7)
126
+ $sha512 = [Security.Cryptography.SHA512]::Create()
127
+ try { $sha512Base64 = [Convert]::ToBase64String($sha512.ComputeHash([IO.File]::ReadAllBytes($registryTarball))) }
128
+ finally { $sha512.Dispose() }
129
+ if ($sha512Base64 -cne $expectedIntegrityBase64) { throw 'R153 live npm registry tarball integrity mismatch' }
130
+
131
+ $members = @(& $systemTar '-tf' $registryTarball)
132
+ if ($LASTEXITCODE -ne 0 -or $members.Count -lt 1) { throw 'R153 exact tarball inventory failed' }
133
+ foreach ($member in $members) {
134
+ $normalized = ([string]$member).Replace('\','/')
135
+ if (-not $normalized.StartsWith('package/') -or $normalized.StartsWith('/') -or
136
+ $normalized.Contains([char]0) -or $normalized.Split('/') -contains '..') {
137
+ throw 'R153 exact tarball contains unsafe member path'
138
+ }
139
+ }
140
+ & $systemTar '-xzf' $registryTarball '-C' $prefix
141
+ if ($LASTEXITCODE -ne 0) { throw "R153 exact tarball extraction failed exit=$LASTEXITCODE" }
142
+ $manifestFile = Join-Path $candidate 'MANIFEST.SHA256'
143
+ $packageFile = Join-Path $candidate 'package.json'
144
+ $manifestItem = Get-Item -LiteralPath $manifestFile -Force
145
+ if (-not $manifestItem.PSIsContainer -and (($manifestItem.Attributes -band [IO.FileAttributes]::ReparsePoint) -eq 0)) {
146
+ if ((Get-FileHash -LiteralPath $manifestFile -Algorithm SHA256).Hash.ToLowerInvariant() -cne $ExpectedManifestSha256) {
147
+ throw 'R153 staged manifest identity mismatch'
148
+ }
149
+ } else { throw 'R153 staged manifest must be a regular file' }
150
+ $package = Get-Content -LiteralPath $packageFile -Raw | ConvertFrom-Json
151
+ if ($package.name -cne 'deadbyte-mcp' -or $package.version -cne $ExpectedVersion) {
152
+ throw 'R153 staged package identity mismatch'
153
+ }
154
+ foreach ($item in Get-ChildItem -LiteralPath $candidate -Recurse -Force) {
155
+ if (($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) { throw 'R153 staged package contains a reparse point' }
156
+ }
157
+ $expected = New-Object 'System.Collections.Generic.HashSet[string]' ([StringComparer]::Ordinal)
158
+ $null = $expected.Add('MANIFEST.SHA256')
159
+ $lineNumber = 0
160
+ foreach ($line in [IO.File]::ReadAllLines($manifestFile)) {
161
+ $lineNumber++
162
+ if ($line.Length -eq 0) { continue }
163
+ if ($line -cnotmatch '^([0-9a-f]{64}) (.+)$') { throw "R153 staged manifest line $lineNumber malformed" }
164
+ $digest = $Matches[1]; $relative = $Matches[2]
165
+ if ($relative.Contains('\') -or $relative.StartsWith('/') -or $relative.Contains([char]0) -or $relative.Split('/') -contains '..') {
166
+ throw 'R153 staged manifest path unsafe'
167
+ }
168
+ if (-not $expected.Add($relative)) { throw 'R153 staged manifest duplicate path' }
169
+ $file = Join-Path $candidate ($relative.Replace('/','\'))
170
+ $entry = Get-Item -LiteralPath $file -Force
171
+ if ($entry.PSIsContainer -or (($entry.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0)) {
172
+ throw 'R153 staged manifest entry is not a regular file'
173
+ }
174
+ if ((Get-FileHash -LiteralPath $file -Algorithm SHA256).Hash.ToLowerInvariant() -cne $digest) {
175
+ throw "R153 staged manifest byte mismatch: $relative"
176
+ }
177
+ }
178
+ $actual = Get-ChildItem -LiteralPath $candidate -Recurse -Force -File | ForEach-Object {
179
+ $_.FullName.Substring($candidate.Length + 1).Replace('\','/')
180
+ }
181
+ foreach ($relative in $actual) { if (-not $expected.Contains($relative)) { throw "R153 staged package unexpected file: $relative" } }
182
+ if ($actual.Count -ne $expected.Count) { throw 'R153 staged package file-count mismatch' }
183
+ & $icacls $prefix '/inheritance:r' '/grant:r' '*S-1-5-32-544:(OI)(CI)(F)' '*S-1-5-18:(OI)(CI)(F)' '/T' '/C' | Out-Null
184
+ if ($LASTEXITCODE -ne 0) { throw 'R153 protected staging final ACL normalization failed' }
185
+ & $icacls $prefix '/setowner' '*S-1-5-32-544' '/T' '/C' | Out-Null
186
+ if ($LASTEXITCODE -ne 0) { throw 'R153 protected staging final owner normalization failed' }
187
+ # /inheritance:r with /T strips inherited Allow ACEs from leaf files.
188
+ # The directory OI/CI grant above does not become a direct leaf ACE on Windows.
189
+ # Restore direct access for Administrators and SYSTEM on every existing member;
190
+ # never grant Users/Everyone, and independently re-hash all package files below.
191
+ & $icacls $prefix '/grant' '*S-1-5-32-544:(F)' '*S-1-5-18:(F)' '/T' '/C' | Out-Null
192
+ if ($LASTEXITCODE -ne 0) { throw 'R153 protected staging leaf ACL restoration failed' }
193
+
194
+ # Re-read every manifest-bound byte after ACL normalization. The first
195
+ # candidate JavaScript below cannot run against bytes changed in the ACL gap.
196
+ foreach ($line in [IO.File]::ReadAllLines($manifestFile)) {
197
+ if ($line.Length -eq 0) { continue }
198
+ if ($line -cnotmatch '^([0-9a-f]{64}) (.+)$') { throw 'R153 staged manifest changed after ACL normalization' }
199
+ $digest = $Matches[1]; $relative = $Matches[2]
200
+ $file = Join-Path $candidate ($relative.Replace('/','\'))
201
+ $entry = Get-Item -LiteralPath $file -Force
202
+ if ($entry.PSIsContainer -or (($entry.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) -or
203
+ (Get-FileHash -LiteralPath $file -Algorithm SHA256).Hash.ToLowerInvariant() -cne $digest) {
204
+ throw "R153 staged package post-ACL verification failed: $relative"
205
+ }
206
+ }
207
+
208
+ foreach ($name in @('NODE_OPTIONS','NODE_PATH','NODE_REPL_EXTERNAL_MODULE','NPM_CONFIG_USERCONFIG','npm_config_userconfig')) {
209
+ Remove-Item -LiteralPath ('Env:' + $name) -ErrorAction SilentlyContinue
210
+ }
211
+ $env:DEADBYTE_STATE_ROOT = $StateRoot
212
+
213
+ $helper = Join-Path $candidate 'scripts\request-uac-migration-r153.ps1'
214
+ $deploy = Join-Path $candidate 'scripts\deploy-r48.mjs'
215
+ $trustVerifier = Join-Path $candidate 'scripts\verify-protected-stage-r153.mjs'
216
+ $trustOutput = & $node $trustVerifier
217
+ if ($LASTEXITCODE -ne 0) { throw 'R153 protected stage R89 trust verification failed' }
218
+ $trust = ($trustOutput -join "`n") | ConvertFrom-Json
219
+ if ($trust.trusted -ne $true) { throw 'R153 protected stage R89 verdict not trusted' }
220
+ $preflightOutput = & $node $deploy '--preflight'
221
+ if ($LASTEXITCODE -ne 0) { throw 'R153 protected activation preflight failed' }
222
+ $preflight = ($preflightOutput -join "`n") | ConvertFrom-Json
223
+ if ($preflight.state -ne 'AUTHORITY_UNVERIFIED' -or $preflight.subject_sha256 -notmatch '^[0-9a-f]{64}$') {
224
+ throw "R153 protected migration not applicable: $($preflight.state)"
225
+ }
226
+ & $helper -Elevated -NodePath $node -DeployScript $deploy -StateRoot $StateRoot `
227
+ -StdoutPath $StdoutPath -StderrPath $StderrPath -MarkerReceiptPath $MarkerReceiptPath `
228
+ -MarkerReceiptSha256 $MarkerReceiptSha256 -PreflightSubjectSha256 $preflight.subject_sha256
229
+ $helperExit = $LASTEXITCODE
230
+ try { Remove-Item -LiteralPath $prefix -Recurse -Force -ErrorAction Stop }
231
+ catch { [Console]::Error.WriteLine('R153_STAGE_CLEANUP_WARNING: protected stage retained for operator reconciliation') }
232
+ exit $helperExit
@@ -12,6 +12,9 @@ import {createEpochNativeContinuityEngine} from '../src/activation-continuity-en
12
12
  import {createActivationClaimManager} from '../src/activation-lock-r65.mjs';
13
13
  import {observeAuthorityEvidence} from '../src/activation-authority-proof-r75.mjs';
14
14
  import {proveLegacyOfflineScopeRepair} from '../src/legacy-offline-policy-proof-r146.mjs';
15
+ import {claimWithOneTimeMigration,verifyNarrowedPolicyGeneration} from '../src/clean-activation-recovery-r153.mjs';
16
+ import {verifyUacApproval,uacPointerSha256,protectedUacReceiptPath,requireTrustedUacInstallation} from '../src/uac-approval-contract-r153.mjs';
17
+ import {inspectTrustedInstallation} from '../src/privileged-install-trust-r89.mjs';
15
18
 
16
19
  const root=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');
17
20
  const state=path.resolve(process.env.DEADBYTE_STATE_ROOT||path.join(os.homedir(),'.deadbyte-mcp'));
@@ -19,10 +22,35 @@ const sha=async f=>createHash('sha256').update(await readFile(f)).digest('hex');
19
22
  const sleep=ms=>new Promise(resolve=>setTimeout(resolve,ms));
20
23
 
21
24
  function parseDeployArgs(argv){
22
- const out={preflight:false,approvalPath:null,approvalSha256:null};
25
+ const out={preflight:false,approvalPath:null,approvalSha256:null,uacLegacyMigration:false,
26
+ markerReceiptPath:null,markerReceiptSha256:null,migrationNonce:null,migrationIssuedAt:null,
27
+ migrationExpiresAt:null,uacApprovalPath:null,uacApprovalSha256:null};
23
28
  for(let index=0;index<argv.length;index++){
24
29
  const arg=argv[index];
25
30
  if(arg==='--preflight'){if(out.preflight)throw new Error('duplicate --preflight');out.preflight=true;continue;}
31
+ if(arg==='--uac-legacy-migration'){if(out.uacLegacyMigration)throw new Error('duplicate --uac-legacy-migration');out.uacLegacyMigration=true;continue;}
32
+ if(arg==='--marker-reconciliation-receipt'){
33
+ if(out.markerReceiptPath)throw new Error('duplicate --marker-reconciliation-receipt');
34
+ const value=argv[++index];if(!value)throw new Error('--marker-reconciliation-receipt requires a path');
35
+ out.markerReceiptPath=path.resolve(value);continue;
36
+ }
37
+ if(arg==='--marker-reconciliation-receipt-sha256'){
38
+ if(out.markerReceiptSha256)throw new Error('duplicate --marker-reconciliation-receipt-sha256');
39
+ const value=String(argv[++index]??'').toLowerCase();if(!/^[0-9a-f]{64}$/.test(value))throw new Error('--marker-reconciliation-receipt-sha256 requires lowercase SHA-256');
40
+ out.markerReceiptSha256=value;continue;
41
+ }
42
+ if(arg==='--migration-nonce'){
43
+ const value=String(argv[++index]??'').toLowerCase();if(!/^[0-9a-f]{64}$/.test(value))throw new Error('--migration-nonce requires lowercase 32-byte hex');
44
+ out.migrationNonce=value;continue;
45
+ }
46
+ if(arg==='--migration-issued-at'){out.migrationIssuedAt=String(argv[++index]??'');continue;}
47
+ if(arg==='--migration-expires-at'){out.migrationExpiresAt=String(argv[++index]??'');continue;}
48
+ if(arg==='--uac-approval-receipt'){
49
+ const value=argv[++index];if(!value)throw new Error('--uac-approval-receipt requires a path');out.uacApprovalPath=path.resolve(value);continue;
50
+ }
51
+ if(arg==='--uac-approval-receipt-sha256'){
52
+ const value=String(argv[++index]??'').toLowerCase();if(!/^[0-9a-f]{64}$/.test(value))throw new Error('--uac-approval-receipt-sha256 requires lowercase SHA-256');out.uacApprovalSha256=value;continue;
53
+ }
26
54
  if(arg==='--policy-migration-approval'){
27
55
  if(out.approvalPath)throw new Error('duplicate --policy-migration-approval');
28
56
  const value=argv[++index];if(!value)throw new Error('--policy-migration-approval requires a path');
@@ -36,6 +64,12 @@ function parseDeployArgs(argv){
36
64
  throw new Error(`unknown deploy argument '${arg}'`);
37
65
  }
38
66
  if(Boolean(out.approvalPath)!==Boolean(out.approvalSha256))throw new Error('policy migration approval path and SHA-256 are required together');
67
+ if(out.uacLegacyMigration&&out.approvalPath)throw new Error('UAC and owner-signed migration authorization are mutually exclusive');
68
+ if(Boolean(out.markerReceiptPath)!==Boolean(out.markerReceiptSha256))throw new Error('marker receipt path and SHA-256 are required together');
69
+ if(out.uacLegacyMigration!==Boolean(out.markerReceiptPath))throw new Error('UAC migration requires a marker reconciliation receipt');
70
+ const uacFields=[out.migrationNonce,out.migrationIssuedAt,out.migrationExpiresAt,out.uacApprovalPath,out.uacApprovalSha256];
71
+ if(out.uacLegacyMigration&&uacFields.some(value=>!value))throw new Error('UAC migration requires nonce, lifetime, and approval receipt');
72
+ if(!out.uacLegacyMigration&&uacFields.some(value=>value))throw new Error('UAC migration arguments require --uac-legacy-migration');
39
73
  return out;
40
74
  }
41
75
  const deployArgs=parseDeployArgs(process.argv.slice(2));
@@ -49,10 +83,19 @@ async function writeExclusive(file,value){
49
83
  const activationClaimManager=createActivationClaimManager({stateRoot:state});
50
84
  let activationClaimOperationId=null;
51
85
 
86
+ let pendingUacConsumption=null;
52
87
  async function claimActivation(subjectSha256){
53
- const claim=await activationClaimManager.claim(subjectSha256);
54
- activationClaimOperationId=claim.operation_id;
55
- return claim.release;
88
+ const held=await claimWithOneTimeMigration({
89
+ claim:value=>activationClaimManager.claim(value),subjectSha256,
90
+ consume:pendingUacConsumption?async()=>{
91
+ const oneTime=pendingUacConsumption;
92
+ await mkdir(oneTime.consumeDir,{recursive:true});
93
+ await writeExclusive(path.join(oneTime.consumeDir,`${oneTime.nonce}.json`),oneTime.receipt);
94
+ pendingUacConsumption=null;
95
+ }:null
96
+ });
97
+ activationClaimOperationId=held.operation_id;
98
+ return held.release;
56
99
  }
57
100
 
58
101
  async function activePolicyPaths(){
@@ -95,13 +138,26 @@ function controllerCommand(command){
95
138
  if(run.error||run.status!==0)throw new Error(`activation controller ${command} failed exit=${run.status} code=${run.error?.code??'none'}`);
96
139
  }
97
140
 
98
- function legacyOfflineRecovery(approvalPath,approvalSha256){
99
- if(!approvalPath)return null;
141
+ function isElevatedWindowsProcess(){
142
+ if(process.platform!=='win32')return false;
143
+ const powershell=path.join(process.env.SystemRoot||'C:\\Windows','System32','WindowsPowerShell','v1.0','powershell.exe');
144
+ const command="([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)";
145
+ const run=spawnSync(powershell,['-NoLogo','-NoProfile','-NonInteractive','-EncodedCommand',Buffer.from(command,'utf16le').toString('base64')],{
146
+ encoding:'utf8',windowsHide:true,shell:false,timeout:15000,env:windowsPowerShellEnvironment()});
147
+ return !run.error&&run.status===0&&/^true$/i.test(String(run.stdout).trim());
148
+ }
149
+
150
+ function legacyOfflineRecovery(approvalPath,approvalSha256,{uacLegacyMigration=false,tarballSha256=null,markerReceipt=null,
151
+ nonce=null,issuedAt=null,expiresAt=null}={}){
152
+ if(!approvalPath&&!uacLegacyMigration)return null;
153
+ const uacAuthorization=uacLegacyMigration?{
154
+ isElevated:isElevatedWindowsProcess(),nonce,issuedAt,expiresAt,tarballSha256,markerReceipt
155
+ }:null;
100
156
  return async({observation,preflight_subject_sha256,pointer,expectedParent,candidate,expectedCandidate})=>
101
157
  proveLegacyOfflineScopeRepair({stateRoot:state,pointer,expectedParent,candidate,expectedCandidate,
102
158
  preflightSubjectSha256:preflight_subject_sha256,observation,policyPaths:observation.policy_paths,
103
159
  policySha256:observation.policy_sha256,approvalPath,approvalSha256,
104
- ownerPublicKeyPath:path.join(state,'trust','r70-owner-public.pem')});
160
+ ownerPublicKeyPath:path.join(state,'trust','r70-owner-public.pem'),uacAuthorization});
105
161
  }
106
162
 
107
163
  async function activationPreflight(manifest,pkg,pointer,recovery){
@@ -116,9 +172,11 @@ async function activationPreflight(manifest,pkg,pointer,recovery){
116
172
  const proof=await recovery({phase:'preflight',observation:observed,preflight_subject_sha256:result.subject_sha256,
117
173
  pointer,expectedParent:release.parent,candidate:{version:pkg.version,manifest_sha256:manifest},
118
174
  expectedCandidate:{version:release.version,manifest_sha256:manifest}});
119
- if(proof?.state==='VERIFIED_OFFLINE_LEGACY_SCOPE_REPAIR'&&proof.owner_approval_verified===true)
175
+ if(proof?.state==='VERIFIED_OFFLINE_LEGACY_SCOPE_REPAIR'&&proof.migration_authorization_verified===true)
120
176
  return {...result,state:'LEGACY_SCOPE_REPAIR_READY',safe_to_activate:true,authority_continuity:'LEGACY_SCOPE_REPAIRED_DISARMED',
121
- legacy_scope_proof_sha256:proof.subject_sha256,owner_approval_subject_sha256:proof.owner_approval_subject_sha256,reauth_planes:[]};
177
+ legacy_scope_proof_sha256:proof.subject_sha256,
178
+ migration_authorization:proof.migration_authorization,
179
+ migration_authorization_subject_sha256:proof.migration_authorization_subject_sha256,reauth_planes:[]};
122
180
  }catch(error){
123
181
  return {...result,state:'LEGACY_SCOPE_REPAIR_UNVERIFIED',safe_to_activate:false,
124
182
  reason:String(error?.message??error).slice(0,256),authority_continuity:'UNVERIFIED',reauth_planes:[]};
@@ -178,14 +236,69 @@ async function waitForActivation({handoffPath,expectedVersion,expectedManifest,t
178
236
  }
179
237
 
180
238
  const manifest=await sha(path.join(root,'MANIFEST.SHA256'));
181
- const pointer=JSON.parse(await readFile(path.join(state,'active-release.json'),'utf8'));
239
+ const pointer=parseActivePointer(await readFile(path.join(state,'active-release.json'),'utf8'));
182
240
  const bridgeRoot=path.join(root,'bridge');
183
241
  const bridgeScript=path.join(bridgeRoot,'src','bridge.mjs');
184
242
  const pkg=JSON.parse(await readFile(path.join(root,'package.json'),'utf8'));
185
243
  const expectedVersion=pkg.version;
186
244
  const release=JSON.parse(await readFile(path.join(root,'RELEASE.json'),'utf8'));
187
- const recovery=legacyOfflineRecovery(deployArgs.approvalPath,deployArgs.approvalSha256);
245
+ let localTarballSha256=null;
246
+ let markerReceipt=null;
247
+ let pendingUacApproval=null;
248
+ if(deployArgs.uacLegacyMigration){
249
+ const localTarball=path.join(state,'evidence','npm-publication',pkg.version,`deadbyte-mcp-${pkg.version}.tgz`);
250
+ localTarballSha256=await sha(localTarball);
251
+ const expectedReceiptRoot=path.resolve(state,'evidence','legacy-migration');
252
+ if(path.resolve(path.dirname(deployArgs.markerReceiptPath)).toLowerCase()!==expectedReceiptRoot.toLowerCase())
253
+ throw new Error('marker reconciliation receipt outside state evidence root');
254
+ const receiptInfo=await lstat(deployArgs.markerReceiptPath);
255
+ if(!receiptInfo.isFile()||receiptInfo.isSymbolicLink())throw new Error('marker reconciliation receipt must be a regular file');
256
+ const receiptBytes=await readFile(deployArgs.markerReceiptPath);
257
+ if(createHash('sha256').update(receiptBytes).digest('hex')!==deployArgs.markerReceiptSha256)
258
+ throw new Error('marker reconciliation receipt hash mismatch');
259
+ markerReceipt=JSON.parse(receiptBytes.toString('utf8').replace(/^\uFEFF/,''));
260
+ const expectedApprovalPath=protectedUacReceiptPath(root,deployArgs.migrationNonce);
261
+ if(path.resolve(deployArgs.uacApprovalPath).toLowerCase()!==path.resolve(expectedApprovalPath).toLowerCase())
262
+ throw new Error('UAC approval receipt path not exact candidate sidecar');
263
+ requireTrustedUacInstallation(await inspectTrustedInstallation([expectedApprovalPath]));
264
+ const approvalInfo=await lstat(deployArgs.uacApprovalPath);
265
+ if(!approvalInfo.isFile()||approvalInfo.isSymbolicLink())throw new Error('UAC approval receipt must be a regular file');
266
+ const approvalBytes=await readFile(deployArgs.uacApprovalPath);
267
+ if(createHash('sha256').update(approvalBytes).digest('hex')!==deployArgs.uacApprovalSha256)
268
+ throw new Error('UAC approval receipt hash mismatch');
269
+ const approval=JSON.parse(approvalBytes.toString('utf8').replace(/^\uFEFF/,''));
270
+ // The full UAC subject is checked AFTER live preflight, since its hash
271
+ // binds the actual predecessor policies and observed preflight identity.
272
+ if(approval?.schema!=='deadbyte.r153-uac-approval.v1'||
273
+ approval.nonce!==deployArgs.migrationNonce||
274
+ approval.issued_at_utc!==deployArgs.migrationIssuedAt||
275
+ approval.expires_at_utc!==deployArgs.migrationExpiresAt)
276
+ throw new Error('UAC approval receipt command-line binding mismatch');
277
+ pendingUacApproval=approval;
278
+ pendingUacConsumption={consumeDir:path.join(state,'evidence','legacy-migration','consumed'),
279
+ nonce:deployArgs.migrationNonce,
280
+ receipt:{schema:'deadbyte.r153-uac-consumption.v1',nonce:deployArgs.migrationNonce,
281
+ approval_sha256:deployArgs.uacApprovalSha256,marker_receipt_sha256:deployArgs.markerReceiptSha256,
282
+ candidate_manifest_sha256:manifest,created_at_utc:new Date().toISOString()}};
283
+ }
284
+ const recovery=legacyOfflineRecovery(deployArgs.approvalPath,deployArgs.approvalSha256,
285
+ {uacLegacyMigration:deployArgs.uacLegacyMigration,tarballSha256:localTarballSha256,markerReceipt,
286
+ nonce:deployArgs.migrationNonce,issuedAt:deployArgs.migrationIssuedAt,expiresAt:deployArgs.migrationExpiresAt});
188
287
  const preflight=await activationPreflight(manifest,pkg,pointer,recovery);
288
+ if(deployArgs.uacLegacyMigration){
289
+ verifyUacApproval(pendingUacApproval,{
290
+ stateRoot:state,candidateRoot:root,candidateVersion:pkg.version,
291
+ candidateManifestSha256:manifest,tarballSha256:localTarballSha256,
292
+ markerReceiptSha256:deployArgs.markerReceiptSha256,
293
+ predecessorPointerSha256:uacPointerSha256(pointer),
294
+ predecessorPolicySha256:markerReceipt.predecessor_policy_sha256,
295
+ preflightSubjectSha256:preflight.subject_sha256,nodeSha256:await sha(process.execPath),
296
+ deployScriptSha256:await sha(fileURLToPath(import.meta.url)),
297
+ nonce:deployArgs.migrationNonce,issuedAt:deployArgs.migrationIssuedAt,
298
+ expiresAt:deployArgs.migrationExpiresAt
299
+ });
300
+ }
301
+
189
302
  if(deployArgs.preflight){
190
303
  console.log(JSON.stringify(preflight));
191
304
  process.exit(0);
@@ -229,6 +342,15 @@ const outcome=await orchestrateActivation({pointer,expectedParent:release.parent
229
342
  result=await runSlotUpdateCli(args,{stateRoot:state});
230
343
  if(!result.handed_off)throw new Error('release activation unexpectedly completed inline');
231
344
  verification=await waitForActivation({handoffPath:result.handoff_path,expectedVersion,expectedManifest:manifest});
345
+ if(deployArgs.uacLegacyMigration){
346
+ const active=parseActivePointer(await readFile(path.join(state,'active-release.json'),'utf8'));
347
+ if(active.version!==expectedVersion||active.manifest_sha256!==manifest)
348
+ throw new Error('UAC migration active pointer drift after activation');
349
+ const codingBytes=await readFile(path.join(active.generation_root,'coding-policy.json'));
350
+ const autonomousBytes=await readFile(path.join(active.generation_root,'autonomous-policy.json'));
351
+ verifyNarrowedPolicyGeneration({codingPolicy:JSON.parse(codingBytes.toString('utf8').replace(/^\uFEFF/,'')),codingPolicyBytes:codingBytes,
352
+ autonomousPolicy:JSON.parse(autonomousBytes.toString('utf8').replace(/^\uFEFF/,''))});
353
+ }
232
354
  return {verified:true,release_id:verification.release_id};
233
355
  }});
234
356
  if(outcome.state!=='ACTIVATED'){
@@ -0,0 +1,107 @@
1
+ // Issue a bounded no-grant UAC receipt only from a protected installed candidate.
2
+ // Never activate, change ACLs, remove markers, issue grants or publish artifacts.
3
+ import {createHash,randomBytes} from 'node:crypto';
4
+ import {readFile,lstat,open,access} from 'node:fs/promises';
5
+ import path from 'node:path';
6
+ import {fileURLToPath} from 'node:url';
7
+ import {spawnSync} from 'node:child_process';
8
+ import {parseActivePointer} from '../src/release-slot.mjs';
9
+ import {inspectTrustedInstallation,privilegedEpochInstallFiles} from '../src/privileged-install-trust-r89.mjs';
10
+ import {verifyMarkerReconciliationReceipt} from '../src/clean-activation-recovery-r153.mjs';
11
+ import {createUacApproval,uacPointerSha256,protectedUacReceiptPath} from '../src/uac-approval-contract-r153.mjs';
12
+
13
+ const root=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');
14
+ const sha=b=>createHash('sha256').update(b).digest('hex');
15
+ const read=async f=>readFile(f);
16
+ const hash=async f=>sha(await read(f));
17
+ const fail=text=>{throw new Error('R153 UAC issuer rejected: '+text);};
18
+ function args(argv){
19
+ const expected=['--state-root','--marker-receipt','--marker-receipt-sha256','--preflight-subject-sha256'];
20
+ if(argv.length!==8||expected.some((key,i)=>argv[2*i]!==key))fail('unexpected arguments');
21
+ const [state,receipt,receiptHash,preflight]=[argv[1],argv[3],argv[5],argv[7]];
22
+ if(!path.win32.isAbsolute(state)||!path.win32.isAbsolute(receipt)||
23
+ !/^[0-9a-f]{64}$/.test(receiptHash)||!/^[0-9a-f]{64}$/.test(preflight))fail('subject arguments invalid');
24
+ return {state:path.resolve(state),receipt:path.resolve(receipt),receiptHash,preflight};
25
+ }
26
+ async function regular(file,label){const s=await lstat(file);if(!s.isFile()||s.isSymbolicLink())fail(label+' must be regular');return s;}
27
+ async function absent(file,label){try{await lstat(file);fail(label+' present');}catch(e){if(e?.code!=='ENOENT')throw e;}}
28
+ async function main(){
29
+ if(process.platform!=='win32'||process.arch!=='x64')fail('Windows x64 required');
30
+ const ps='C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe';
31
+ const check="([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)";
32
+ const elevated=spawnSync(ps,['-NoLogo','-NoProfile','-NonInteractive','-EncodedCommand',Buffer.from(check,'utf16le').toString('base64')],
33
+ {encoding:'utf8',shell:false,windowsHide:true,timeout:12000});
34
+ if(elevated.error||elevated.status!==0||String(elevated.stdout).trim().toLowerCase()!=='true')
35
+ fail('explicit Windows UAC elevation required');
36
+ const {state,receipt,receiptHash,preflight}=args(process.argv.slice(2));
37
+ if(path.dirname(receipt).toLowerCase()!==path.join(state,'evidence','legacy-migration').toLowerCase())
38
+ fail('marker receipt path not in state evidence root');
39
+ const issueFile=fileURLToPath(import.meta.url);
40
+ const deployFile=path.join(root,'scripts','deploy-r48.mjs');
41
+ const helperFile=path.join(root,'scripts','request-uac-migration-r153.ps1');
42
+ const trust=await inspectTrustedInstallation([...privilegedEpochInstallFiles(root,process.execPath),
43
+ issueFile,deployFile,helperFile,path.join(root,'package.json'),path.join(root,'MANIFEST.SHA256'),path.join(root,'RELEASE.json'),path.join(root,'src','uac-approval-contract-r153.mjs'),
44
+ path.join(root,'src','clean-activation-recovery-r153.mjs'),path.join(root,'src','legacy-offline-policy-proof-r146.mjs')]);
45
+ if(trust.trusted!==true)fail('candidate not R89 protected');
46
+ const pkg=JSON.parse((await read(path.join(root,'package.json'))).toString('utf8'));
47
+ const rel=JSON.parse((await read(path.join(root,'RELEASE.json'))).toString('utf8'));
48
+ if(pkg.version!=='0.15.59'||rel.version!==pkg.version)fail('candidate release identity mismatch');
49
+ const pointer=parseActivePointer((await read(path.join(state,'active-release.json'))).toString('utf8'));
50
+ if(pointer.version!=='0.15.45'||rel.parent?.release_id!==pointer.release_id||
51
+ rel.parent?.manifest_sha256!==pointer.manifest_sha256||
52
+ rel.parent?.generation_manifest_sha256!==pointer.generation_manifest_sha256)
53
+ fail('predecessor pointer drift');
54
+ const manifest=await hash(path.join(root,'MANIFEST.SHA256'));
55
+ const verifyManifest=spawnSync(process.execPath,[path.join(root,'scripts','release-manifest.mjs'),'verify-installed'],
56
+ {cwd:root,encoding:'utf8',shell:false,windowsHide:true,timeout:30000});
57
+ if(verifyManifest.error||verifyManifest.status!==0)fail('candidate manifest verification failed');
58
+ await regular(receipt,'marker receipt');
59
+ const receiptBytes=await read(receipt);
60
+ if(sha(receiptBytes)!==receiptHash)fail('marker receipt digest mismatch');
61
+ const marker=JSON.parse(receiptBytes.toString('utf8').replace(/^\uFEFF/,''));
62
+ const policies=marker.predecessor_policy_sha256;
63
+ verifyMarkerReconciliationReceipt(marker,{pointer,policySha256:policies,markerSha256:marker.marker_sha256});
64
+ await absent(path.join(state,'runtime','remote-session.json'),'foreground owner marker');
65
+ const grantField={process:'grant_file',coding:'lease_file',autonomous:'lease_file',desktop:'grant_file',host:'arm_file'};
66
+ for(const plane of Object.keys(grantField)){
67
+ const filename=path.join(pointer.generation_root,`${plane}-policy.json`);
68
+ await regular(filename,plane+' policy');
69
+ const bytes=await read(filename);
70
+ if(sha(bytes)!==policies?.[plane])fail(plane+' policy drift');
71
+ const policy=JSON.parse(bytes.toString('utf8').replace(/^\uFEFF/,''));
72
+ const grant=policy[grantField[plane]];
73
+ if(typeof grant!=='string'||!path.isAbsolute(grant)||
74
+ path.dirname(grant).toLowerCase()!==path.join(state,'runtime').toLowerCase())fail(plane+' grant path invalid');
75
+ await absent(grant,plane+' grant');
76
+ await absent(path.join(state,'runtime','authority-epochs',plane,'.lock'),plane+' authority lock');
77
+ }
78
+ const tarball=path.join(state,'evidence','npm-publication',pkg.version,`deadbyte-mcp-${pkg.version}.tgz`);
79
+ await regular(tarball,'exact publication tarball');
80
+ const bytes=await read(tarball);
81
+ const issuedAt=new Date().toISOString(),expiresAt=new Date(Date.now()+300_000).toISOString();
82
+ const nonce=randomBytes(32).toString('hex');
83
+ const approval=createUacApproval({stateRoot:state,candidateRoot:root,candidateVersion:pkg.version,
84
+ candidateManifestSha256:manifest,tarballSha256:sha(bytes),markerReceiptSha256:receiptHash,
85
+ predecessorPointerSha256:uacPointerSha256(pointer),
86
+ predecessorPolicySha256:policies,preflightSubjectSha256:preflight,
87
+ nodeSha256:await hash(process.execPath),deployScriptSha256:await hash(deployFile),
88
+ nonce,issuedAt,expiresAt});
89
+ // The strict installation manifest disallows unmanifested files. A receipt
90
+ // under root would turn an otherwise valid candidate into an invalid tree.
91
+ const file=protectedUacReceiptPath(root,nonce);
92
+ const h=await open(file,'wx',0o600);
93
+ try{await h.writeFile(JSON.stringify(approval)+'\n','utf8');await h.sync();}finally{await h.close();}
94
+ // Newly created files can be owned by the interactive administrator's SID.
95
+ // Normalize the exact sidecar to the built-in Administrators owner and
96
+ // independently require the R89 ACL verdict. No arbitrary path argument.
97
+ const ownerFix=spawnSync('C:\\Windows\\System32\\icacls.exe',
98
+ [file,'/setowner','*S-1-5-32-544'],
99
+ {encoding:'utf8',shell:false,windowsHide:true,timeout:15000});
100
+ if(ownerFix.error||ownerFix.status!==0)fail('protected approval owner normalization failed');
101
+ const receiptTrust=await inspectTrustedInstallation([file]);
102
+ if(receiptTrust.trusted!==true)fail('approval sidecar not R89 protected');
103
+ process.stdout.write(JSON.stringify({schema:'deadbyte.r153-uac-issuer-result.v1',
104
+ receipt_path:file,receipt_sha256:await hash(file),nonce,issued_at_utc:issuedAt,
105
+ expires_at_utc:expiresAt})+'\n');
106
+ }
107
+ main().catch(error=>{process.stderr.write(String(error?.message||error)+'\n');process.exitCode=2;});
@@ -0,0 +1,57 @@
1
+ // R153 additive release-lineage gate. Read-only: no manifests rewritten, no publish/activation.
2
+ import {readFile} from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import {fileURLToPath} from 'node:url';
5
+
6
+ const defaultRoot=path.resolve(path.dirname(fileURLToPath(import.meta.url)),'..');
7
+ const hex64=/^[0-9a-f]{64}$/;
8
+ const EXPECTED=Object.freeze({
9
+ candidateVersion:'0.15.59', train:'R153',
10
+ sourceVersion:'0.15.58',
11
+ sourceSha256:'4bc53cfa6b3890b5749066cb507e8773b36d6c255192a8973dadb8546a39822e',
12
+ runtimeVersion:'0.15.45',
13
+ runtimeReleaseId:'v0.15.45-34aa8d1241cfb8bf',
14
+ runtimeManifest:'34aa8d1241cfb8bf0b5ba303bf3c06e0e9e1bf01f64457bca659d6dd4d634fdc',
15
+ runtimeGeneration:'ff2b73410062688651abd7bc2dd7b2b8c87810a28e3808bfe1f4352a25b0e6e4'
16
+ });
17
+ const keys=['version','release_id','manifest_sha256','generation_manifest_sha256'];
18
+ const same=(a,b)=>keys.every(k=>a?.[k]===b?.[k]);
19
+ function requireEqual(actual,expected,label){
20
+ if(actual!==expected)throw new Error(`R153 identity ${label} mismatch: ${JSON.stringify(actual)}`);
21
+ }
22
+ export function verifyR153Lineage({pkg,release,bridge}){
23
+ if(pkg?.name!=='deadbyte-mcp'||release?.name!=='deadbyte-mcp')throw new Error('R153 package name mismatch');
24
+ requireEqual(pkg.version,EXPECTED.candidateVersion,'package version');
25
+ requireEqual(release.version,EXPECTED.candidateVersion,'release version');
26
+ requireEqual(release.train,EXPECTED.train,'train');
27
+ requireEqual(release.schema,'deadbyte.release.v1','release schema');
28
+ requireEqual(bridge?.name,'deadbyte-chatgpt-bridge','bridge name');
29
+ requireEqual(bridge.version,pkg.version,'bridge version');
30
+ requireEqual(release.source_predecessor?.version,EXPECTED.sourceVersion,'source predecessor version');
31
+ requireEqual(release.source_predecessor?.tarball_sha256,EXPECTED.sourceSha256,'source predecessor tarball');
32
+ requireEqual(release.source_predecessor?.release_status,'REGISTRY_PUBLISHED','source predecessor registry status');
33
+ const predecessor=release.runtime_predecessor;
34
+ requireEqual(predecessor?.version,EXPECTED.runtimeVersion,'runtime predecessor version');
35
+ requireEqual(predecessor?.release_id,EXPECTED.runtimeReleaseId,'runtime release ID');
36
+ requireEqual(predecessor?.manifest_sha256,EXPECTED.runtimeManifest,'runtime manifest SHA-256');
37
+ requireEqual(predecessor?.generation_manifest_sha256,EXPECTED.runtimeGeneration,'runtime generation SHA-256');
38
+ if(!same(release.parent,predecessor))throw new Error('R153 parent is not exact runtime predecessor');
39
+ if(release.source_predecessor.version===predecessor.version)throw new Error('R153 predecessor roles collapsed');
40
+ for(const key of ['tarball_sha256','tarball_integrity','npm_integrity'])
41
+ if(Object.hasOwn(release,key))throw new Error(`R153 release contains self-referential ${key}`);
42
+ for(const key of ['manifest_sha256','generation_manifest_sha256'])
43
+ if(!hex64.test(predecessor[key]))throw new Error(`R153 ${key} malformed`);
44
+ return Object.freeze({schema:'deadbyte.r153-release-lineage-gate.v1',status:'PASS',
45
+ candidate_version:pkg.version,source_predecessor:EXPECTED.sourceVersion,
46
+ source_tarball_sha256:EXPECTED.sourceSha256,runtime_predecessor:EXPECTED.runtimeVersion,
47
+ runtime_release_id:EXPECTED.runtimeReleaseId,scope:'METADATA_ONLY'});
48
+ }
49
+ export async function verifyR153IdentityTree(root=defaultRoot){
50
+ const regularJSON=async rel=>JSON.parse(await readFile(path.join(root,rel),'utf8'));
51
+ return verifyR153Lineage({pkg:await regularJSON('package.json'),
52
+ release:await regularJSON('RELEASE.json'),bridge:await regularJSON('bridge/package.json')});
53
+ }
54
+ if(process.argv[1]&&path.resolve(process.argv[1])===fileURLToPath(import.meta.url)){
55
+ try{console.log(JSON.stringify(await verifyR153IdentityTree(),null,2));}
56
+ catch(e){console.error('R153_LINEAGE_FAIL '+String(e?.message??e));process.exitCode=1;}
57
+ }