deadbyte-mcp 0.15.43 → 0.15.45
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 +160 -161
- package/PROVENANCE.json +257 -232
- package/README.txt +18 -18
- package/RELEASE.json +348 -320
- package/SBOM.cdx.json +14 -2
- package/TEST-MATRIX.json +503 -460
- package/bin/deadbyte-native-inspect-r67.exe +0 -0
- package/bridge/package.json +1 -1
- package/controller/deadbyte-controller.ps1 +1857 -1769
- package/controller/deadbyte-desktop-policy.json +6 -6
- package/controller/deadbyte-process-policy.json +4 -4
- package/docs/R67-ALPHA-REFERENCE-DIFFERENTIAL.md +42 -0
- package/docs/R67-ALPHA-STATIC-COMPARISON.json +205 -0
- package/docs/R67-CANDIDATE-PARENT.json +10 -0
- package/docs/R67-EVIDENCE-RECONCILE-CLOSEOUT-20261011.md +64 -0
- package/docs/R67-FULL-REGRESSION-UNKNOWN.json +27 -0
- package/docs/R67-NATIVE-INSPECT-BINARY.json +11 -0
- package/docs/R67-NPM-EXDEV-PRESERVATION-20261011.md +39 -0
- package/docs/R67-OBSERVATION-TRUTH.md +82 -0
- package/docs/R67-REFERENCE-GAP-MATRIX.json +57 -0
- package/docs/R67-REGRESSION-CLOSURE-20261011.md +24 -0
- package/docs/R68-COMPAT-HOST-EXEC-CANDIDATE.md +63 -0
- package/docs/R69-LIVE-OUTCOME-R70-POLICY-TRANSFER.md +136 -0
- package/docs/R70-CODING-DIGEST-SUPERVISOR-CHECKPOINT-20261013.md +42 -0
- package/docs/R70-FINITE-GRANT-OWNER-BOUNDARY-20261013.md +80 -0
- package/docs/R70-OWNER-BOOTSTRAP-CHECKPOINT-20261013.md +60 -0
- package/docs/R70-REFERENCE-MECHANISMS.json +95 -0
- package/docs/R70-TRUST-GUARD-EXPIRY-CHECKPOINT-20261013.md +47 -0
- package/native/deadbyte-native-inspect-r67.cpp +252 -0
- package/package.json +6 -3
- package/scripts/benchmark-l0-r71.mjs +76 -0
- package/scripts/build-native-inspector-r67.ps1 +28 -0
- package/scripts/gate-windows-r67.ps1 +56 -0
- package/scripts/gate-windows-r71.ps1 +64 -0
- package/scripts/npm-preserve-artifact-r67.mjs +99 -0
- package/scripts/npm-release-r48.mjs +12 -2
- package/scripts/npm-release-readiness-r67.mjs +26 -0
- package/scripts/npm-release-readiness-r71.mjs +21 -0
- package/scripts/r67-acquire-alpha-reference.mjs +95 -0
- package/scripts/r67-audit-nonpass-source.mjs +39 -0
- package/scripts/r67-compare-desktop-alpha.mjs +48 -0
- package/scripts/r67-diagnose-failed-shard.mjs +23 -0
- package/scripts/r67-durable-batch.mjs +42 -0
- package/scripts/r67-focused-gate.mjs +58 -0
- package/scripts/r67-full-regression.mjs +28 -0
- package/scripts/r67-independent-observation-verify.mjs +36 -0
- package/scripts/r67-independent-runtime-observation-verify.mjs +63 -0
- package/scripts/r67-nonpass-adjudicate.mjs +20 -0
- package/scripts/r67-publish-boundary-audit.mjs +32 -0
- package/scripts/r67-regression-reconcile.mjs +10 -0
- package/scripts/r67-regression-shard-run.mjs +70 -0
- package/scripts/r67-run-shard-batch.mjs +18 -0
- package/scripts/r67-stage-candidate.mjs +30 -0
- package/scripts/r67-verify-parent-ping.mjs +22 -0
- package/scripts/release-manifest.mjs +1 -0
- package/scripts/verify-r70-full-regression.ps1 +72 -0
- package/src/activation-continuity-engine-r65.mjs +2 -1
- package/src/authority-continuity-r65.mjs +28 -8
- package/src/autonomous-mcp-tools.mjs +1 -1
- package/src/autonomous-output-contracts.mjs +4 -1
- package/src/autonomous-policy.mjs +54 -7
- package/src/autonomous-runtime.mjs +1 -1
- package/src/capability-ledger.mjs +1 -1
- package/src/capability-resolver.mjs +2 -1
- package/src/commander-compat-runtime.mjs +30 -2
- package/src/deadbyte-cli.mjs +41 -7
- package/src/machine-fs-smoke-client.mjs +13 -5
- package/src/machine-identity.mjs +59 -2
- package/src/machine-mcp-tools.mjs +8 -0
- package/src/mcp-server.mjs +11 -2
- package/src/native-diff-v2.mjs +18 -5
- package/src/native-diff.mjs +21 -5
- package/src/native-inspector-r67.mjs +66 -0
- package/src/r67-diagnostic-export.mjs +118 -0
- package/src/r67-diagnostics-verify.mjs +82 -0
- package/src/r67-evidence-reconcile.mjs +128 -0
- package/src/r67-native-observation-authority.mjs +45 -0
- package/src/r67-observation-receipt.mjs +43 -0
- package/src/r67-regression-shards.mjs +176 -0
- package/src/r70-owner-approval.mjs +78 -0
- package/src/remote-live-log.mjs +62 -24
- package/src/remote-result-journal.mjs +1 -1
- package/src/runtime-update.mjs +2051 -1891
- package/src/tool-audit.mjs +3 -1
- package/src/version.mjs +1 -1
- package/test/agent-runtime.test.mjs +1 -1
- package/test/autonomous-local-supervisor.test.mjs +2 -1
- package/test/autonomous-loop.test.mjs +11 -6
- package/test/autonomous-output-contract.test.mjs +17 -1
- package/test/autonomous-runtime.test.mjs +1 -1
- package/test/autonomous-supervisor-integration.test.mjs +2 -1
- package/test/capability-resolver.test.mjs +17 -0
- package/test/codex-autonomous-r44.test.mjs +1 -1
- package/test/coding-plane.test.mjs +1 -1
- package/test/commander-compat-runtime.test.mjs +42 -0
- package/test/controller-policy-preflight-r59.test.mjs +1 -0
- package/test/full-max-catalog-r46.test.mjs +1 -1
- package/test/native-diff-topology-r36.test.mjs +35 -2
- package/test/native-diff.test.mjs +83 -3
- package/test/output-schema.test.mjs +1 -1
- package/test/r51-live-log-polling.test.mjs +12 -0
- package/test/r53-release-contract.test.mjs +1 -1
- package/test/r54-release-contract.test.mjs +1 -1
- package/test/r55-release-contract.test.mjs +1 -1
- package/test/r56-release-contract.test.mjs +1 -1
- package/test/r57-release-contract.test.mjs +1 -1
- package/test/r58-bundled-bridge.test.mjs +1 -1
- package/test/r58-release-contract.test.mjs +2 -2
- package/test/r58-windows-gate.test.mjs +1 -1
- package/test/r59-tool-surface-compat.test.mjs +9 -5
- package/test/r60-release-contract.test.mjs +3 -3
- package/test/r61-release-contract.test.mjs +1 -1
- package/test/r62-release-contract.test.mjs +1 -1
- package/test/r63-release-contract.test.mjs +1 -1
- package/test/r64-release-contract.test.mjs +1 -1
- package/test/r65-authority-runtime-epoch.test.mjs +9 -1
- package/test/r65-epoch-native-activation-integration.test.mjs +4 -4
- package/test/r65-release-contract.test.mjs +1 -1
- package/test/r66-release-contract.test.mjs +2 -1
- package/test/r67-cli-integration.test.mjs +41 -0
- package/test/r67-diagnostics-cli.test.mjs +60 -0
- package/test/r67-diagnostics-verify.test.mjs +49 -0
- package/test/r67-diagnostics-zip-oracle.test.mjs +38 -0
- package/test/r67-diagnostics.test.mjs +60 -0
- package/test/r67-evidence-reconcile.test.mjs +186 -0
- package/test/r67-isolated-slot-rollback-restore.test.mjs +63 -0
- package/test/r67-live-log-secret-canary.test.mjs +44 -0
- package/test/r67-machine-fs-smoke-epoch.test.mjs +22 -0
- package/test/r67-native-inspector.test.mjs +131 -0
- package/test/r67-native-observation-authority.test.mjs +49 -0
- package/test/r67-npm-cross-volume.test.mjs +74 -0
- package/test/r67-npm-publish-gate.test.mjs +40 -0
- package/test/r67-regression-shards.test.mjs +184 -0
- package/test/r67-release-contract.test.mjs +45 -0
- package/test/r67-runtime-observation-binding.test.mjs +86 -0
- package/test/r67-server-observation-wiring.test.mjs +12 -0
- package/test/r67-signed-receipt.test.mjs +66 -0
- package/test/r67-skip-rationale.test.mjs +31 -0
- package/test/r67-tool-annotations.test.mjs +21 -0
- package/test/r67-windows-gate-argv.test.mjs +13 -0
- package/test/r69-live-outcome.test.mjs +137 -0
- package/test/r70-autonomous-grant-expiry.test.mjs +89 -0
- package/test/r70-autonomous-policy-bootstrap.test.mjs +166 -0
- package/test/r70-controller-autonomous-root.test.mjs +104 -0
- package/test/r70-controller-grant-subset.test.mjs +28 -0
- package/test/r70-controller-owner-issue-boundary.test.mjs +80 -0
- package/test/r70-controller-policy-snapshot.test.mjs +66 -0
- package/test/r70-owner-approval.test.mjs +67 -0
- package/test/r71-full-verifier-artifact.test.mjs +28 -0
- package/test/r71-isolated-slot-rollback-restore.test.mjs +62 -0
- package/test/r71-native-diff-mcp-wire.test.mjs +75 -0
- package/test/r71-publish-fence.test.mjs +38 -0
- package/test/r71-release-identity.test.mjs +31 -0
- package/test/release-version.test.mjs +1 -1
- package/test/remote-live-log-r36.test.mjs +10 -0
- package/test/remote-live-log-r37.test.mjs +29 -0
- package/test/remote-result-journal.test.mjs +12 -0
- package/test/remote-session-cli.test.mjs +2 -1
- package/test/semantic-multilang.test.mjs +1 -1
- package/test/standing-grants.test.mjs +2 -2
- package/test/tool-audit.test.mjs +14 -0
- package/bin/appcontainer-stage.obj +0 -0
- package/bin/bootstrap-advapi32.obj +0 -0
- package/bin/bootstrap-exitcode.obj +0 -0
- package/bin/bootstrap-kernel32.obj +0 -0
- package/bin/child-control-probe.obj +0 -0
- package/bin/child-control-stage.obj +0 -0
- package/bin/contained-reverse-worker.obj +0 -0
- package/bin/contained-transform-worker.obj +0 -0
- package/bin/containment-probe.obj +0 -0
- package/bin/deadbyte-contain.obj +0 -0
- package/bin/deadbyte-desktop-input-r40.obj +0 -0
- package/bin/deadbyte-desktop-observer-r40.obj +0 -0
- package/bin/deadbyte-desktop-uia-r40.obj +0 -0
- package/bin/deadbyte-exec.obj +0 -0
- package/bin/deadbyte-hardware-observer-r45.obj +0 -0
- package/bin/deadbyte-process-host.obj +0 -0
- package/bin/deadbyte-tunnel-host.obj +0 -0
- package/conpty-input-observer.obj +0 -0
- package/miniconpty.obj +0 -0
- package/proof/CONTAINMENT-BUILD.txt +0 -163
- package/proof/MCP-SMOKE-STATUS.txt +0 -9
- package/proof/NODE-SYNTAX.txt +0 -4
- package/proof/PROOF.txt +0 -6
- package/proof/R34-TASK8-SUPERVISOR-CHECKPOINT.json +0 -29
- package/proof/R34-TASK9-MCP-SURFACE-CHECKPOINT.json +0 -28
- package/proof/SECRET-SCAN.txt +0 -4
- package/proof/SOURCE-GATES.txt +0 -16
- package/proof/STATUS.txt +0 -11
- package/proof/TARGET-V0313-CONTAINMENT-PASS.sha256 +0 -1
- package/proof/TARGET-V0313-CONTAINMENT-PASS.txt +0 -215
- package/proof/TARGET-V040-CONTAINED-IPC-FAIL.sha256 +0 -1
- package/proof/TARGET-V040-CONTAINED-IPC-FAIL.txt +0 -299
- package/proof/TARGET-V041-CONTAINED-CREATEPROCESS-203.sha256 +0 -1
- package/proof/TARGET-V041-CONTAINED-CREATEPROCESS-203.txt +0 -311
- package/proof/TARGET-V042-CONTAINED-CREATEPROCESS-203.sha256 +0 -1
- package/proof/TARGET-V042-CONTAINED-CREATEPROCESS-203.txt +0 -313
- package/proof/TARGET-V043-CONTAINED-CREATEPROCESS-203.sha256 +0 -1
- package/proof/TARGET-V043-CONTAINED-CREATEPROCESS-203.txt +0 -308
- package/proof/TARGET-V044-CONTAINED-PASS.sha256 +0 -1
- package/proof/TARGET-V044-CONTAINED-PASS.txt +0 -432
- package/proof/TARGET-V045-DUAL-OPERATION-PASS.sha256 +0 -1
- package/proof/TARGET-V045-DUAL-OPERATION-PASS.txt +0 -481
- package/proof/TARGET-V046-FIXED-EXECUTABLE-REGISTRY-PASS.sha256 +0 -1
- package/proof/TARGET-V046-FIXED-EXECUTABLE-REGISTRY-PASS.txt +0 -610
- package/proof/TARGET-V047-RUNTIME-MANIFEST-PASS.sha256 +0 -1
- package/proof/TARGET-V047-RUNTIME-MANIFEST-PASS.txt +0 -642
- package/proof/TARGET-V050-BATCH-GATE-PASS.sha256 +0 -1
- package/proof/TARGET-V050-BATCH-GATE-PASS.txt +0 -713
- package/proof/TESTS.txt +0 -686
- package/proof/TRUST-EPHEMERAL.txt +0 -4
- package/proof/V03-FREEZE.txt +0 -27
- package/proof/V041-IPC-HOTFIX.txt +0 -16
- package/proof/V042-ENV-HOTFIX.txt +0 -15
- package/proof/V043-DRIVE-CWD-HOTFIX.txt +0 -14
- package/proof/V044-LOCALAPPDATA-HOTFIX.txt +0 -18
- package/proof/V045-OPERATION-REGISTRY.txt +0 -7
- package/proof/V046-WORKER-REGISTRY.txt +0 -12
- package/proof/V047-WORKER-MANIFEST.txt +0 -18
- package/proof/V050-BATCH-TRUST-CLOSURE.txt +0 -21
- package/proof/V060-ACTIVE-NETWORK-AUTHORITY.txt +0 -29
- package/proof/V070-NODE-SYNTAX.txt +0 -36
- package/proof/V070-NODE-TESTS.txt +0 -752
- package/proof/V070-PACK-DRY-RUN.txt +0 -126
- package/proof/V070-SOURCE-GATE.txt +0 -30
- package/proof/V070-STATIC-SCAN.txt +0 -4
- package/proof/V070-WINDOWS-GATE-PENDING.txt +0 -25
- package/proof/V071-NODE-SYNTAX.txt +0 -1
- package/proof/V071-NODE-TESTS.txt +0 -796
- package/proof/V071-PACK-DRY-RUN.json +0 -656
- package/proof/V071-SOURCE-GATE.txt +0 -16
- package/proof/V071-WINDOWS-GATE-PENDING.txt +0 -24
- package/proof/V073-NODE-SYNTAX.txt +0 -4
- package/proof/V073-SOURCE-GATE.txt +0 -21
- package/proof/V073-STATIC-SCAN.txt +0 -10
- package/proof/V073-WINDOWS-GATE-PENDING.txt +0 -5
- package/proof/VERIFY.txt +0 -7
- package/proof/WINDOWS-CONTAINED-STATUS.txt +0 -5
- package/proof/WINDOWS-CONTAINMENT-STATUS.txt +0 -3
- package/proof/commander-closure-20260927/CONTINUATION.json +0 -43
- package/proof/commander-closure-20260927/RESULT.json +0 -30
- package/proof/commander-closure-20260927/SEARCH-CONTINUATION.json +0 -30
- package/proof/commander-closure-20260927/XLS-REFERENCE-PROBE.json +0 -13
- package/proof/commander-closure-20260928/DIFFERENTIAL-CHECKPOINT.json +0 -122
- package/proof/r59-full-regression-current.json +0 -13
- package/proof/r59-full-regression-current.stderr.log +0 -0
- package/proof/r59-full-regression-current.stdout.log +0 -1268
|
@@ -1,1771 +1,1859 @@
|
|
|
1
|
-
#requires -Version 5.1
|
|
2
|
-
[CmdletBinding()]
|
|
3
|
-
param(
|
|
4
|
-
[Parameter(Position=0)]
|
|
5
|
-
[ValidateSet('menu','start','resume','park','stop','restart','status','url','test','fulltest','toggle','clean','machinetest','armprocess','disarmprocess','processstatus','armhost','disarmhost','hoststatus','hosttest','armcoding','disarmcoding','codingstatus','codingtest','armautonomous','disarmautonomous','autonomousstatus','autonomoustest','supervisorstart','supervisorstop','supervisorstatus','supervisorrecover','armdesktop','disarmdesktop','desktopstatus')]
|
|
6
|
-
[string]$Command = 'menu',
|
|
7
|
-
[switch]$Json
|
|
8
|
-
)
|
|
9
|
-
|
|
10
|
-
Set-StrictMode -Version 3.0
|
|
11
|
-
$ErrorActionPreference = 'Stop'
|
|
12
|
-
|
|
13
|
-
$ControllerSchema = 'deadbyte.controller.v1'
|
|
14
|
-
$Root = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
15
|
-
$RuntimeDir = if ($env:DEADBYTE_CONTROLLER_RUNTIME_ROOT) { [System.IO.Path]::GetFullPath([string]$env:DEADBYTE_CONTROLLER_RUNTIME_ROOT) } else { Join-Path $Root 'runtime' }
|
|
16
|
-
$LogDir = Join-Path $RuntimeDir 'logs'
|
|
17
|
-
$ConfigPath = if ($env:DEADBYTE_CONTROLLER_CONFIG) { $env:DEADBYTE_CONTROLLER_CONFIG } else { Join-Path $Root 'deadbyte-controller.config.psd1' }
|
|
18
|
-
|
|
19
|
-
function Fail([string]$Message) { throw "DEADBYTE controller: $Message" }
|
|
20
|
-
if ($Json -and $Command -ne 'status') { Fail 'JSON observation is available only for status' }
|
|
21
|
-
|
|
22
|
-
function Get-ControllerMutationMutexName {
|
|
23
|
-
$normalized = ([System.IO.Path]::GetFullPath($RuntimeDir)).TrimEnd('\\').ToLowerInvariant()
|
|
24
|
-
$sha = [System.Security.Cryptography.SHA256]::Create()
|
|
25
|
-
try {
|
|
26
|
-
$bytes = [System.Text.Encoding]::UTF8.GetBytes($normalized)
|
|
27
|
-
$digest = $sha.ComputeHash($bytes)
|
|
28
|
-
} finally {
|
|
29
|
-
$sha.Dispose()
|
|
30
|
-
}
|
|
31
|
-
$hex = ([System.BitConverter]::ToString($digest)).Replace('-', '').ToLowerInvariant().Substring(0, 24)
|
|
32
|
-
return "Local\DEADBYTE-MCP-Controller-$hex"
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function Invoke-WithControllerMutationLock([string]$Name, [scriptblock]$Body) {
|
|
36
|
-
$mutexName = Get-ControllerMutationMutexName
|
|
37
|
-
$mutex = New-Object System.Threading.Mutex($false, $mutexName)
|
|
38
|
-
$held = $false
|
|
39
|
-
try {
|
|
40
|
-
try {
|
|
41
|
-
$held = $mutex.WaitOne([TimeSpan]::FromSeconds(180))
|
|
42
|
-
} catch [System.Threading.AbandonedMutexException] {
|
|
43
|
-
$held = $true
|
|
44
|
-
}
|
|
45
|
-
if (-not $held) { Fail "controller mutation lock timeout for '$Name'" }
|
|
46
|
-
& $Body
|
|
47
|
-
} finally {
|
|
48
|
-
if ($held) {
|
|
49
|
-
try { $mutex.ReleaseMutex() } catch {}
|
|
50
|
-
}
|
|
51
|
-
$mutex.Dispose()
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function Ensure-Directory([string]$Path) { if (-not (Test-Path -LiteralPath $Path -PathType Container)) { New-Item -ItemType Directory -Path $Path -Force | Out-Null } }
|
|
56
|
-
function Ledger-Path([string]$Name) { Join-Path $RuntimeDir ("{0}.instance.json" -f $Name.ToLowerInvariant()) }
|
|
57
|
-
function Log-Path([string]$Name, [string]$Kind) { Join-Path $LogDir ("{0}.{1}.log" -f $Name.ToLowerInvariant(), $Kind) }
|
|
58
|
-
function Controller-StartFailurePath { Join-Path $RuntimeDir 'controller-start-failure.json' }
|
|
59
|
-
function Read-LogTailBounded([string]$Path, [int]$MaxChars = 8192) {
|
|
60
|
-
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { return '' }
|
|
61
|
-
try {
|
|
62
|
-
$text = (@(Get-Content -LiteralPath $Path -Encoding UTF8 -Tail 80 -ErrorAction Stop) -join [Environment]::NewLine)
|
|
63
|
-
if ($text.Length -gt $MaxChars) { return $text.Substring($text.Length - $MaxChars) }
|
|
64
|
-
return $text
|
|
65
|
-
} catch { return '' }
|
|
66
|
-
}
|
|
67
|
-
function Write-ControllerStartFailure([string]$Name, [System.Exception]$Exception) {
|
|
68
|
-
Ensure-Directory $RuntimeDir; Ensure-Directory $LogDir
|
|
69
|
-
$body = [ordered]@{
|
|
70
|
-
schema = 'deadbyte.controller-start-failure.v1'
|
|
71
|
-
command = $Name
|
|
72
|
-
failed_at_utc = [DateTime]::UtcNow.ToString('o')
|
|
73
|
-
message = [string]$Exception.Message
|
|
74
|
-
bridge_stdout_tail = Read-LogTailBounded (Log-Path 'Bridge' 'stdout')
|
|
75
|
-
bridge_stderr_tail = Read-LogTailBounded (Log-Path 'Bridge' 'stderr')
|
|
76
|
-
cloud_stdout_tail = Read-LogTailBounded (Log-Path 'Cloud' 'stdout')
|
|
77
|
-
cloud_stderr_tail = Read-LogTailBounded (Log-Path 'Cloud' 'stderr')
|
|
78
|
-
}
|
|
79
|
-
$path = Controller-StartFailurePath
|
|
80
|
-
$tmp = $path + '.tmp'
|
|
81
|
-
$json = $body | ConvertTo-Json -Depth 4
|
|
82
|
-
[System.IO.File]::WriteAllText($tmp, $json, (New-Object System.Text.UTF8Encoding($false)))
|
|
83
|
-
Move-Item -LiteralPath $tmp -Destination $path -Force
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
function Read-TextFileSafe([string]$Path) {
|
|
88
|
-
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { return '' }
|
|
89
|
-
$text = Get-Content -LiteralPath $Path -Raw -Encoding UTF8
|
|
90
|
-
if ($null -eq $text) { return '' }
|
|
91
|
-
return [string]$text
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function Get-Config {
|
|
95
|
-
if (-not (Test-Path -LiteralPath $ConfigPath -PathType Leaf)) {
|
|
96
|
-
Fail "missing trusted config '$ConfigPath'; copy deadbyte-controller.config.psd1.example and set exact long-lived executable paths"
|
|
97
|
-
}
|
|
98
|
-
$cfg = Import-PowerShellDataFile -LiteralPath $ConfigPath
|
|
99
|
-
if ($cfg.Schema -ne $ControllerSchema) { Fail 'controller config schema mismatch' }
|
|
100
|
-
$coreRoot = if ($cfg.ContainsKey('CoreRoot') -and -not [string]::IsNullOrWhiteSpace([string]$cfg.CoreRoot)) { [string]$cfg.CoreRoot } else { Split-Path -Parent $Root }
|
|
101
|
-
$coreRoot = [System.IO.Path]::GetFullPath($coreRoot)
|
|
102
|
-
if (-not (Test-Path -LiteralPath (Join-Path $coreRoot 'package.json') -PathType Leaf) -or -not (Test-Path -LiteralPath (Join-Path $coreRoot 'src\mcp-server.mjs') -PathType Leaf)) {
|
|
103
|
-
Fail "CoreRoot is not a DEADBYTE MCP package: $coreRoot"
|
|
104
|
-
}
|
|
105
|
-
$cfg['CoreRoot'] = $coreRoot
|
|
106
|
-
$workspaceRoot = if ($cfg.ContainsKey('WorkspaceRoot') -and -not [string]::IsNullOrWhiteSpace([string]$cfg.WorkspaceRoot)) { [string]$cfg.WorkspaceRoot } else { Join-Path $coreRoot 'workspaces' }
|
|
107
|
-
$workspaceRoot = [System.IO.Path]::GetFullPath($workspaceRoot)
|
|
108
|
-
if (-not (Test-Path -LiteralPath $workspaceRoot -PathType Container)) { Fail "WorkspaceRoot missing: $workspaceRoot" }
|
|
109
|
-
$cfg['WorkspaceRoot'] = $workspaceRoot
|
|
110
|
-
if ($cfg.ContainsKey('AutonomousSupervisorEnabled')) {
|
|
111
|
-
if ($cfg.AutonomousSupervisorEnabled -isnot [bool]) { Fail 'AutonomousSupervisorEnabled must be boolean' }
|
|
112
|
-
} else {
|
|
113
|
-
$cfg['AutonomousSupervisorEnabled'] = $false
|
|
114
|
-
}
|
|
115
|
-
$componentNames = @('Bridge','Cloud')
|
|
116
|
-
if ([bool]$cfg.AutonomousSupervisorEnabled) { $componentNames += 'Supervisor' }
|
|
117
|
-
foreach ($name in $componentNames) {
|
|
118
|
-
$spec = $cfg[$name]
|
|
119
|
-
if (-not $spec -or -not $spec.ContainsKey('FilePath') -or [string]::IsNullOrWhiteSpace([string]$spec.FilePath)) { Fail "$name FilePath missing" }
|
|
120
|
-
if (-not $spec.ContainsKey('Arguments')) { Fail "$name Arguments field missing" }
|
|
121
|
-
if (-not $spec.ContainsKey('WorkingDirectory')) { Fail "$name WorkingDirectory field missing" }
|
|
122
|
-
if (-not (Test-Path -LiteralPath $spec.FilePath -PathType Leaf)) { Fail "$name executable missing: $($spec.FilePath)" }
|
|
123
|
-
if (-not $spec.ContainsKey('FileSha256') -or [string]$spec.FileSha256 -cnotmatch '^[0-9a-f]{64}$') { Fail "$name FileSha256 invalid" }
|
|
124
|
-
$componentExecutableHash = Get-ExecutableHash ([string]$spec.FilePath)
|
|
125
|
-
if ($componentExecutableHash -ne [string]$spec.FileSha256) { Fail "$name executable hash mismatch: expected $($spec.FileSha256), found $componentExecutableHash" }
|
|
126
|
-
if ([string]::IsNullOrWhiteSpace([string]$spec.WorkingDirectory) -or -not (Test-Path -LiteralPath $spec.WorkingDirectory -PathType Container)) {
|
|
127
|
-
Fail "$name WorkingDirectory missing: $($spec.WorkingDirectory)"
|
|
128
|
-
}
|
|
129
|
-
Normalize-ComponentDependencies $name $spec
|
|
130
|
-
if ($name -eq 'Cloud' -and $spec.ContainsKey('Transport')) {
|
|
131
|
-
$transport = [string]$spec.Transport
|
|
132
|
-
$dependencyCount = @($spec.Dependencies).Count
|
|
133
|
-
if ($transport -eq 'deadbyte.named-tunnel.v1') {
|
|
134
|
-
if ($dependencyCount -ne 3) { Fail 'deadbyte.named-tunnel.v1 requires exactly three pinned dependencies' }
|
|
135
|
-
} elseif ($transport -eq 'deadbyte.named-tunnel.v2') {
|
|
136
|
-
if ($dependencyCount -ne 2) { Fail 'deadbyte.named-tunnel.v2 requires exactly cloudflared plus token-file pins' }
|
|
137
|
-
} else { Fail 'Cloud Transport unsupported' }
|
|
138
|
-
}
|
|
139
|
-
if ($name -in @('Bridge','Supervisor')) {
|
|
140
|
-
if (-not $spec.ContainsKey('ScriptPath') -or [string]::IsNullOrWhiteSpace([string]$spec.ScriptPath)) { Fail "$name ScriptPath missing" }
|
|
141
|
-
if (-not $spec.ContainsKey('ScriptSha256') -or [string]$spec.ScriptSha256 -cnotmatch '^[0-9a-f]{64}$') { Fail "$name ScriptSha256 invalid" }
|
|
142
|
-
$scriptPath = [System.IO.Path]::GetFullPath([string]$spec.ScriptPath)
|
|
143
|
-
if (-not (Test-Path -LiteralPath $scriptPath -PathType Leaf)) { Fail "$name ScriptPath missing: $scriptPath" }
|
|
144
|
-
if ($name -eq 'Supervisor' -and [System.IO.Path]::GetFileName($scriptPath) -ne 'autonomous-supervisor-host.mjs') { Fail 'Supervisor ScriptPath must target autonomous-supervisor-host.mjs' }
|
|
145
|
-
$scriptHash = Get-ExecutableHash $scriptPath
|
|
146
|
-
if ($scriptHash -ne [string]$spec.ScriptSha256) { Fail "$name script hash mismatch: expected $($spec.ScriptSha256), found $scriptHash" }
|
|
147
|
-
$spec['ScriptPath'] = $scriptPath
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
$cloudTransport = if ($cfg.Cloud.ContainsKey('Transport')) { [string]$cfg.Cloud.Transport } else { '' }
|
|
151
|
-
if ($cloudTransport -eq 'deadbyte.named-tunnel.v2') {
|
|
152
|
-
if (-not $cfg.ContainsKey('McpUrlFile') -or [string]::IsNullOrWhiteSpace([string]$cfg.McpUrlFile)) { Fail 'deadbyte.named-tunnel.v2 requires McpUrlFile' }
|
|
153
|
-
if (-not $cfg.ContainsKey('McpUrlFileSha256') -or [string]$cfg.McpUrlFileSha256 -cnotmatch '^[0-9a-f]{64}$') { Fail 'deadbyte.named-tunnel.v2 requires McpUrlFileSha256' }
|
|
154
|
-
$mcpUrlFile = [System.IO.Path]::GetFullPath([string]$cfg.McpUrlFile)
|
|
155
|
-
if (-not (Test-Path -LiteralPath $mcpUrlFile -PathType Leaf)) { Fail "McpUrlFile missing: $mcpUrlFile" }
|
|
156
|
-
$mcpUrlItem = Get-Item -LiteralPath $mcpUrlFile -Force
|
|
157
|
-
if (($mcpUrlItem.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0) { Fail 'McpUrlFile must not be a reparse point' }
|
|
158
|
-
$mcpUrlHash = Get-ExecutableHash $mcpUrlFile
|
|
159
|
-
if ($mcpUrlHash -ne [string]$cfg.McpUrlFileSha256) { Fail "McpUrlFile SHA-256 mismatch: expected $($cfg.McpUrlFileSha256), found $mcpUrlHash" }
|
|
160
|
-
$cfg['McpUrlFile'] = $mcpUrlFile
|
|
161
|
-
}
|
|
162
|
-
if ($cfg.ContainsKey('HostPolicyFile') -and -not [string]::IsNullOrWhiteSpace([string]$cfg.HostPolicyFile)) {
|
|
163
|
-
$hostPolicy = [System.IO.Path]::GetFullPath([string]$cfg.HostPolicyFile)
|
|
164
|
-
if (-not (Test-Path -LiteralPath $hostPolicy -PathType Leaf)) { Fail "HostPolicyFile missing: $hostPolicy" }
|
|
165
|
-
$cfg['HostPolicyFile'] = $hostPolicy
|
|
166
|
-
} else {
|
|
167
|
-
$cfg['HostPolicyFile'] = ''
|
|
168
|
-
}
|
|
169
|
-
if ($cfg.ContainsKey('MachineRuntimeEnabled')) {
|
|
170
|
-
if ($cfg.MachineRuntimeEnabled -isnot [bool]) { Fail 'MachineRuntimeEnabled must be boolean' }
|
|
171
|
-
} else {
|
|
172
|
-
$cfg['MachineRuntimeEnabled'] = $true
|
|
173
|
-
}
|
|
174
|
-
if ($cfg.ContainsKey('AgentRuntimeEnabled')) {
|
|
175
|
-
if ($cfg.AgentRuntimeEnabled -isnot [bool]) { Fail 'AgentRuntimeEnabled must be boolean' }
|
|
176
|
-
} else {
|
|
177
|
-
$cfg['AgentRuntimeEnabled'] = $false
|
|
178
|
-
}
|
|
179
|
-
if ($cfg.ContainsKey('ProcessRuntimeEnabled')) {
|
|
180
|
-
if ($cfg.ProcessRuntimeEnabled -isnot [bool]) { Fail 'ProcessRuntimeEnabled must be boolean' }
|
|
181
|
-
} else {
|
|
182
|
-
$cfg['ProcessRuntimeEnabled'] = $false
|
|
183
|
-
}
|
|
184
|
-
if ($cfg.ContainsKey('ProcessPolicyFile') -and -not [string]::IsNullOrWhiteSpace([string]$cfg.ProcessPolicyFile)) {
|
|
185
|
-
$processPolicy = [System.IO.Path]::GetFullPath([string]$cfg.ProcessPolicyFile)
|
|
186
|
-
if (-not (Test-Path -LiteralPath $processPolicy -PathType Leaf)) { Fail "ProcessPolicyFile missing: $processPolicy" }
|
|
187
|
-
$cfg['ProcessPolicyFile'] = $processPolicy
|
|
188
|
-
} else {
|
|
189
|
-
$cfg['ProcessPolicyFile'] = ''
|
|
190
|
-
}
|
|
1
|
+
#requires -Version 5.1
|
|
2
|
+
[CmdletBinding()]
|
|
3
|
+
param(
|
|
4
|
+
[Parameter(Position=0)]
|
|
5
|
+
[ValidateSet('menu','start','resume','park','stop','restart','status','url','test','fulltest','toggle','clean','machinetest','armprocess','disarmprocess','processstatus','armhost','disarmhost','hoststatus','hosttest','armcoding','disarmcoding','codingstatus','codingtest','armautonomous','disarmautonomous','autonomousstatus','autonomoustest','supervisorstart','supervisorstop','supervisorstatus','supervisorrecover','armdesktop','disarmdesktop','desktopstatus')]
|
|
6
|
+
[string]$Command = 'menu',
|
|
7
|
+
[switch]$Json
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
Set-StrictMode -Version 3.0
|
|
11
|
+
$ErrorActionPreference = 'Stop'
|
|
12
|
+
|
|
13
|
+
$ControllerSchema = 'deadbyte.controller.v1'
|
|
14
|
+
$Root = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
15
|
+
$RuntimeDir = if ($env:DEADBYTE_CONTROLLER_RUNTIME_ROOT) { [System.IO.Path]::GetFullPath([string]$env:DEADBYTE_CONTROLLER_RUNTIME_ROOT) } else { Join-Path $Root 'runtime' }
|
|
16
|
+
$LogDir = Join-Path $RuntimeDir 'logs'
|
|
17
|
+
$ConfigPath = if ($env:DEADBYTE_CONTROLLER_CONFIG) { $env:DEADBYTE_CONTROLLER_CONFIG } else { Join-Path $Root 'deadbyte-controller.config.psd1' }
|
|
18
|
+
|
|
19
|
+
function Fail([string]$Message) { throw "DEADBYTE controller: $Message" }
|
|
20
|
+
if ($Json -and $Command -ne 'status') { Fail 'JSON observation is available only for status' }
|
|
21
|
+
|
|
22
|
+
function Get-ControllerMutationMutexName {
|
|
23
|
+
$normalized = ([System.IO.Path]::GetFullPath($RuntimeDir)).TrimEnd('\\').ToLowerInvariant()
|
|
24
|
+
$sha = [System.Security.Cryptography.SHA256]::Create()
|
|
25
|
+
try {
|
|
26
|
+
$bytes = [System.Text.Encoding]::UTF8.GetBytes($normalized)
|
|
27
|
+
$digest = $sha.ComputeHash($bytes)
|
|
28
|
+
} finally {
|
|
29
|
+
$sha.Dispose()
|
|
30
|
+
}
|
|
31
|
+
$hex = ([System.BitConverter]::ToString($digest)).Replace('-', '').ToLowerInvariant().Substring(0, 24)
|
|
32
|
+
return "Local\DEADBYTE-MCP-Controller-$hex"
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function Invoke-WithControllerMutationLock([string]$Name, [scriptblock]$Body) {
|
|
36
|
+
$mutexName = Get-ControllerMutationMutexName
|
|
37
|
+
$mutex = New-Object System.Threading.Mutex($false, $mutexName)
|
|
38
|
+
$held = $false
|
|
39
|
+
try {
|
|
40
|
+
try {
|
|
41
|
+
$held = $mutex.WaitOne([TimeSpan]::FromSeconds(180))
|
|
42
|
+
} catch [System.Threading.AbandonedMutexException] {
|
|
43
|
+
$held = $true
|
|
44
|
+
}
|
|
45
|
+
if (-not $held) { Fail "controller mutation lock timeout for '$Name'" }
|
|
46
|
+
& $Body
|
|
47
|
+
} finally {
|
|
48
|
+
if ($held) {
|
|
49
|
+
try { $mutex.ReleaseMutex() } catch {}
|
|
50
|
+
}
|
|
51
|
+
$mutex.Dispose()
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function Ensure-Directory([string]$Path) { if (-not (Test-Path -LiteralPath $Path -PathType Container)) { New-Item -ItemType Directory -Path $Path -Force | Out-Null } }
|
|
56
|
+
function Ledger-Path([string]$Name) { Join-Path $RuntimeDir ("{0}.instance.json" -f $Name.ToLowerInvariant()) }
|
|
57
|
+
function Log-Path([string]$Name, [string]$Kind) { Join-Path $LogDir ("{0}.{1}.log" -f $Name.ToLowerInvariant(), $Kind) }
|
|
58
|
+
function Controller-StartFailurePath { Join-Path $RuntimeDir 'controller-start-failure.json' }
|
|
59
|
+
function Read-LogTailBounded([string]$Path, [int]$MaxChars = 8192) {
|
|
60
|
+
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { return '' }
|
|
61
|
+
try {
|
|
62
|
+
$text = (@(Get-Content -LiteralPath $Path -Encoding UTF8 -Tail 80 -ErrorAction Stop) -join [Environment]::NewLine)
|
|
63
|
+
if ($text.Length -gt $MaxChars) { return $text.Substring($text.Length - $MaxChars) }
|
|
64
|
+
return $text
|
|
65
|
+
} catch { return '' }
|
|
66
|
+
}
|
|
67
|
+
function Write-ControllerStartFailure([string]$Name, [System.Exception]$Exception) {
|
|
68
|
+
Ensure-Directory $RuntimeDir; Ensure-Directory $LogDir
|
|
69
|
+
$body = [ordered]@{
|
|
70
|
+
schema = 'deadbyte.controller-start-failure.v1'
|
|
71
|
+
command = $Name
|
|
72
|
+
failed_at_utc = [DateTime]::UtcNow.ToString('o')
|
|
73
|
+
message = [string]$Exception.Message
|
|
74
|
+
bridge_stdout_tail = Read-LogTailBounded (Log-Path 'Bridge' 'stdout')
|
|
75
|
+
bridge_stderr_tail = Read-LogTailBounded (Log-Path 'Bridge' 'stderr')
|
|
76
|
+
cloud_stdout_tail = Read-LogTailBounded (Log-Path 'Cloud' 'stdout')
|
|
77
|
+
cloud_stderr_tail = Read-LogTailBounded (Log-Path 'Cloud' 'stderr')
|
|
78
|
+
}
|
|
79
|
+
$path = Controller-StartFailurePath
|
|
80
|
+
$tmp = $path + '.tmp'
|
|
81
|
+
$json = $body | ConvertTo-Json -Depth 4
|
|
82
|
+
[System.IO.File]::WriteAllText($tmp, $json, (New-Object System.Text.UTF8Encoding($false)))
|
|
83
|
+
Move-Item -LiteralPath $tmp -Destination $path -Force
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
function Read-TextFileSafe([string]$Path) {
|
|
88
|
+
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { return '' }
|
|
89
|
+
$text = Get-Content -LiteralPath $Path -Raw -Encoding UTF8
|
|
90
|
+
if ($null -eq $text) { return '' }
|
|
91
|
+
return [string]$text
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function Get-Config {
|
|
95
|
+
if (-not (Test-Path -LiteralPath $ConfigPath -PathType Leaf)) {
|
|
96
|
+
Fail "missing trusted config '$ConfigPath'; copy deadbyte-controller.config.psd1.example and set exact long-lived executable paths"
|
|
97
|
+
}
|
|
98
|
+
$cfg = Import-PowerShellDataFile -LiteralPath $ConfigPath
|
|
99
|
+
if ($cfg.Schema -ne $ControllerSchema) { Fail 'controller config schema mismatch' }
|
|
100
|
+
$coreRoot = if ($cfg.ContainsKey('CoreRoot') -and -not [string]::IsNullOrWhiteSpace([string]$cfg.CoreRoot)) { [string]$cfg.CoreRoot } else { Split-Path -Parent $Root }
|
|
101
|
+
$coreRoot = [System.IO.Path]::GetFullPath($coreRoot)
|
|
102
|
+
if (-not (Test-Path -LiteralPath (Join-Path $coreRoot 'package.json') -PathType Leaf) -or -not (Test-Path -LiteralPath (Join-Path $coreRoot 'src\mcp-server.mjs') -PathType Leaf)) {
|
|
103
|
+
Fail "CoreRoot is not a DEADBYTE MCP package: $coreRoot"
|
|
104
|
+
}
|
|
105
|
+
$cfg['CoreRoot'] = $coreRoot
|
|
106
|
+
$workspaceRoot = if ($cfg.ContainsKey('WorkspaceRoot') -and -not [string]::IsNullOrWhiteSpace([string]$cfg.WorkspaceRoot)) { [string]$cfg.WorkspaceRoot } else { Join-Path $coreRoot 'workspaces' }
|
|
107
|
+
$workspaceRoot = [System.IO.Path]::GetFullPath($workspaceRoot)
|
|
108
|
+
if (-not (Test-Path -LiteralPath $workspaceRoot -PathType Container)) { Fail "WorkspaceRoot missing: $workspaceRoot" }
|
|
109
|
+
$cfg['WorkspaceRoot'] = $workspaceRoot
|
|
110
|
+
if ($cfg.ContainsKey('AutonomousSupervisorEnabled')) {
|
|
111
|
+
if ($cfg.AutonomousSupervisorEnabled -isnot [bool]) { Fail 'AutonomousSupervisorEnabled must be boolean' }
|
|
112
|
+
} else {
|
|
113
|
+
$cfg['AutonomousSupervisorEnabled'] = $false
|
|
114
|
+
}
|
|
115
|
+
$componentNames = @('Bridge','Cloud')
|
|
116
|
+
if ([bool]$cfg.AutonomousSupervisorEnabled) { $componentNames += 'Supervisor' }
|
|
117
|
+
foreach ($name in $componentNames) {
|
|
118
|
+
$spec = $cfg[$name]
|
|
119
|
+
if (-not $spec -or -not $spec.ContainsKey('FilePath') -or [string]::IsNullOrWhiteSpace([string]$spec.FilePath)) { Fail "$name FilePath missing" }
|
|
120
|
+
if (-not $spec.ContainsKey('Arguments')) { Fail "$name Arguments field missing" }
|
|
121
|
+
if (-not $spec.ContainsKey('WorkingDirectory')) { Fail "$name WorkingDirectory field missing" }
|
|
122
|
+
if (-not (Test-Path -LiteralPath $spec.FilePath -PathType Leaf)) { Fail "$name executable missing: $($spec.FilePath)" }
|
|
123
|
+
if (-not $spec.ContainsKey('FileSha256') -or [string]$spec.FileSha256 -cnotmatch '^[0-9a-f]{64}$') { Fail "$name FileSha256 invalid" }
|
|
124
|
+
$componentExecutableHash = Get-ExecutableHash ([string]$spec.FilePath)
|
|
125
|
+
if ($componentExecutableHash -ne [string]$spec.FileSha256) { Fail "$name executable hash mismatch: expected $($spec.FileSha256), found $componentExecutableHash" }
|
|
126
|
+
if ([string]::IsNullOrWhiteSpace([string]$spec.WorkingDirectory) -or -not (Test-Path -LiteralPath $spec.WorkingDirectory -PathType Container)) {
|
|
127
|
+
Fail "$name WorkingDirectory missing: $($spec.WorkingDirectory)"
|
|
128
|
+
}
|
|
129
|
+
Normalize-ComponentDependencies $name $spec
|
|
130
|
+
if ($name -eq 'Cloud' -and $spec.ContainsKey('Transport')) {
|
|
131
|
+
$transport = [string]$spec.Transport
|
|
132
|
+
$dependencyCount = @($spec.Dependencies).Count
|
|
133
|
+
if ($transport -eq 'deadbyte.named-tunnel.v1') {
|
|
134
|
+
if ($dependencyCount -ne 3) { Fail 'deadbyte.named-tunnel.v1 requires exactly three pinned dependencies' }
|
|
135
|
+
} elseif ($transport -eq 'deadbyte.named-tunnel.v2') {
|
|
136
|
+
if ($dependencyCount -ne 2) { Fail 'deadbyte.named-tunnel.v2 requires exactly cloudflared plus token-file pins' }
|
|
137
|
+
} else { Fail 'Cloud Transport unsupported' }
|
|
138
|
+
}
|
|
139
|
+
if ($name -in @('Bridge','Supervisor')) {
|
|
140
|
+
if (-not $spec.ContainsKey('ScriptPath') -or [string]::IsNullOrWhiteSpace([string]$spec.ScriptPath)) { Fail "$name ScriptPath missing" }
|
|
141
|
+
if (-not $spec.ContainsKey('ScriptSha256') -or [string]$spec.ScriptSha256 -cnotmatch '^[0-9a-f]{64}$') { Fail "$name ScriptSha256 invalid" }
|
|
142
|
+
$scriptPath = [System.IO.Path]::GetFullPath([string]$spec.ScriptPath)
|
|
143
|
+
if (-not (Test-Path -LiteralPath $scriptPath -PathType Leaf)) { Fail "$name ScriptPath missing: $scriptPath" }
|
|
144
|
+
if ($name -eq 'Supervisor' -and [System.IO.Path]::GetFileName($scriptPath) -ne 'autonomous-supervisor-host.mjs') { Fail 'Supervisor ScriptPath must target autonomous-supervisor-host.mjs' }
|
|
145
|
+
$scriptHash = Get-ExecutableHash $scriptPath
|
|
146
|
+
if ($scriptHash -ne [string]$spec.ScriptSha256) { Fail "$name script hash mismatch: expected $($spec.ScriptSha256), found $scriptHash" }
|
|
147
|
+
$spec['ScriptPath'] = $scriptPath
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
$cloudTransport = if ($cfg.Cloud.ContainsKey('Transport')) { [string]$cfg.Cloud.Transport } else { '' }
|
|
151
|
+
if ($cloudTransport -eq 'deadbyte.named-tunnel.v2') {
|
|
152
|
+
if (-not $cfg.ContainsKey('McpUrlFile') -or [string]::IsNullOrWhiteSpace([string]$cfg.McpUrlFile)) { Fail 'deadbyte.named-tunnel.v2 requires McpUrlFile' }
|
|
153
|
+
if (-not $cfg.ContainsKey('McpUrlFileSha256') -or [string]$cfg.McpUrlFileSha256 -cnotmatch '^[0-9a-f]{64}$') { Fail 'deadbyte.named-tunnel.v2 requires McpUrlFileSha256' }
|
|
154
|
+
$mcpUrlFile = [System.IO.Path]::GetFullPath([string]$cfg.McpUrlFile)
|
|
155
|
+
if (-not (Test-Path -LiteralPath $mcpUrlFile -PathType Leaf)) { Fail "McpUrlFile missing: $mcpUrlFile" }
|
|
156
|
+
$mcpUrlItem = Get-Item -LiteralPath $mcpUrlFile -Force
|
|
157
|
+
if (($mcpUrlItem.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0) { Fail 'McpUrlFile must not be a reparse point' }
|
|
158
|
+
$mcpUrlHash = Get-ExecutableHash $mcpUrlFile
|
|
159
|
+
if ($mcpUrlHash -ne [string]$cfg.McpUrlFileSha256) { Fail "McpUrlFile SHA-256 mismatch: expected $($cfg.McpUrlFileSha256), found $mcpUrlHash" }
|
|
160
|
+
$cfg['McpUrlFile'] = $mcpUrlFile
|
|
161
|
+
}
|
|
162
|
+
if ($cfg.ContainsKey('HostPolicyFile') -and -not [string]::IsNullOrWhiteSpace([string]$cfg.HostPolicyFile)) {
|
|
163
|
+
$hostPolicy = [System.IO.Path]::GetFullPath([string]$cfg.HostPolicyFile)
|
|
164
|
+
if (-not (Test-Path -LiteralPath $hostPolicy -PathType Leaf)) { Fail "HostPolicyFile missing: $hostPolicy" }
|
|
165
|
+
$cfg['HostPolicyFile'] = $hostPolicy
|
|
166
|
+
} else {
|
|
167
|
+
$cfg['HostPolicyFile'] = ''
|
|
168
|
+
}
|
|
169
|
+
if ($cfg.ContainsKey('MachineRuntimeEnabled')) {
|
|
170
|
+
if ($cfg.MachineRuntimeEnabled -isnot [bool]) { Fail 'MachineRuntimeEnabled must be boolean' }
|
|
171
|
+
} else {
|
|
172
|
+
$cfg['MachineRuntimeEnabled'] = $true
|
|
173
|
+
}
|
|
174
|
+
if ($cfg.ContainsKey('AgentRuntimeEnabled')) {
|
|
175
|
+
if ($cfg.AgentRuntimeEnabled -isnot [bool]) { Fail 'AgentRuntimeEnabled must be boolean' }
|
|
176
|
+
} else {
|
|
177
|
+
$cfg['AgentRuntimeEnabled'] = $false
|
|
178
|
+
}
|
|
179
|
+
if ($cfg.ContainsKey('ProcessRuntimeEnabled')) {
|
|
180
|
+
if ($cfg.ProcessRuntimeEnabled -isnot [bool]) { Fail 'ProcessRuntimeEnabled must be boolean' }
|
|
181
|
+
} else {
|
|
182
|
+
$cfg['ProcessRuntimeEnabled'] = $false
|
|
183
|
+
}
|
|
184
|
+
if ($cfg.ContainsKey('ProcessPolicyFile') -and -not [string]::IsNullOrWhiteSpace([string]$cfg.ProcessPolicyFile)) {
|
|
185
|
+
$processPolicy = [System.IO.Path]::GetFullPath([string]$cfg.ProcessPolicyFile)
|
|
186
|
+
if (-not (Test-Path -LiteralPath $processPolicy -PathType Leaf)) { Fail "ProcessPolicyFile missing: $processPolicy" }
|
|
187
|
+
$cfg['ProcessPolicyFile'] = $processPolicy
|
|
188
|
+
} else {
|
|
189
|
+
$cfg['ProcessPolicyFile'] = ''
|
|
190
|
+
}
|
|
191
191
|
if ([bool]$cfg.ProcessRuntimeEnabled -and [string]::IsNullOrWhiteSpace([string]$cfg.ProcessPolicyFile)) { Fail 'ProcessRuntimeEnabled requires ProcessPolicyFile' }
|
|
192
|
-
if ($cfg.ContainsKey('CodingRuntimeEnabled')) {
|
|
193
|
-
if ($cfg.CodingRuntimeEnabled -isnot [bool]) { Fail 'CodingRuntimeEnabled must be boolean' }
|
|
194
|
-
} else {
|
|
195
|
-
$cfg['CodingRuntimeEnabled'] = $false
|
|
196
|
-
}
|
|
197
|
-
if ($cfg.ContainsKey('CodingPolicyFile') -and -not [string]::IsNullOrWhiteSpace([string]$cfg.CodingPolicyFile)) {
|
|
198
|
-
$codingPolicy = [System.IO.Path]::GetFullPath([string]$cfg.CodingPolicyFile)
|
|
199
|
-
if (-not (Test-Path -LiteralPath $codingPolicy -PathType Leaf)) { Fail "CodingPolicyFile missing: $codingPolicy" }
|
|
200
|
-
$cfg['CodingPolicyFile'] = $codingPolicy
|
|
201
|
-
} else {
|
|
202
|
-
$cfg['CodingPolicyFile'] = ''
|
|
203
|
-
}
|
|
204
|
-
if ([bool]$cfg.CodingRuntimeEnabled -and [string]::IsNullOrWhiteSpace([string]$cfg.CodingPolicyFile)) { Fail 'CodingRuntimeEnabled requires CodingPolicyFile' }
|
|
205
|
-
$semanticConfigFile = if ($cfg.ContainsKey('SemanticLspConfigFile')) { [string]$cfg.SemanticLspConfigFile } else { '' }
|
|
206
|
-
$semanticConfigSha = if ($cfg.ContainsKey('SemanticLspConfigSha256')) { ([string]$cfg.SemanticLspConfigSha256).ToLowerInvariant() } else { '' }
|
|
207
|
-
if ([string]::IsNullOrWhiteSpace($semanticConfigFile) -ne [string]::IsNullOrWhiteSpace($semanticConfigSha)) { Fail 'SemanticLspConfigFile and SemanticLspConfigSha256 must be supplied together' }
|
|
208
|
-
if (-not [string]::IsNullOrWhiteSpace($semanticConfigFile)) {
|
|
209
|
-
if (-not [bool]$cfg.CodingRuntimeEnabled) { Fail 'SemanticLspConfigFile requires CodingRuntimeEnabled' }
|
|
210
|
-
if ($semanticConfigSha -cnotmatch '^[0-9a-f]{64}$') { Fail 'SemanticLspConfigSha256 invalid' }
|
|
211
|
-
$semanticConfigFile = [System.IO.Path]::GetFullPath($semanticConfigFile)
|
|
212
|
-
if (-not (Test-Path -LiteralPath $semanticConfigFile -PathType Leaf)) { Fail "SemanticLspConfigFile missing: $semanticConfigFile" }
|
|
213
|
-
$semanticItem = Get-Item -LiteralPath $semanticConfigFile -Force
|
|
214
|
-
if (($semanticItem.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0) { Fail 'SemanticLspConfigFile must not be a reparse point' }
|
|
215
|
-
$semanticObserved = Get-ExecutableHash $semanticConfigFile
|
|
216
|
-
if ($semanticObserved -ne $semanticConfigSha) { Fail "semantic LSP config SHA-256 mismatch: expected $semanticConfigSha, found $semanticObserved" }
|
|
217
|
-
$cfg['SemanticLspConfigFile'] = $semanticConfigFile
|
|
218
|
-
$cfg['SemanticLspConfigSha256'] = $semanticConfigSha
|
|
219
|
-
} else {
|
|
220
|
-
$cfg['SemanticLspConfigFile'] = ''
|
|
221
|
-
$cfg['SemanticLspConfigSha256'] = ''
|
|
222
|
-
}
|
|
223
|
-
if ($cfg.ContainsKey('MachineFsRuntimeEnabled')) {
|
|
224
|
-
if ($cfg.MachineFsRuntimeEnabled -isnot [bool]) { Fail 'MachineFsRuntimeEnabled must be boolean' }
|
|
225
|
-
} else {
|
|
226
|
-
$cfg['MachineFsRuntimeEnabled'] = $false
|
|
227
|
-
}
|
|
228
|
-
if ([bool]$cfg.MachineFsRuntimeEnabled -and -not [bool]$cfg.CodingRuntimeEnabled) { Fail 'MachineFsRuntimeEnabled requires CodingRuntimeEnabled' }
|
|
229
|
-
if ($cfg.ContainsKey('ObservationRuntimeEnabled')) {
|
|
230
|
-
if ($cfg.ObservationRuntimeEnabled -isnot [bool]) { Fail 'ObservationRuntimeEnabled must be boolean' }
|
|
231
|
-
} else {
|
|
232
|
-
$cfg['ObservationRuntimeEnabled'] = $false
|
|
233
|
-
}
|
|
234
|
-
if ([bool]$cfg.ObservationRuntimeEnabled -and -not [bool]$cfg.MachineRuntimeEnabled) { Fail 'ObservationRuntimeEnabled requires MachineRuntimeEnabled' }
|
|
235
|
-
if ([bool]$cfg.ObservationRuntimeEnabled -and -not [bool]$cfg.CodingRuntimeEnabled) { Fail 'ObservationRuntimeEnabled requires CodingRuntimeEnabled' }
|
|
236
|
-
if ($cfg.ContainsKey('DesktopRuntimeEnabled')) {
|
|
237
|
-
if ($cfg.DesktopRuntimeEnabled -isnot [bool]) { Fail 'DesktopRuntimeEnabled must be boolean' }
|
|
238
|
-
} else {
|
|
239
|
-
$cfg['DesktopRuntimeEnabled'] = $false
|
|
240
|
-
}
|
|
241
|
-
if ($cfg.ContainsKey('DesktopPolicyFile') -and -not [string]::IsNullOrWhiteSpace([string]$cfg.DesktopPolicyFile)) {
|
|
242
|
-
$desktopPolicy = [System.IO.Path]::GetFullPath([string]$cfg.DesktopPolicyFile)
|
|
243
|
-
if (-not (Test-Path -LiteralPath $desktopPolicy -PathType Leaf)) { Fail "DesktopPolicyFile missing: $desktopPolicy" }
|
|
244
|
-
$cfg['DesktopPolicyFile'] = $desktopPolicy
|
|
245
|
-
} else {
|
|
246
|
-
$cfg['DesktopPolicyFile'] = ''
|
|
247
|
-
}
|
|
248
|
-
if ([bool]$cfg.DesktopRuntimeEnabled -and [string]::IsNullOrWhiteSpace([string]$cfg.DesktopPolicyFile)) { Fail 'DesktopRuntimeEnabled requires DesktopPolicyFile' }
|
|
249
|
-
if ($cfg.ContainsKey('AutonomousRuntimeEnabled')) {
|
|
250
|
-
if ($cfg.AutonomousRuntimeEnabled -isnot [bool]) { Fail 'AutonomousRuntimeEnabled must be boolean' }
|
|
251
|
-
} else {
|
|
252
|
-
$cfg['AutonomousRuntimeEnabled'] = $false
|
|
253
|
-
}
|
|
254
|
-
if ($cfg.ContainsKey('AutonomousPolicyFile') -and -not [string]::IsNullOrWhiteSpace([string]$cfg.AutonomousPolicyFile)) {
|
|
255
|
-
$autonomousPolicy = [System.IO.Path]::GetFullPath([string]$cfg.AutonomousPolicyFile)
|
|
256
|
-
if (-not (Test-Path -LiteralPath $autonomousPolicy -PathType Leaf)) { Fail "AutonomousPolicyFile missing: $autonomousPolicy" }
|
|
257
|
-
$cfg['AutonomousPolicyFile'] = $autonomousPolicy
|
|
258
|
-
} else {
|
|
259
|
-
$cfg['AutonomousPolicyFile'] = ''
|
|
260
|
-
}
|
|
261
|
-
if ([bool]$cfg.AutonomousRuntimeEnabled -and -not [bool]$cfg.CodingRuntimeEnabled) { Fail 'AutonomousRuntimeEnabled requires CodingRuntimeEnabled' }
|
|
262
|
-
if ([bool]$cfg.AutonomousRuntimeEnabled -and [string]::IsNullOrWhiteSpace([string]$cfg.AutonomousPolicyFile)) { Fail 'AutonomousRuntimeEnabled requires AutonomousPolicyFile' }
|
|
263
|
-
if ([bool]$cfg.AutonomousSupervisorEnabled -and -not [bool]$cfg.AutonomousRuntimeEnabled) { Fail 'AutonomousSupervisorEnabled requires AutonomousRuntimeEnabled' }
|
|
264
|
-
if ([int]$cfg.StartTimeoutSeconds -lt 1 -or [int]$cfg.StartTimeoutSeconds -gt 120) { Fail 'StartTimeoutSeconds out of range' }
|
|
265
|
-
if ([int]$cfg.ProbeTimeoutSeconds -lt 1 -or [int]$cfg.ProbeTimeoutSeconds -gt 15) { Fail 'ProbeTimeoutSeconds out of range' }
|
|
266
|
-
return $cfg
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
function Assert-ControllerRootBound($Cfg) {
|
|
270
|
-
$expected = [System.IO.Path]::GetFullPath((Join-Path ([string]$Cfg.CoreRoot) 'controller')).TrimEnd('\')
|
|
271
|
-
$actual = [System.IO.Path]::GetFullPath($Root).TrimEnd('\')
|
|
272
|
-
if (-not [string]::Equals($actual, $expected, [System.StringComparison]::OrdinalIgnoreCase)) {
|
|
273
|
-
Fail "controller root mismatch: running '$actual', expected '$expected'; use canonical DEADBYTE-MCP.cmd"
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
function Get-ExecutableHash([string]$Path) {
|
|
278
|
-
(Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash.ToLowerInvariant()
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
function Normalize-ComponentDependencies([string]$Name, $Spec) {
|
|
282
|
-
if (-not $Spec.ContainsKey('Dependencies')) { $Spec['Dependencies'] = @(); return }
|
|
283
|
-
$dependencies = @($Spec.Dependencies)
|
|
284
|
-
if ($dependencies.Count -gt 16) { Fail "$Name dependency count exceeds 16" }
|
|
285
|
-
$normalized = @()
|
|
286
|
-
foreach ($dependency in $dependencies) {
|
|
287
|
-
$depPath = [System.IO.Path]::GetFullPath([string]$dependency.Path)
|
|
288
|
-
$depHash = ([string]$dependency.Sha256).ToLowerInvariant()
|
|
289
|
-
if ([string]::IsNullOrWhiteSpace($depPath) -or -not (Test-Path -LiteralPath $depPath -PathType Leaf)) { Fail "$Name dependency missing: $depPath" }
|
|
290
|
-
if ($depHash -cnotmatch '^[0-9a-f]{64}$') { Fail "$Name dependency SHA-256 invalid: $depPath" }
|
|
291
|
-
$item = Get-Item -LiteralPath $depPath -Force
|
|
292
|
-
if (($item.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0) { Fail "$Name dependency must not be a reparse point: $depPath" }
|
|
293
|
-
$actual = Get-ExecutableHash $depPath
|
|
294
|
-
if ($actual -ne $depHash) { Fail "$Name dependency hash mismatch in trusted config: $depPath" }
|
|
295
|
-
$depName = if ([string]::IsNullOrWhiteSpace([string]$dependency.Name)) { [System.IO.Path]::GetFileName($depPath) } else { [string]$dependency.Name }
|
|
296
|
-
$normalized += [ordered]@{ Name=$depName; Path=$depPath; Sha256=$depHash }
|
|
297
|
-
}
|
|
298
|
-
$Spec['Dependencies'] = $normalized
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
function Assert-ComponentDependencies([string]$Name, $Spec, [string]$Phase) {
|
|
302
|
-
foreach ($dependency in @($Spec.Dependencies)) {
|
|
303
|
-
$depPath = [string]$dependency.Path
|
|
304
|
-
$expected = [string]$dependency.Sha256
|
|
305
|
-
if (-not (Test-Path -LiteralPath $depPath -PathType Leaf)) { Fail "$Name dependency missing ${Phase}: $depPath" }
|
|
306
|
-
$actual = Get-ExecutableHash $depPath
|
|
307
|
-
if ($actual -ne $expected) { Fail "$Name dependency hash mismatch ${Phase}: $depPath expected $expected found $actual" }
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
function Get-LimitedProcessImagePath([int]$ProcessId) {
|
|
312
|
-
if (-not ('DeadbyteProcessQuery' -as [type])) {
|
|
313
|
-
$source = @'
|
|
314
|
-
using System;
|
|
315
|
-
using System.Text;
|
|
316
|
-
using System.Runtime.InteropServices;
|
|
317
|
-
public static class DeadbyteProcessQuery {
|
|
318
|
-
[DllImport("kernel32.dll", SetLastError=true)] static extern IntPtr OpenProcess(uint access, bool inheritHandle, int processId);
|
|
319
|
-
[DllImport("kernel32.dll", CharSet=CharSet.Unicode, SetLastError=true)] static extern bool QueryFullProcessImageName(IntPtr process, uint flags, StringBuilder path, ref int size);
|
|
320
|
-
[DllImport("kernel32.dll")] static extern bool CloseHandle(IntPtr handle);
|
|
321
|
-
public static string GetImagePath(int processId) { const uint PROCESS_QUERY_LIMITED_INFORMATION=0x1000; IntPtr h=OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION,false,processId); if(h==IntPtr.Zero)return null; try { var b=new StringBuilder(32768); int n=b.Capacity; return QueryFullProcessImageName(h,0,b,ref n)?b.ToString():null; } finally { CloseHandle(h); } }
|
|
322
|
-
}
|
|
323
|
-
'@
|
|
324
|
-
Add-Type -TypeDefinition $source -Language CSharp -ErrorAction Stop
|
|
325
|
-
}
|
|
326
|
-
try { $path=[DeadbyteProcessQuery]::GetImagePath($ProcessId); if ($null -eq $path) { return '' }; return [string]$path } catch { return '' }
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
function Get-ProcessObservation([int]$ProcessId) {
|
|
330
|
-
try { $p = Get-Process -Id $ProcessId -ErrorAction Stop } catch { return $null }
|
|
331
|
-
$image = ''
|
|
332
|
-
try {
|
|
333
|
-
$cim = Get-CimInstance Win32_Process -Filter ("ProcessId={0}" -f $ProcessId) -ErrorAction Stop
|
|
334
|
-
if ($cim -and -not [string]::IsNullOrWhiteSpace([string]$cim.ExecutablePath)) {
|
|
335
|
-
$image = [string]$cim.ExecutablePath
|
|
336
|
-
}
|
|
337
|
-
} catch {}
|
|
338
|
-
if ([string]::IsNullOrWhiteSpace($image)) {
|
|
339
|
-
try { $image = [string]$p.Path } catch {}
|
|
340
|
-
}
|
|
341
|
-
if ([string]::IsNullOrWhiteSpace($image)) {
|
|
342
|
-
try { $image = Get-LimitedProcessImagePath $ProcessId } catch {}
|
|
343
|
-
}
|
|
344
|
-
if ([string]::IsNullOrWhiteSpace($image)) {
|
|
345
|
-
try { $image = [string]$p.MainModule.FileName } catch {}
|
|
346
|
-
}
|
|
347
|
-
if ([string]::IsNullOrWhiteSpace($image)) { return $null }
|
|
348
|
-
try { $image = [System.IO.Path]::GetFullPath($image) } catch { return $null }
|
|
349
|
-
if (-not (Test-Path -LiteralPath $image -PathType Leaf)) { return $null }
|
|
350
|
-
try { $imageHash = Get-ExecutableHash $image } catch { return $null }
|
|
351
|
-
[pscustomobject]@{
|
|
352
|
-
pid = [int]$p.Id
|
|
353
|
-
creation_time_utc = $p.StartTime.ToUniversalTime().ToString('o')
|
|
354
|
-
image_path = $image
|
|
355
|
-
image_sha256 = $imageHash
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
function Read-Ledger([string]$Name) {
|
|
360
|
-
$path = Ledger-Path $Name
|
|
361
|
-
if (-not (Test-Path -LiteralPath $path -PathType Leaf)) { return $null }
|
|
362
|
-
try { return (Get-Content -LiteralPath $path -Raw -Encoding UTF8 | ConvertFrom-Json) } catch { return [pscustomobject]@{ corrupt = $true; path = $path } }
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
function Test-OwnedInstance([string]$Name) {
|
|
366
|
-
$ledger = Read-Ledger $Name
|
|
367
|
-
if (-not $ledger) { return [pscustomobject]@{ Name=$Name; Alive=$false; Reason='no-ledger'; Ledger=$null; Observation=$null } }
|
|
368
|
-
if ($ledger.PSObject.Properties.Name -contains 'corrupt' -and $ledger.corrupt -eq $true) { return [pscustomobject]@{ Name=$Name; Alive=$false; Reason='corrupt-ledger'; Ledger=$ledger; Observation=$null } }
|
|
369
|
-
if ($ledger.schema -ne $ControllerSchema -or -not $ledger.instance_nonce) { return [pscustomobject]@{ Name=$Name; Alive=$false; Reason='ledger-schema'; Ledger=$ledger; Observation=$null } }
|
|
370
|
-
if ($Name -in @('Bridge','Supervisor')) {
|
|
371
|
-
if (-not ($ledger.PSObject.Properties.Name -contains 'script_path') -or -not ($ledger.PSObject.Properties.Name -contains 'script_sha256')) {
|
|
372
|
-
return [pscustomobject]@{ Name=$Name; Alive=$false; Reason='ledger-schema'; Ledger=$ledger; Observation=$null }
|
|
373
|
-
}
|
|
374
|
-
$scriptPath = [string]$ledger.script_path
|
|
375
|
-
$scriptHash = [string]$ledger.script_sha256
|
|
376
|
-
if ([string]::IsNullOrWhiteSpace($scriptPath) -or $scriptHash -cnotmatch '^[0-9a-f]{64}$' -or -not (Test-Path -LiteralPath $scriptPath -PathType Leaf)) {
|
|
377
|
-
return [pscustomobject]@{ Name=$Name; Alive=$false; Reason='script-hash-mismatch'; Ledger=$ledger; Observation=$null }
|
|
378
|
-
}
|
|
379
|
-
try { $observedScriptHash = Get-ExecutableHash $scriptPath } catch { $observedScriptHash = '' }
|
|
380
|
-
if ($observedScriptHash -ne $scriptHash) { return [pscustomobject]@{ Name=$Name; Alive=$false; Reason='script-hash-mismatch'; Ledger=$ledger; Observation=$null } }
|
|
381
|
-
}
|
|
382
|
-
if ($ledger.PSObject.Properties.Name -contains 'dependencies') {
|
|
383
|
-
foreach ($dependency in @($ledger.dependencies)) {
|
|
384
|
-
$depPath = [string]$dependency.path
|
|
385
|
-
$depHash = [string]$dependency.sha256
|
|
386
|
-
if ([string]::IsNullOrWhiteSpace($depPath) -or $depHash -cnotmatch '^[0-9a-f]{64}$' -or -not (Test-Path -LiteralPath $depPath -PathType Leaf)) {
|
|
387
|
-
return [pscustomobject]@{ Name=$Name; Alive=$false; Reason='dependency-hash-mismatch'; Ledger=$ledger; Observation=$null }
|
|
388
|
-
}
|
|
389
|
-
try { $observedDependencyHash = Get-ExecutableHash $depPath } catch { $observedDependencyHash = '' }
|
|
390
|
-
if ($observedDependencyHash -ne $depHash) { return [pscustomobject]@{ Name=$Name; Alive=$false; Reason='dependency-hash-mismatch'; Ledger=$ledger; Observation=$null } }
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
$obs = Get-ProcessObservation ([int]$ledger.pid)
|
|
394
|
-
if (-not $obs) { try { [void](Get-Process -Id ([int]$ledger.pid) -ErrorAction Stop); return [pscustomobject]@{ Name=$Name; Alive=$false; Reason='process-unobservable'; Ledger=$ledger; Observation=$null } } catch { return [pscustomobject]@{ Name=$Name; Alive=$false; Reason='process-missing'; Ledger=$ledger; Observation=$null } } }
|
|
395
|
-
if ($obs.creation_time_utc -ne [string]$ledger.creation_time_utc) { return [pscustomobject]@{ Name=$Name; Alive=$false; Reason='pid-reused'; Ledger=$ledger; Observation=$obs } }
|
|
396
|
-
if (-not [string]::Equals($obs.image_path, [string]$ledger.image_path, [System.StringComparison]::OrdinalIgnoreCase)) { return [pscustomobject]@{ Name=$Name; Alive=$false; Reason='image-path-mismatch'; Ledger=$ledger; Observation=$obs } }
|
|
397
|
-
if ($obs.image_sha256 -ne [string]$ledger.image_sha256) { return [pscustomobject]@{ Name=$Name; Alive=$false; Reason='image-hash-mismatch'; Ledger=$ledger; Observation=$obs } }
|
|
398
|
-
[pscustomobject]@{ Name=$Name; Alive=$true; Reason='verified'; Ledger=$ledger; Observation=$obs }
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
function Reconcile-PidReusedLedger([string]$Name, $Ownership) {
|
|
402
|
-
if ($null -eq $Ownership -or [string]$Ownership.Reason -ne 'pid-reused') { Fail "$Name PID-reuse reconcile requires pid-reused ownership state" }
|
|
403
|
-
if ($null -eq $Ownership.Ledger -or $null -eq $Ownership.Observation) { Fail "$Name PID-reuse reconcile requires stale ledger and current process observation" }
|
|
404
|
-
$ledger = $Ownership.Ledger
|
|
405
|
-
$observed = $Ownership.Observation
|
|
406
|
-
if ([int]$ledger.pid -ne [int]$observed.pid) { Fail "$Name PID-reuse reconcile PID mismatch" }
|
|
407
|
-
if ([string]$ledger.creation_time_utc -eq [string]$observed.creation_time_utc) { Fail "$Name PID-reuse reconcile requires creation-time mismatch" }
|
|
408
|
-
$ledgerPath = Ledger-Path $Name
|
|
409
|
-
if (-not (Test-Path -LiteralPath $ledgerPath -PathType Leaf)) { Fail "$Name PID-reuse stale ledger disappeared before archive" }
|
|
410
|
-
$ledgerSha256 = (Get-FileHash -Algorithm SHA256 -LiteralPath $ledgerPath).Hash.ToLowerInvariant()
|
|
411
|
-
if ($ledgerSha256 -cnotmatch '^[0-9a-f]{64}$') { Fail "$Name PID-reuse stale ledger SHA-256 invalid" }
|
|
412
|
-
$evidenceDir = Join-Path $RuntimeDir 'ownership-evidence'
|
|
413
|
-
Ensure-Directory $evidenceDir
|
|
414
|
-
$archivePath = Join-Path $evidenceDir ("{0}.stale.{1}.json" -f $Name.ToLowerInvariant(), $ledgerSha256)
|
|
415
|
-
if (Test-Path -LiteralPath $archivePath -PathType Leaf) {
|
|
416
|
-
$existingSha256 = (Get-FileHash -Algorithm SHA256 -LiteralPath $archivePath).Hash.ToLowerInvariant()
|
|
417
|
-
if ($existingSha256 -ne $ledgerSha256) { Fail "$Name PID-reuse evidence archive hash conflict" }
|
|
418
|
-
} else {
|
|
419
|
-
Copy-Item -LiteralPath $ledgerPath -Destination $archivePath
|
|
420
|
-
}
|
|
421
|
-
$archiveSha256 = (Get-FileHash -Algorithm SHA256 -LiteralPath $archivePath).Hash.ToLowerInvariant()
|
|
422
|
-
if ($archiveSha256 -ne $ledgerSha256) { Fail "$Name PID-reuse evidence archive verification failed" }
|
|
423
|
-
$receipt = [ordered]@{
|
|
424
|
-
schema = 'deadbyte.ownership-recovery.v1'
|
|
425
|
-
component = $Name.ToLowerInvariant()
|
|
426
|
-
reason = 'pid-reused'
|
|
427
|
-
stale_ledger_sha256 = $ledgerSha256
|
|
428
|
-
stale_ledger_archive = $archivePath
|
|
429
|
-
stale_pid = [int]$ledger.pid
|
|
430
|
-
stale_creation_time_utc = [string]$ledger.creation_time_utc
|
|
431
|
-
stale_image_path = [string]$ledger.image_path
|
|
432
|
-
stale_image_sha256 = [string]$ledger.image_sha256
|
|
433
|
-
observed_pid = [int]$observed.pid
|
|
434
|
-
observed_creation_time_utc = [string]$observed.creation_time_utc
|
|
435
|
-
observed_image_path = [string]$observed.image_path
|
|
436
|
-
observed_image_sha256 = [string]$observed.image_sha256
|
|
437
|
-
reconciled_at_utc = [DateTime]::UtcNow.ToString('o')
|
|
438
|
-
action = 'archive-stale-ledger-remove-metadata-no-process-kill'
|
|
439
|
-
}
|
|
440
|
-
$receiptPath = Join-Path $evidenceDir ("{0}.recovery.{1}.json" -f $Name.ToLowerInvariant(), $ledgerSha256)
|
|
441
|
-
$receiptTemp = $receiptPath + '.tmp'
|
|
442
|
-
$receiptJson = $receipt | ConvertTo-Json -Depth 5
|
|
443
|
-
[System.IO.File]::WriteAllText($receiptTemp, $receiptJson, (New-Object System.Text.UTF8Encoding($false)))
|
|
444
|
-
Move-Item -LiteralPath $receiptTemp -Destination $receiptPath -Force
|
|
445
|
-
Remove-Item -LiteralPath (Ledger-Path $Name) -Force
|
|
446
|
-
if (Test-Path -LiteralPath (Ledger-Path $Name) -PathType Leaf) { Fail "$Name PID-reuse stale ownership metadata removal failed" }
|
|
447
|
-
return [pscustomobject]@{ Name=$Name; Reason='pid-reused-reconciled'; LedgerSha256=$ledgerSha256; ArchivePath=$archivePath; ReceiptPath=$receiptPath }
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
function Write-Ledger([string]$Name, $Observation, [string]$Nonce, $Spec = $null) {
|
|
451
|
-
Ensure-Directory $RuntimeDir
|
|
452
|
-
$body = [ordered]@{
|
|
453
|
-
schema = $ControllerSchema
|
|
454
|
-
component = $Name.ToLowerInvariant()
|
|
455
|
-
instance_nonce = $Nonce
|
|
456
|
-
pid = [int]$Observation.pid
|
|
457
|
-
creation_time_utc = [string]$Observation.creation_time_utc
|
|
458
|
-
image_path = [string]$Observation.image_path
|
|
459
|
-
image_sha256 = [string]$Observation.image_sha256
|
|
460
|
-
}
|
|
461
|
-
if ($Name -in @('Bridge','Supervisor')) {
|
|
462
|
-
if (-not $Spec) { Fail "$Name ledger requires launch specification" }
|
|
463
|
-
$body['script_path'] = [string]$Spec.ScriptPath
|
|
464
|
-
$body['script_sha256'] = [string]$Spec.ScriptSha256
|
|
465
|
-
}
|
|
466
|
-
if ($Spec -and @($Spec.Dependencies).Count -gt 0) {
|
|
467
|
-
$body['dependencies'] = @($Spec.Dependencies | ForEach-Object { [ordered]@{ name=[string]$_.Name; path=[string]$_.Path; sha256=[string]$_.Sha256 } })
|
|
468
|
-
}
|
|
469
|
-
$tmp = (Ledger-Path $Name) + '.tmp'
|
|
470
|
-
$body | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath $tmp -Encoding UTF8
|
|
471
|
-
Move-Item -LiteralPath $tmp -Destination (Ledger-Path $Name) -Force
|
|
472
|
-
}
|
|
473
|
-
|
|
474
|
-
function Start-OwnedComponent([string]$Name, $Spec, $Cfg = $null) {
|
|
475
|
-
$current = Test-OwnedInstance $Name
|
|
476
|
-
if ($current.Alive) { return $current }
|
|
477
|
-
if ($current.Reason -eq 'pid-reused') {
|
|
478
|
-
Reconcile-PidReusedLedger $Name $current | Out-Null
|
|
479
|
-
$current = Test-OwnedInstance $Name
|
|
480
|
-
if ($current.Reason -ne 'no-ledger') { Fail "$Name PID-reuse reconcile did not clear stale ownership metadata" }
|
|
481
|
-
}
|
|
482
|
-
if ($current.Reason -in @('process-unobservable','image-path-mismatch','image-hash-mismatch','script-hash-mismatch','dependency-hash-mismatch','corrupt-ledger','ledger-schema')) {
|
|
483
|
-
Fail "$Name ownership conflict: $($current.Reason); refusing to attach or kill an unverified process"
|
|
484
|
-
}
|
|
485
|
-
Remove-Item -LiteralPath (Ledger-Path $Name) -Force -ErrorAction SilentlyContinue
|
|
486
|
-
if ($Name -eq 'Cloud') { Remove-Item -LiteralPath (Join-Path $RuntimeDir 'current-origin.txt') -Force -ErrorAction SilentlyContinue }
|
|
487
|
-
Ensure-Directory $LogDir
|
|
488
|
-
$stdout = Log-Path $Name 'stdout'
|
|
489
|
-
$stderr = Log-Path $Name 'stderr'
|
|
490
|
-
Remove-Item -LiteralPath $stdout,$stderr -Force -ErrorAction SilentlyContinue
|
|
491
|
-
$nonceBytes = New-Object byte[] 32
|
|
492
|
-
[System.Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($nonceBytes)
|
|
493
|
-
$nonce = -join ($nonceBytes | ForEach-Object { $_.ToString('x2') })
|
|
494
|
-
$componentExecutableHash = Get-ExecutableHash ([string]$Spec.FilePath)
|
|
495
|
-
if ($componentExecutableHash -ne [string]$Spec.FileSha256) { Fail "$Name executable hash mismatch before start: expected $($Spec.FileSha256), found $componentExecutableHash" }
|
|
496
|
-
Assert-ComponentDependencies $Name $Spec 'before start'
|
|
497
|
-
if ($Name -in @('Bridge','Supervisor')) {
|
|
498
|
-
$scriptHash = Get-ExecutableHash ([string]$Spec.ScriptPath)
|
|
499
|
-
if ($scriptHash -ne [string]$Spec.ScriptSha256) { Fail "$Name script hash mismatch before start: expected $($Spec.ScriptSha256), found $scriptHash" }
|
|
500
|
-
}
|
|
501
|
-
$args = [string]$Spec.Arguments
|
|
502
|
-
$start = @{ FilePath=[string]$Spec.FilePath; WorkingDirectory=[string]$Spec.WorkingDirectory; PassThru=$true; WindowStyle='Hidden'; RedirectStandardOutput=$stdout; RedirectStandardError=$stderr }
|
|
503
|
-
if (-not [string]::IsNullOrWhiteSpace($args)) { $start.ArgumentList = $args }
|
|
504
|
-
$hadCoreRootEnv = Test-Path Env:DEADBYTE_CORE_ROOT
|
|
505
|
-
$previousCoreRootEnv = $env:DEADBYTE_CORE_ROOT
|
|
506
|
-
$hadHostPolicyEnv = Test-Path Env:DEADBYTE_PRIVILEGED_POLICY
|
|
507
|
-
$previousHostPolicyEnv = $env:DEADBYTE_PRIVILEGED_POLICY
|
|
508
|
-
$hadWorkspaceRootEnv = Test-Path Env:DEADBYTE_WORKSPACE_ROOT
|
|
509
|
-
$previousWorkspaceRootEnv = $env:DEADBYTE_WORKSPACE_ROOT
|
|
510
|
-
$hadAuditRuntimeEnv = Test-Path Env:DEADBYTE_AUDIT_RUNTIME
|
|
511
|
-
$previousAuditRuntimeEnv = $env:DEADBYTE_AUDIT_RUNTIME
|
|
512
|
-
$hadMachineRuntimeEnv = Test-Path Env:DEADBYTE_MACHINE_RUNTIME
|
|
513
|
-
$previousMachineRuntimeEnv = $env:DEADBYTE_MACHINE_RUNTIME
|
|
514
|
-
$hadAgentRuntimeEnv = Test-Path Env:DEADBYTE_AGENT_RUNTIME
|
|
515
|
-
$previousAgentRuntimeEnv = $env:DEADBYTE_AGENT_RUNTIME
|
|
516
|
-
$hadProcessRuntimeEnv = Test-Path Env:DEADBYTE_PROCESS_RUNTIME
|
|
517
|
-
$previousProcessRuntimeEnv = $env:DEADBYTE_PROCESS_RUNTIME
|
|
518
|
-
$hadProcessPolicyEnv = Test-Path Env:DEADBYTE_PROCESS_POLICY
|
|
519
|
-
$previousProcessPolicyEnv = $env:DEADBYTE_PROCESS_POLICY
|
|
520
|
-
$hadCodingRuntimeEnv = Test-Path Env:DEADBYTE_CODING_RUNTIME
|
|
521
|
-
$previousCodingRuntimeEnv = $env:DEADBYTE_CODING_RUNTIME
|
|
522
|
-
$hadMachineFsRuntimeEnv = Test-Path Env:DEADBYTE_MACHINE_FS_RUNTIME
|
|
523
|
-
$previousMachineFsRuntimeEnv = $env:DEADBYTE_MACHINE_FS_RUNTIME
|
|
524
|
-
$hadObservationRuntimeEnv = Test-Path Env:DEADBYTE_OBSERVATION_RUNTIME
|
|
525
|
-
$previousObservationRuntimeEnv = $env:DEADBYTE_OBSERVATION_RUNTIME
|
|
526
|
-
$hadDesktopRuntimeEnv = Test-Path Env:DEADBYTE_DESKTOP_RUNTIME
|
|
527
|
-
$previousDesktopRuntimeEnv = $env:DEADBYTE_DESKTOP_RUNTIME
|
|
528
|
-
$hadDesktopPolicyEnv = Test-Path Env:DEADBYTE_DESKTOP_POLICY
|
|
529
|
-
$previousDesktopPolicyEnv = $env:DEADBYTE_DESKTOP_POLICY
|
|
530
|
-
$hadCodingPolicyEnv = Test-Path Env:DEADBYTE_CODING_POLICY
|
|
531
|
-
$previousCodingPolicyEnv = $env:DEADBYTE_CODING_POLICY
|
|
532
|
-
$hadSemanticConfigEnv = Test-Path Env:DEADBYTE_SEMANTIC_LSP_CONFIG
|
|
533
|
-
$previousSemanticLspConfigEnv = $env:DEADBYTE_SEMANTIC_LSP_CONFIG
|
|
534
|
-
$hadSemanticConfigShaEnv = Test-Path Env:DEADBYTE_SEMANTIC_LSP_CONFIG_SHA256
|
|
535
|
-
$previousSemanticLspConfigShaEnv = $env:DEADBYTE_SEMANTIC_LSP_CONFIG_SHA256
|
|
536
|
-
$hadAutonomousRuntimeEnv = Test-Path Env:DEADBYTE_AUTONOMOUS_RUNTIME
|
|
537
|
-
$previousAutonomousRuntimeEnv = $env:DEADBYTE_AUTONOMOUS_RUNTIME
|
|
538
|
-
$hadAutonomousPolicyEnv = Test-Path Env:DEADBYTE_AUTONOMOUS_POLICY
|
|
539
|
-
$previousAutonomousPolicyEnv = $env:DEADBYTE_AUTONOMOUS_POLICY
|
|
540
|
-
$hadSupervisorOnceEnv = Test-Path Env:DEADBYTE_SUPERVISOR_ONCE
|
|
541
|
-
$previousSupervisorOnceEnv = $env:DEADBYTE_SUPERVISOR_ONCE
|
|
542
|
-
$hadSupervisorMarkerEnv = Test-Path Env:DEADBYTE_SUPERVISOR_MARKER
|
|
543
|
-
$previousSupervisorMarkerEnv = $env:DEADBYTE_SUPERVISOR_MARKER
|
|
544
|
-
$hadAutonomousSupervisorModeEnv = Test-Path Env:DEADBYTE_AUTONOMOUS_SUPERVISOR_MODE
|
|
545
|
-
$previousAutonomousSupervisorModeEnv = $env:DEADBYTE_AUTONOMOUS_SUPERVISOR_MODE
|
|
546
|
-
$hadCapabilityProfileEnv = Test-Path Env:DEADBYTE_CAPABILITY_PROFILE
|
|
547
|
-
$previousCapabilityProfileEnv = $env:DEADBYTE_CAPABILITY_PROFILE
|
|
548
|
-
try {
|
|
549
|
-
if ($Name -eq 'Bridge' -and $Cfg) {
|
|
550
|
-
$env:DEADBYTE_CAPABILITY_PROFILE = 'compact'
|
|
551
|
-
$env:DEADBYTE_AUDIT_RUNTIME = '1'
|
|
552
|
-
$env:DEADBYTE_CORE_ROOT = [string]$Cfg.CoreRoot
|
|
553
|
-
$env:DEADBYTE_WORKSPACE_ROOT = [string]$Cfg.WorkspaceRoot
|
|
554
|
-
$env:DEADBYTE_MACHINE_RUNTIME = if ([bool]$Cfg.MachineRuntimeEnabled) { '1' } else { '0' }
|
|
192
|
+
if ($cfg.ContainsKey('CodingRuntimeEnabled')) {
|
|
193
|
+
if ($cfg.CodingRuntimeEnabled -isnot [bool]) { Fail 'CodingRuntimeEnabled must be boolean' }
|
|
194
|
+
} else {
|
|
195
|
+
$cfg['CodingRuntimeEnabled'] = $false
|
|
196
|
+
}
|
|
197
|
+
if ($cfg.ContainsKey('CodingPolicyFile') -and -not [string]::IsNullOrWhiteSpace([string]$cfg.CodingPolicyFile)) {
|
|
198
|
+
$codingPolicy = [System.IO.Path]::GetFullPath([string]$cfg.CodingPolicyFile)
|
|
199
|
+
if (-not (Test-Path -LiteralPath $codingPolicy -PathType Leaf)) { Fail "CodingPolicyFile missing: $codingPolicy" }
|
|
200
|
+
$cfg['CodingPolicyFile'] = $codingPolicy
|
|
201
|
+
} else {
|
|
202
|
+
$cfg['CodingPolicyFile'] = ''
|
|
203
|
+
}
|
|
204
|
+
if ([bool]$cfg.CodingRuntimeEnabled -and [string]::IsNullOrWhiteSpace([string]$cfg.CodingPolicyFile)) { Fail 'CodingRuntimeEnabled requires CodingPolicyFile' }
|
|
205
|
+
$semanticConfigFile = if ($cfg.ContainsKey('SemanticLspConfigFile')) { [string]$cfg.SemanticLspConfigFile } else { '' }
|
|
206
|
+
$semanticConfigSha = if ($cfg.ContainsKey('SemanticLspConfigSha256')) { ([string]$cfg.SemanticLspConfigSha256).ToLowerInvariant() } else { '' }
|
|
207
|
+
if ([string]::IsNullOrWhiteSpace($semanticConfigFile) -ne [string]::IsNullOrWhiteSpace($semanticConfigSha)) { Fail 'SemanticLspConfigFile and SemanticLspConfigSha256 must be supplied together' }
|
|
208
|
+
if (-not [string]::IsNullOrWhiteSpace($semanticConfigFile)) {
|
|
209
|
+
if (-not [bool]$cfg.CodingRuntimeEnabled) { Fail 'SemanticLspConfigFile requires CodingRuntimeEnabled' }
|
|
210
|
+
if ($semanticConfigSha -cnotmatch '^[0-9a-f]{64}$') { Fail 'SemanticLspConfigSha256 invalid' }
|
|
211
|
+
$semanticConfigFile = [System.IO.Path]::GetFullPath($semanticConfigFile)
|
|
212
|
+
if (-not (Test-Path -LiteralPath $semanticConfigFile -PathType Leaf)) { Fail "SemanticLspConfigFile missing: $semanticConfigFile" }
|
|
213
|
+
$semanticItem = Get-Item -LiteralPath $semanticConfigFile -Force
|
|
214
|
+
if (($semanticItem.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0) { Fail 'SemanticLspConfigFile must not be a reparse point' }
|
|
215
|
+
$semanticObserved = Get-ExecutableHash $semanticConfigFile
|
|
216
|
+
if ($semanticObserved -ne $semanticConfigSha) { Fail "semantic LSP config SHA-256 mismatch: expected $semanticConfigSha, found $semanticObserved" }
|
|
217
|
+
$cfg['SemanticLspConfigFile'] = $semanticConfigFile
|
|
218
|
+
$cfg['SemanticLspConfigSha256'] = $semanticConfigSha
|
|
219
|
+
} else {
|
|
220
|
+
$cfg['SemanticLspConfigFile'] = ''
|
|
221
|
+
$cfg['SemanticLspConfigSha256'] = ''
|
|
222
|
+
}
|
|
223
|
+
if ($cfg.ContainsKey('MachineFsRuntimeEnabled')) {
|
|
224
|
+
if ($cfg.MachineFsRuntimeEnabled -isnot [bool]) { Fail 'MachineFsRuntimeEnabled must be boolean' }
|
|
225
|
+
} else {
|
|
226
|
+
$cfg['MachineFsRuntimeEnabled'] = $false
|
|
227
|
+
}
|
|
228
|
+
if ([bool]$cfg.MachineFsRuntimeEnabled -and -not [bool]$cfg.CodingRuntimeEnabled) { Fail 'MachineFsRuntimeEnabled requires CodingRuntimeEnabled' }
|
|
229
|
+
if ($cfg.ContainsKey('ObservationRuntimeEnabled')) {
|
|
230
|
+
if ($cfg.ObservationRuntimeEnabled -isnot [bool]) { Fail 'ObservationRuntimeEnabled must be boolean' }
|
|
231
|
+
} else {
|
|
232
|
+
$cfg['ObservationRuntimeEnabled'] = $false
|
|
233
|
+
}
|
|
234
|
+
if ([bool]$cfg.ObservationRuntimeEnabled -and -not [bool]$cfg.MachineRuntimeEnabled) { Fail 'ObservationRuntimeEnabled requires MachineRuntimeEnabled' }
|
|
235
|
+
if ([bool]$cfg.ObservationRuntimeEnabled -and -not [bool]$cfg.CodingRuntimeEnabled) { Fail 'ObservationRuntimeEnabled requires CodingRuntimeEnabled' }
|
|
236
|
+
if ($cfg.ContainsKey('DesktopRuntimeEnabled')) {
|
|
237
|
+
if ($cfg.DesktopRuntimeEnabled -isnot [bool]) { Fail 'DesktopRuntimeEnabled must be boolean' }
|
|
238
|
+
} else {
|
|
239
|
+
$cfg['DesktopRuntimeEnabled'] = $false
|
|
240
|
+
}
|
|
241
|
+
if ($cfg.ContainsKey('DesktopPolicyFile') -and -not [string]::IsNullOrWhiteSpace([string]$cfg.DesktopPolicyFile)) {
|
|
242
|
+
$desktopPolicy = [System.IO.Path]::GetFullPath([string]$cfg.DesktopPolicyFile)
|
|
243
|
+
if (-not (Test-Path -LiteralPath $desktopPolicy -PathType Leaf)) { Fail "DesktopPolicyFile missing: $desktopPolicy" }
|
|
244
|
+
$cfg['DesktopPolicyFile'] = $desktopPolicy
|
|
245
|
+
} else {
|
|
246
|
+
$cfg['DesktopPolicyFile'] = ''
|
|
247
|
+
}
|
|
248
|
+
if ([bool]$cfg.DesktopRuntimeEnabled -and [string]::IsNullOrWhiteSpace([string]$cfg.DesktopPolicyFile)) { Fail 'DesktopRuntimeEnabled requires DesktopPolicyFile' }
|
|
249
|
+
if ($cfg.ContainsKey('AutonomousRuntimeEnabled')) {
|
|
250
|
+
if ($cfg.AutonomousRuntimeEnabled -isnot [bool]) { Fail 'AutonomousRuntimeEnabled must be boolean' }
|
|
251
|
+
} else {
|
|
252
|
+
$cfg['AutonomousRuntimeEnabled'] = $false
|
|
253
|
+
}
|
|
254
|
+
if ($cfg.ContainsKey('AutonomousPolicyFile') -and -not [string]::IsNullOrWhiteSpace([string]$cfg.AutonomousPolicyFile)) {
|
|
255
|
+
$autonomousPolicy = [System.IO.Path]::GetFullPath([string]$cfg.AutonomousPolicyFile)
|
|
256
|
+
if (-not (Test-Path -LiteralPath $autonomousPolicy -PathType Leaf)) { Fail "AutonomousPolicyFile missing: $autonomousPolicy" }
|
|
257
|
+
$cfg['AutonomousPolicyFile'] = $autonomousPolicy
|
|
258
|
+
} else {
|
|
259
|
+
$cfg['AutonomousPolicyFile'] = ''
|
|
260
|
+
}
|
|
261
|
+
if ([bool]$cfg.AutonomousRuntimeEnabled -and -not [bool]$cfg.CodingRuntimeEnabled) { Fail 'AutonomousRuntimeEnabled requires CodingRuntimeEnabled' }
|
|
262
|
+
if ([bool]$cfg.AutonomousRuntimeEnabled -and [string]::IsNullOrWhiteSpace([string]$cfg.AutonomousPolicyFile)) { Fail 'AutonomousRuntimeEnabled requires AutonomousPolicyFile' }
|
|
263
|
+
if ([bool]$cfg.AutonomousSupervisorEnabled -and -not [bool]$cfg.AutonomousRuntimeEnabled) { Fail 'AutonomousSupervisorEnabled requires AutonomousRuntimeEnabled' }
|
|
264
|
+
if ([int]$cfg.StartTimeoutSeconds -lt 1 -or [int]$cfg.StartTimeoutSeconds -gt 120) { Fail 'StartTimeoutSeconds out of range' }
|
|
265
|
+
if ([int]$cfg.ProbeTimeoutSeconds -lt 1 -or [int]$cfg.ProbeTimeoutSeconds -gt 15) { Fail 'ProbeTimeoutSeconds out of range' }
|
|
266
|
+
return $cfg
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function Assert-ControllerRootBound($Cfg) {
|
|
270
|
+
$expected = [System.IO.Path]::GetFullPath((Join-Path ([string]$Cfg.CoreRoot) 'controller')).TrimEnd('\')
|
|
271
|
+
$actual = [System.IO.Path]::GetFullPath($Root).TrimEnd('\')
|
|
272
|
+
if (-not [string]::Equals($actual, $expected, [System.StringComparison]::OrdinalIgnoreCase)) {
|
|
273
|
+
Fail "controller root mismatch: running '$actual', expected '$expected'; use canonical DEADBYTE-MCP.cmd"
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function Get-ExecutableHash([string]$Path) {
|
|
278
|
+
(Get-FileHash -LiteralPath $Path -Algorithm SHA256).Hash.ToLowerInvariant()
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function Normalize-ComponentDependencies([string]$Name, $Spec) {
|
|
282
|
+
if (-not $Spec.ContainsKey('Dependencies')) { $Spec['Dependencies'] = @(); return }
|
|
283
|
+
$dependencies = @($Spec.Dependencies)
|
|
284
|
+
if ($dependencies.Count -gt 16) { Fail "$Name dependency count exceeds 16" }
|
|
285
|
+
$normalized = @()
|
|
286
|
+
foreach ($dependency in $dependencies) {
|
|
287
|
+
$depPath = [System.IO.Path]::GetFullPath([string]$dependency.Path)
|
|
288
|
+
$depHash = ([string]$dependency.Sha256).ToLowerInvariant()
|
|
289
|
+
if ([string]::IsNullOrWhiteSpace($depPath) -or -not (Test-Path -LiteralPath $depPath -PathType Leaf)) { Fail "$Name dependency missing: $depPath" }
|
|
290
|
+
if ($depHash -cnotmatch '^[0-9a-f]{64}$') { Fail "$Name dependency SHA-256 invalid: $depPath" }
|
|
291
|
+
$item = Get-Item -LiteralPath $depPath -Force
|
|
292
|
+
if (($item.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0) { Fail "$Name dependency must not be a reparse point: $depPath" }
|
|
293
|
+
$actual = Get-ExecutableHash $depPath
|
|
294
|
+
if ($actual -ne $depHash) { Fail "$Name dependency hash mismatch in trusted config: $depPath" }
|
|
295
|
+
$depName = if ([string]::IsNullOrWhiteSpace([string]$dependency.Name)) { [System.IO.Path]::GetFileName($depPath) } else { [string]$dependency.Name }
|
|
296
|
+
$normalized += [ordered]@{ Name=$depName; Path=$depPath; Sha256=$depHash }
|
|
297
|
+
}
|
|
298
|
+
$Spec['Dependencies'] = $normalized
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function Assert-ComponentDependencies([string]$Name, $Spec, [string]$Phase) {
|
|
302
|
+
foreach ($dependency in @($Spec.Dependencies)) {
|
|
303
|
+
$depPath = [string]$dependency.Path
|
|
304
|
+
$expected = [string]$dependency.Sha256
|
|
305
|
+
if (-not (Test-Path -LiteralPath $depPath -PathType Leaf)) { Fail "$Name dependency missing ${Phase}: $depPath" }
|
|
306
|
+
$actual = Get-ExecutableHash $depPath
|
|
307
|
+
if ($actual -ne $expected) { Fail "$Name dependency hash mismatch ${Phase}: $depPath expected $expected found $actual" }
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function Get-LimitedProcessImagePath([int]$ProcessId) {
|
|
312
|
+
if (-not ('DeadbyteProcessQuery' -as [type])) {
|
|
313
|
+
$source = @'
|
|
314
|
+
using System;
|
|
315
|
+
using System.Text;
|
|
316
|
+
using System.Runtime.InteropServices;
|
|
317
|
+
public static class DeadbyteProcessQuery {
|
|
318
|
+
[DllImport("kernel32.dll", SetLastError=true)] static extern IntPtr OpenProcess(uint access, bool inheritHandle, int processId);
|
|
319
|
+
[DllImport("kernel32.dll", CharSet=CharSet.Unicode, SetLastError=true)] static extern bool QueryFullProcessImageName(IntPtr process, uint flags, StringBuilder path, ref int size);
|
|
320
|
+
[DllImport("kernel32.dll")] static extern bool CloseHandle(IntPtr handle);
|
|
321
|
+
public static string GetImagePath(int processId) { const uint PROCESS_QUERY_LIMITED_INFORMATION=0x1000; IntPtr h=OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION,false,processId); if(h==IntPtr.Zero)return null; try { var b=new StringBuilder(32768); int n=b.Capacity; return QueryFullProcessImageName(h,0,b,ref n)?b.ToString():null; } finally { CloseHandle(h); } }
|
|
322
|
+
}
|
|
323
|
+
'@
|
|
324
|
+
Add-Type -TypeDefinition $source -Language CSharp -ErrorAction Stop
|
|
325
|
+
}
|
|
326
|
+
try { $path=[DeadbyteProcessQuery]::GetImagePath($ProcessId); if ($null -eq $path) { return '' }; return [string]$path } catch { return '' }
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function Get-ProcessObservation([int]$ProcessId) {
|
|
330
|
+
try { $p = Get-Process -Id $ProcessId -ErrorAction Stop } catch { return $null }
|
|
331
|
+
$image = ''
|
|
332
|
+
try {
|
|
333
|
+
$cim = Get-CimInstance Win32_Process -Filter ("ProcessId={0}" -f $ProcessId) -ErrorAction Stop
|
|
334
|
+
if ($cim -and -not [string]::IsNullOrWhiteSpace([string]$cim.ExecutablePath)) {
|
|
335
|
+
$image = [string]$cim.ExecutablePath
|
|
336
|
+
}
|
|
337
|
+
} catch {}
|
|
338
|
+
if ([string]::IsNullOrWhiteSpace($image)) {
|
|
339
|
+
try { $image = [string]$p.Path } catch {}
|
|
340
|
+
}
|
|
341
|
+
if ([string]::IsNullOrWhiteSpace($image)) {
|
|
342
|
+
try { $image = Get-LimitedProcessImagePath $ProcessId } catch {}
|
|
343
|
+
}
|
|
344
|
+
if ([string]::IsNullOrWhiteSpace($image)) {
|
|
345
|
+
try { $image = [string]$p.MainModule.FileName } catch {}
|
|
346
|
+
}
|
|
347
|
+
if ([string]::IsNullOrWhiteSpace($image)) { return $null }
|
|
348
|
+
try { $image = [System.IO.Path]::GetFullPath($image) } catch { return $null }
|
|
349
|
+
if (-not (Test-Path -LiteralPath $image -PathType Leaf)) { return $null }
|
|
350
|
+
try { $imageHash = Get-ExecutableHash $image } catch { return $null }
|
|
351
|
+
[pscustomobject]@{
|
|
352
|
+
pid = [int]$p.Id
|
|
353
|
+
creation_time_utc = $p.StartTime.ToUniversalTime().ToString('o')
|
|
354
|
+
image_path = $image
|
|
355
|
+
image_sha256 = $imageHash
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function Read-Ledger([string]$Name) {
|
|
360
|
+
$path = Ledger-Path $Name
|
|
361
|
+
if (-not (Test-Path -LiteralPath $path -PathType Leaf)) { return $null }
|
|
362
|
+
try { return (Get-Content -LiteralPath $path -Raw -Encoding UTF8 | ConvertFrom-Json) } catch { return [pscustomobject]@{ corrupt = $true; path = $path } }
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function Test-OwnedInstance([string]$Name) {
|
|
366
|
+
$ledger = Read-Ledger $Name
|
|
367
|
+
if (-not $ledger) { return [pscustomobject]@{ Name=$Name; Alive=$false; Reason='no-ledger'; Ledger=$null; Observation=$null } }
|
|
368
|
+
if ($ledger.PSObject.Properties.Name -contains 'corrupt' -and $ledger.corrupt -eq $true) { return [pscustomobject]@{ Name=$Name; Alive=$false; Reason='corrupt-ledger'; Ledger=$ledger; Observation=$null } }
|
|
369
|
+
if ($ledger.schema -ne $ControllerSchema -or -not $ledger.instance_nonce) { return [pscustomobject]@{ Name=$Name; Alive=$false; Reason='ledger-schema'; Ledger=$ledger; Observation=$null } }
|
|
370
|
+
if ($Name -in @('Bridge','Supervisor')) {
|
|
371
|
+
if (-not ($ledger.PSObject.Properties.Name -contains 'script_path') -or -not ($ledger.PSObject.Properties.Name -contains 'script_sha256')) {
|
|
372
|
+
return [pscustomobject]@{ Name=$Name; Alive=$false; Reason='ledger-schema'; Ledger=$ledger; Observation=$null }
|
|
373
|
+
}
|
|
374
|
+
$scriptPath = [string]$ledger.script_path
|
|
375
|
+
$scriptHash = [string]$ledger.script_sha256
|
|
376
|
+
if ([string]::IsNullOrWhiteSpace($scriptPath) -or $scriptHash -cnotmatch '^[0-9a-f]{64}$' -or -not (Test-Path -LiteralPath $scriptPath -PathType Leaf)) {
|
|
377
|
+
return [pscustomobject]@{ Name=$Name; Alive=$false; Reason='script-hash-mismatch'; Ledger=$ledger; Observation=$null }
|
|
378
|
+
}
|
|
379
|
+
try { $observedScriptHash = Get-ExecutableHash $scriptPath } catch { $observedScriptHash = '' }
|
|
380
|
+
if ($observedScriptHash -ne $scriptHash) { return [pscustomobject]@{ Name=$Name; Alive=$false; Reason='script-hash-mismatch'; Ledger=$ledger; Observation=$null } }
|
|
381
|
+
}
|
|
382
|
+
if ($ledger.PSObject.Properties.Name -contains 'dependencies') {
|
|
383
|
+
foreach ($dependency in @($ledger.dependencies)) {
|
|
384
|
+
$depPath = [string]$dependency.path
|
|
385
|
+
$depHash = [string]$dependency.sha256
|
|
386
|
+
if ([string]::IsNullOrWhiteSpace($depPath) -or $depHash -cnotmatch '^[0-9a-f]{64}$' -or -not (Test-Path -LiteralPath $depPath -PathType Leaf)) {
|
|
387
|
+
return [pscustomobject]@{ Name=$Name; Alive=$false; Reason='dependency-hash-mismatch'; Ledger=$ledger; Observation=$null }
|
|
388
|
+
}
|
|
389
|
+
try { $observedDependencyHash = Get-ExecutableHash $depPath } catch { $observedDependencyHash = '' }
|
|
390
|
+
if ($observedDependencyHash -ne $depHash) { return [pscustomobject]@{ Name=$Name; Alive=$false; Reason='dependency-hash-mismatch'; Ledger=$ledger; Observation=$null } }
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
$obs = Get-ProcessObservation ([int]$ledger.pid)
|
|
394
|
+
if (-not $obs) { try { [void](Get-Process -Id ([int]$ledger.pid) -ErrorAction Stop); return [pscustomobject]@{ Name=$Name; Alive=$false; Reason='process-unobservable'; Ledger=$ledger; Observation=$null } } catch { return [pscustomobject]@{ Name=$Name; Alive=$false; Reason='process-missing'; Ledger=$ledger; Observation=$null } } }
|
|
395
|
+
if ($obs.creation_time_utc -ne [string]$ledger.creation_time_utc) { return [pscustomobject]@{ Name=$Name; Alive=$false; Reason='pid-reused'; Ledger=$ledger; Observation=$obs } }
|
|
396
|
+
if (-not [string]::Equals($obs.image_path, [string]$ledger.image_path, [System.StringComparison]::OrdinalIgnoreCase)) { return [pscustomobject]@{ Name=$Name; Alive=$false; Reason='image-path-mismatch'; Ledger=$ledger; Observation=$obs } }
|
|
397
|
+
if ($obs.image_sha256 -ne [string]$ledger.image_sha256) { return [pscustomobject]@{ Name=$Name; Alive=$false; Reason='image-hash-mismatch'; Ledger=$ledger; Observation=$obs } }
|
|
398
|
+
[pscustomobject]@{ Name=$Name; Alive=$true; Reason='verified'; Ledger=$ledger; Observation=$obs }
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function Reconcile-PidReusedLedger([string]$Name, $Ownership) {
|
|
402
|
+
if ($null -eq $Ownership -or [string]$Ownership.Reason -ne 'pid-reused') { Fail "$Name PID-reuse reconcile requires pid-reused ownership state" }
|
|
403
|
+
if ($null -eq $Ownership.Ledger -or $null -eq $Ownership.Observation) { Fail "$Name PID-reuse reconcile requires stale ledger and current process observation" }
|
|
404
|
+
$ledger = $Ownership.Ledger
|
|
405
|
+
$observed = $Ownership.Observation
|
|
406
|
+
if ([int]$ledger.pid -ne [int]$observed.pid) { Fail "$Name PID-reuse reconcile PID mismatch" }
|
|
407
|
+
if ([string]$ledger.creation_time_utc -eq [string]$observed.creation_time_utc) { Fail "$Name PID-reuse reconcile requires creation-time mismatch" }
|
|
408
|
+
$ledgerPath = Ledger-Path $Name
|
|
409
|
+
if (-not (Test-Path -LiteralPath $ledgerPath -PathType Leaf)) { Fail "$Name PID-reuse stale ledger disappeared before archive" }
|
|
410
|
+
$ledgerSha256 = (Get-FileHash -Algorithm SHA256 -LiteralPath $ledgerPath).Hash.ToLowerInvariant()
|
|
411
|
+
if ($ledgerSha256 -cnotmatch '^[0-9a-f]{64}$') { Fail "$Name PID-reuse stale ledger SHA-256 invalid" }
|
|
412
|
+
$evidenceDir = Join-Path $RuntimeDir 'ownership-evidence'
|
|
413
|
+
Ensure-Directory $evidenceDir
|
|
414
|
+
$archivePath = Join-Path $evidenceDir ("{0}.stale.{1}.json" -f $Name.ToLowerInvariant(), $ledgerSha256)
|
|
415
|
+
if (Test-Path -LiteralPath $archivePath -PathType Leaf) {
|
|
416
|
+
$existingSha256 = (Get-FileHash -Algorithm SHA256 -LiteralPath $archivePath).Hash.ToLowerInvariant()
|
|
417
|
+
if ($existingSha256 -ne $ledgerSha256) { Fail "$Name PID-reuse evidence archive hash conflict" }
|
|
418
|
+
} else {
|
|
419
|
+
Copy-Item -LiteralPath $ledgerPath -Destination $archivePath
|
|
420
|
+
}
|
|
421
|
+
$archiveSha256 = (Get-FileHash -Algorithm SHA256 -LiteralPath $archivePath).Hash.ToLowerInvariant()
|
|
422
|
+
if ($archiveSha256 -ne $ledgerSha256) { Fail "$Name PID-reuse evidence archive verification failed" }
|
|
423
|
+
$receipt = [ordered]@{
|
|
424
|
+
schema = 'deadbyte.ownership-recovery.v1'
|
|
425
|
+
component = $Name.ToLowerInvariant()
|
|
426
|
+
reason = 'pid-reused'
|
|
427
|
+
stale_ledger_sha256 = $ledgerSha256
|
|
428
|
+
stale_ledger_archive = $archivePath
|
|
429
|
+
stale_pid = [int]$ledger.pid
|
|
430
|
+
stale_creation_time_utc = [string]$ledger.creation_time_utc
|
|
431
|
+
stale_image_path = [string]$ledger.image_path
|
|
432
|
+
stale_image_sha256 = [string]$ledger.image_sha256
|
|
433
|
+
observed_pid = [int]$observed.pid
|
|
434
|
+
observed_creation_time_utc = [string]$observed.creation_time_utc
|
|
435
|
+
observed_image_path = [string]$observed.image_path
|
|
436
|
+
observed_image_sha256 = [string]$observed.image_sha256
|
|
437
|
+
reconciled_at_utc = [DateTime]::UtcNow.ToString('o')
|
|
438
|
+
action = 'archive-stale-ledger-remove-metadata-no-process-kill'
|
|
439
|
+
}
|
|
440
|
+
$receiptPath = Join-Path $evidenceDir ("{0}.recovery.{1}.json" -f $Name.ToLowerInvariant(), $ledgerSha256)
|
|
441
|
+
$receiptTemp = $receiptPath + '.tmp'
|
|
442
|
+
$receiptJson = $receipt | ConvertTo-Json -Depth 5
|
|
443
|
+
[System.IO.File]::WriteAllText($receiptTemp, $receiptJson, (New-Object System.Text.UTF8Encoding($false)))
|
|
444
|
+
Move-Item -LiteralPath $receiptTemp -Destination $receiptPath -Force
|
|
445
|
+
Remove-Item -LiteralPath (Ledger-Path $Name) -Force
|
|
446
|
+
if (Test-Path -LiteralPath (Ledger-Path $Name) -PathType Leaf) { Fail "$Name PID-reuse stale ownership metadata removal failed" }
|
|
447
|
+
return [pscustomobject]@{ Name=$Name; Reason='pid-reused-reconciled'; LedgerSha256=$ledgerSha256; ArchivePath=$archivePath; ReceiptPath=$receiptPath }
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function Write-Ledger([string]$Name, $Observation, [string]$Nonce, $Spec = $null) {
|
|
451
|
+
Ensure-Directory $RuntimeDir
|
|
452
|
+
$body = [ordered]@{
|
|
453
|
+
schema = $ControllerSchema
|
|
454
|
+
component = $Name.ToLowerInvariant()
|
|
455
|
+
instance_nonce = $Nonce
|
|
456
|
+
pid = [int]$Observation.pid
|
|
457
|
+
creation_time_utc = [string]$Observation.creation_time_utc
|
|
458
|
+
image_path = [string]$Observation.image_path
|
|
459
|
+
image_sha256 = [string]$Observation.image_sha256
|
|
460
|
+
}
|
|
461
|
+
if ($Name -in @('Bridge','Supervisor')) {
|
|
462
|
+
if (-not $Spec) { Fail "$Name ledger requires launch specification" }
|
|
463
|
+
$body['script_path'] = [string]$Spec.ScriptPath
|
|
464
|
+
$body['script_sha256'] = [string]$Spec.ScriptSha256
|
|
465
|
+
}
|
|
466
|
+
if ($Spec -and @($Spec.Dependencies).Count -gt 0) {
|
|
467
|
+
$body['dependencies'] = @($Spec.Dependencies | ForEach-Object { [ordered]@{ name=[string]$_.Name; path=[string]$_.Path; sha256=[string]$_.Sha256 } })
|
|
468
|
+
}
|
|
469
|
+
$tmp = (Ledger-Path $Name) + '.tmp'
|
|
470
|
+
$body | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath $tmp -Encoding UTF8
|
|
471
|
+
Move-Item -LiteralPath $tmp -Destination (Ledger-Path $Name) -Force
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function Start-OwnedComponent([string]$Name, $Spec, $Cfg = $null) {
|
|
475
|
+
$current = Test-OwnedInstance $Name
|
|
476
|
+
if ($current.Alive) { return $current }
|
|
477
|
+
if ($current.Reason -eq 'pid-reused') {
|
|
478
|
+
Reconcile-PidReusedLedger $Name $current | Out-Null
|
|
479
|
+
$current = Test-OwnedInstance $Name
|
|
480
|
+
if ($current.Reason -ne 'no-ledger') { Fail "$Name PID-reuse reconcile did not clear stale ownership metadata" }
|
|
481
|
+
}
|
|
482
|
+
if ($current.Reason -in @('process-unobservable','image-path-mismatch','image-hash-mismatch','script-hash-mismatch','dependency-hash-mismatch','corrupt-ledger','ledger-schema')) {
|
|
483
|
+
Fail "$Name ownership conflict: $($current.Reason); refusing to attach or kill an unverified process"
|
|
484
|
+
}
|
|
485
|
+
Remove-Item -LiteralPath (Ledger-Path $Name) -Force -ErrorAction SilentlyContinue
|
|
486
|
+
if ($Name -eq 'Cloud') { Remove-Item -LiteralPath (Join-Path $RuntimeDir 'current-origin.txt') -Force -ErrorAction SilentlyContinue }
|
|
487
|
+
Ensure-Directory $LogDir
|
|
488
|
+
$stdout = Log-Path $Name 'stdout'
|
|
489
|
+
$stderr = Log-Path $Name 'stderr'
|
|
490
|
+
Remove-Item -LiteralPath $stdout,$stderr -Force -ErrorAction SilentlyContinue
|
|
491
|
+
$nonceBytes = New-Object byte[] 32
|
|
492
|
+
[System.Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($nonceBytes)
|
|
493
|
+
$nonce = -join ($nonceBytes | ForEach-Object { $_.ToString('x2') })
|
|
494
|
+
$componentExecutableHash = Get-ExecutableHash ([string]$Spec.FilePath)
|
|
495
|
+
if ($componentExecutableHash -ne [string]$Spec.FileSha256) { Fail "$Name executable hash mismatch before start: expected $($Spec.FileSha256), found $componentExecutableHash" }
|
|
496
|
+
Assert-ComponentDependencies $Name $Spec 'before start'
|
|
497
|
+
if ($Name -in @('Bridge','Supervisor')) {
|
|
498
|
+
$scriptHash = Get-ExecutableHash ([string]$Spec.ScriptPath)
|
|
499
|
+
if ($scriptHash -ne [string]$Spec.ScriptSha256) { Fail "$Name script hash mismatch before start: expected $($Spec.ScriptSha256), found $scriptHash" }
|
|
500
|
+
}
|
|
501
|
+
$args = [string]$Spec.Arguments
|
|
502
|
+
$start = @{ FilePath=[string]$Spec.FilePath; WorkingDirectory=[string]$Spec.WorkingDirectory; PassThru=$true; WindowStyle='Hidden'; RedirectStandardOutput=$stdout; RedirectStandardError=$stderr }
|
|
503
|
+
if (-not [string]::IsNullOrWhiteSpace($args)) { $start.ArgumentList = $args }
|
|
504
|
+
$hadCoreRootEnv = Test-Path Env:DEADBYTE_CORE_ROOT
|
|
505
|
+
$previousCoreRootEnv = $env:DEADBYTE_CORE_ROOT
|
|
506
|
+
$hadHostPolicyEnv = Test-Path Env:DEADBYTE_PRIVILEGED_POLICY
|
|
507
|
+
$previousHostPolicyEnv = $env:DEADBYTE_PRIVILEGED_POLICY
|
|
508
|
+
$hadWorkspaceRootEnv = Test-Path Env:DEADBYTE_WORKSPACE_ROOT
|
|
509
|
+
$previousWorkspaceRootEnv = $env:DEADBYTE_WORKSPACE_ROOT
|
|
510
|
+
$hadAuditRuntimeEnv = Test-Path Env:DEADBYTE_AUDIT_RUNTIME
|
|
511
|
+
$previousAuditRuntimeEnv = $env:DEADBYTE_AUDIT_RUNTIME
|
|
512
|
+
$hadMachineRuntimeEnv = Test-Path Env:DEADBYTE_MACHINE_RUNTIME
|
|
513
|
+
$previousMachineRuntimeEnv = $env:DEADBYTE_MACHINE_RUNTIME
|
|
514
|
+
$hadAgentRuntimeEnv = Test-Path Env:DEADBYTE_AGENT_RUNTIME
|
|
515
|
+
$previousAgentRuntimeEnv = $env:DEADBYTE_AGENT_RUNTIME
|
|
516
|
+
$hadProcessRuntimeEnv = Test-Path Env:DEADBYTE_PROCESS_RUNTIME
|
|
517
|
+
$previousProcessRuntimeEnv = $env:DEADBYTE_PROCESS_RUNTIME
|
|
518
|
+
$hadProcessPolicyEnv = Test-Path Env:DEADBYTE_PROCESS_POLICY
|
|
519
|
+
$previousProcessPolicyEnv = $env:DEADBYTE_PROCESS_POLICY
|
|
520
|
+
$hadCodingRuntimeEnv = Test-Path Env:DEADBYTE_CODING_RUNTIME
|
|
521
|
+
$previousCodingRuntimeEnv = $env:DEADBYTE_CODING_RUNTIME
|
|
522
|
+
$hadMachineFsRuntimeEnv = Test-Path Env:DEADBYTE_MACHINE_FS_RUNTIME
|
|
523
|
+
$previousMachineFsRuntimeEnv = $env:DEADBYTE_MACHINE_FS_RUNTIME
|
|
524
|
+
$hadObservationRuntimeEnv = Test-Path Env:DEADBYTE_OBSERVATION_RUNTIME
|
|
525
|
+
$previousObservationRuntimeEnv = $env:DEADBYTE_OBSERVATION_RUNTIME
|
|
526
|
+
$hadDesktopRuntimeEnv = Test-Path Env:DEADBYTE_DESKTOP_RUNTIME
|
|
527
|
+
$previousDesktopRuntimeEnv = $env:DEADBYTE_DESKTOP_RUNTIME
|
|
528
|
+
$hadDesktopPolicyEnv = Test-Path Env:DEADBYTE_DESKTOP_POLICY
|
|
529
|
+
$previousDesktopPolicyEnv = $env:DEADBYTE_DESKTOP_POLICY
|
|
530
|
+
$hadCodingPolicyEnv = Test-Path Env:DEADBYTE_CODING_POLICY
|
|
531
|
+
$previousCodingPolicyEnv = $env:DEADBYTE_CODING_POLICY
|
|
532
|
+
$hadSemanticConfigEnv = Test-Path Env:DEADBYTE_SEMANTIC_LSP_CONFIG
|
|
533
|
+
$previousSemanticLspConfigEnv = $env:DEADBYTE_SEMANTIC_LSP_CONFIG
|
|
534
|
+
$hadSemanticConfigShaEnv = Test-Path Env:DEADBYTE_SEMANTIC_LSP_CONFIG_SHA256
|
|
535
|
+
$previousSemanticLspConfigShaEnv = $env:DEADBYTE_SEMANTIC_LSP_CONFIG_SHA256
|
|
536
|
+
$hadAutonomousRuntimeEnv = Test-Path Env:DEADBYTE_AUTONOMOUS_RUNTIME
|
|
537
|
+
$previousAutonomousRuntimeEnv = $env:DEADBYTE_AUTONOMOUS_RUNTIME
|
|
538
|
+
$hadAutonomousPolicyEnv = Test-Path Env:DEADBYTE_AUTONOMOUS_POLICY
|
|
539
|
+
$previousAutonomousPolicyEnv = $env:DEADBYTE_AUTONOMOUS_POLICY
|
|
540
|
+
$hadSupervisorOnceEnv = Test-Path Env:DEADBYTE_SUPERVISOR_ONCE
|
|
541
|
+
$previousSupervisorOnceEnv = $env:DEADBYTE_SUPERVISOR_ONCE
|
|
542
|
+
$hadSupervisorMarkerEnv = Test-Path Env:DEADBYTE_SUPERVISOR_MARKER
|
|
543
|
+
$previousSupervisorMarkerEnv = $env:DEADBYTE_SUPERVISOR_MARKER
|
|
544
|
+
$hadAutonomousSupervisorModeEnv = Test-Path Env:DEADBYTE_AUTONOMOUS_SUPERVISOR_MODE
|
|
545
|
+
$previousAutonomousSupervisorModeEnv = $env:DEADBYTE_AUTONOMOUS_SUPERVISOR_MODE
|
|
546
|
+
$hadCapabilityProfileEnv = Test-Path Env:DEADBYTE_CAPABILITY_PROFILE
|
|
547
|
+
$previousCapabilityProfileEnv = $env:DEADBYTE_CAPABILITY_PROFILE
|
|
548
|
+
try {
|
|
549
|
+
if ($Name -eq 'Bridge' -and $Cfg) {
|
|
550
|
+
$env:DEADBYTE_CAPABILITY_PROFILE = 'compact'
|
|
551
|
+
$env:DEADBYTE_AUDIT_RUNTIME = '1'
|
|
552
|
+
$env:DEADBYTE_CORE_ROOT = [string]$Cfg.CoreRoot
|
|
553
|
+
$env:DEADBYTE_WORKSPACE_ROOT = [string]$Cfg.WorkspaceRoot
|
|
554
|
+
$env:DEADBYTE_MACHINE_RUNTIME = if ([bool]$Cfg.MachineRuntimeEnabled) { '1' } else { '0' }
|
|
555
555
|
$env:DEADBYTE_AGENT_RUNTIME = if ([bool]$Cfg.AgentRuntimeEnabled) { '1' } else { '0' }
|
|
556
|
-
$env:DEADBYTE_PROCESS_RUNTIME = if ([bool]$Cfg.ProcessRuntimeEnabled) { '1' } else { '0' }
|
|
557
|
-
if (-not [string]::IsNullOrWhiteSpace([string]$Cfg.ProcessPolicyFile)) {
|
|
558
|
-
$env:DEADBYTE_PROCESS_POLICY = [string]$Cfg.ProcessPolicyFile
|
|
559
|
-
}
|
|
560
|
-
$env:DEADBYTE_CODING_RUNTIME = if ([bool]$Cfg.CodingRuntimeEnabled) { '1' } else { '0' }
|
|
561
|
-
$env:DEADBYTE_MACHINE_FS_RUNTIME = if ([bool]$Cfg.MachineFsRuntimeEnabled) { '1' } else { '0' }
|
|
562
|
-
$env:DEADBYTE_OBSERVATION_RUNTIME = if ([bool]$Cfg.ObservationRuntimeEnabled) { '1' } else { '0' }
|
|
563
|
-
$env:DEADBYTE_DESKTOP_RUNTIME = if ([bool]$Cfg.DesktopRuntimeEnabled) { '1' } else { '0' }
|
|
564
|
-
if (-not [string]::IsNullOrWhiteSpace([string]$Cfg.DesktopPolicyFile)) {
|
|
565
|
-
$env:DEADBYTE_DESKTOP_POLICY = [string]$Cfg.DesktopPolicyFile
|
|
566
|
-
} else {
|
|
567
|
-
Remove-Item Env:DEADBYTE_DESKTOP_POLICY -ErrorAction SilentlyContinue
|
|
568
|
-
}
|
|
569
|
-
if (-not [string]::IsNullOrWhiteSpace([string]$Cfg.CodingPolicyFile)) {
|
|
570
|
-
$env:DEADBYTE_CODING_POLICY = [string]$Cfg.CodingPolicyFile
|
|
571
|
-
}
|
|
572
|
-
if (-not [string]::IsNullOrWhiteSpace([string]$Cfg.SemanticLspConfigFile)) {
|
|
573
|
-
$env:DEADBYTE_SEMANTIC_LSP_CONFIG = [string]$Cfg.SemanticLspConfigFile
|
|
574
|
-
$env:DEADBYTE_SEMANTIC_LSP_CONFIG_SHA256 = [string]$Cfg.SemanticLspConfigSha256
|
|
575
|
-
} else {
|
|
576
|
-
Remove-Item Env:DEADBYTE_SEMANTIC_LSP_CONFIG -ErrorAction SilentlyContinue
|
|
577
|
-
Remove-Item Env:DEADBYTE_SEMANTIC_LSP_CONFIG_SHA256 -ErrorAction SilentlyContinue
|
|
578
|
-
}
|
|
579
|
-
$env:DEADBYTE_AUTONOMOUS_RUNTIME = if ([bool]$Cfg.AutonomousRuntimeEnabled) { '1' } else { '0' }
|
|
580
|
-
$env:DEADBYTE_AUTONOMOUS_SUPERVISOR_MODE = if ([bool]$Cfg.AutonomousSupervisorEnabled) { 'external' } else { 'in_process' }
|
|
581
|
-
if (-not [string]::IsNullOrWhiteSpace([string]$Cfg.AutonomousPolicyFile)) {
|
|
582
|
-
$env:DEADBYTE_AUTONOMOUS_POLICY = [string]$Cfg.AutonomousPolicyFile
|
|
583
|
-
}
|
|
584
|
-
if (-not [string]::IsNullOrWhiteSpace([string]$Cfg.HostPolicyFile)) {
|
|
585
|
-
$env:DEADBYTE_PRIVILEGED_POLICY = [string]$Cfg.HostPolicyFile
|
|
586
|
-
}
|
|
587
|
-
} elseif ($Name -eq 'Supervisor' -and $Cfg) {
|
|
588
|
-
Remove-Item Env:DEADBYTE_CAPABILITY_PROFILE -ErrorAction SilentlyContinue
|
|
589
|
-
Remove-Item Env:DEADBYTE_AUDIT_RUNTIME -ErrorAction SilentlyContinue
|
|
590
|
-
Remove-Item Env:DEADBYTE_PRIVILEGED_POLICY -ErrorAction SilentlyContinue
|
|
591
|
-
Remove-Item Env:DEADBYTE_MACHINE_RUNTIME -ErrorAction SilentlyContinue
|
|
592
|
-
Remove-Item Env:DEADBYTE_AGENT_RUNTIME -ErrorAction SilentlyContinue
|
|
593
|
-
Remove-Item Env:DEADBYTE_PROCESS_RUNTIME -ErrorAction SilentlyContinue
|
|
594
|
-
Remove-Item Env:DEADBYTE_PROCESS_POLICY -ErrorAction SilentlyContinue
|
|
595
|
-
Remove-Item Env:DEADBYTE_MACHINE_FS_RUNTIME -ErrorAction SilentlyContinue
|
|
596
|
-
Remove-Item Env:DEADBYTE_OBSERVATION_RUNTIME -ErrorAction SilentlyContinue
|
|
597
|
-
Remove-Item Env:DEADBYTE_DESKTOP_RUNTIME -ErrorAction SilentlyContinue
|
|
598
|
-
Remove-Item Env:DEADBYTE_DESKTOP_POLICY -ErrorAction SilentlyContinue
|
|
599
|
-
Remove-Item Env:DEADBYTE_SEMANTIC_LSP_CONFIG -ErrorAction SilentlyContinue
|
|
600
|
-
Remove-Item Env:DEADBYTE_SEMANTIC_LSP_CONFIG_SHA256 -ErrorAction SilentlyContinue
|
|
601
|
-
Remove-Item Env:DEADBYTE_AUTONOMOUS_SUPERVISOR_MODE -ErrorAction SilentlyContinue
|
|
602
|
-
$env:DEADBYTE_CODING_RUNTIME = '1'
|
|
603
|
-
$env:DEADBYTE_AUTONOMOUS_RUNTIME = '1'
|
|
604
|
-
$env:DEADBYTE_CODING_POLICY = [string]$Cfg.CodingPolicyFile
|
|
605
|
-
$env:DEADBYTE_AUTONOMOUS_POLICY = [string]$Cfg.AutonomousPolicyFile
|
|
606
|
-
$env:DEADBYTE_SUPERVISOR_ONCE = '0'
|
|
607
|
-
$env:DEADBYTE_SUPERVISOR_MARKER = Join-Path $RuntimeDir 'autonomous-supervisor-owner.json'
|
|
608
|
-
} else {
|
|
609
|
-
Remove-Item Env:DEADBYTE_CAPABILITY_PROFILE -ErrorAction SilentlyContinue
|
|
610
|
-
Remove-Item Env:DEADBYTE_AUDIT_RUNTIME -ErrorAction SilentlyContinue
|
|
611
|
-
}
|
|
612
|
-
$proc = Start-Process @start
|
|
613
|
-
} finally {
|
|
614
|
-
if ($hadCoreRootEnv) { $env:DEADBYTE_CORE_ROOT = $previousCoreRootEnv }
|
|
615
|
-
else { Remove-Item Env:DEADBYTE_CORE_ROOT -ErrorAction SilentlyContinue }
|
|
616
|
-
if ($hadHostPolicyEnv) { $env:DEADBYTE_PRIVILEGED_POLICY = $previousHostPolicyEnv }
|
|
617
|
-
else { Remove-Item Env:DEADBYTE_PRIVILEGED_POLICY -ErrorAction SilentlyContinue }
|
|
618
|
-
if ($hadWorkspaceRootEnv) { $env:DEADBYTE_WORKSPACE_ROOT = $previousWorkspaceRootEnv }
|
|
619
|
-
else { Remove-Item Env:DEADBYTE_WORKSPACE_ROOT -ErrorAction SilentlyContinue }
|
|
620
|
-
if ($hadAuditRuntimeEnv) { $env:DEADBYTE_AUDIT_RUNTIME = $previousAuditRuntimeEnv }
|
|
621
|
-
else { Remove-Item Env:DEADBYTE_AUDIT_RUNTIME -ErrorAction SilentlyContinue }
|
|
622
|
-
if ($hadMachineRuntimeEnv) { $env:DEADBYTE_MACHINE_RUNTIME = $previousMachineRuntimeEnv }
|
|
623
|
-
else { Remove-Item Env:DEADBYTE_MACHINE_RUNTIME -ErrorAction SilentlyContinue }
|
|
624
|
-
if ($hadAgentRuntimeEnv) { $env:DEADBYTE_AGENT_RUNTIME = $previousAgentRuntimeEnv }
|
|
625
|
-
else { Remove-Item Env:DEADBYTE_AGENT_RUNTIME -ErrorAction SilentlyContinue }
|
|
626
|
-
if ($hadProcessRuntimeEnv) { $env:DEADBYTE_PROCESS_RUNTIME = $previousProcessRuntimeEnv }
|
|
627
|
-
else { Remove-Item Env:DEADBYTE_PROCESS_RUNTIME -ErrorAction SilentlyContinue }
|
|
628
|
-
if ($hadProcessPolicyEnv) { $env:DEADBYTE_PROCESS_POLICY = $previousProcessPolicyEnv }
|
|
629
|
-
else { Remove-Item Env:DEADBYTE_PROCESS_POLICY -ErrorAction SilentlyContinue }
|
|
630
|
-
if ($hadCodingRuntimeEnv) { $env:DEADBYTE_CODING_RUNTIME = $previousCodingRuntimeEnv }
|
|
631
|
-
else { Remove-Item Env:DEADBYTE_CODING_RUNTIME -ErrorAction SilentlyContinue }
|
|
632
|
-
if ($hadMachineFsRuntimeEnv) { $env:DEADBYTE_MACHINE_FS_RUNTIME = $previousMachineFsRuntimeEnv }
|
|
633
|
-
else { Remove-Item Env:DEADBYTE_MACHINE_FS_RUNTIME -ErrorAction SilentlyContinue }
|
|
634
|
-
if ($hadObservationRuntimeEnv) { $env:DEADBYTE_OBSERVATION_RUNTIME = $previousObservationRuntimeEnv }
|
|
635
|
-
else { Remove-Item Env:DEADBYTE_OBSERVATION_RUNTIME -ErrorAction SilentlyContinue }
|
|
636
|
-
if ($hadDesktopRuntimeEnv) { $env:DEADBYTE_DESKTOP_RUNTIME = $previousDesktopRuntimeEnv }
|
|
637
|
-
else { Remove-Item Env:DEADBYTE_DESKTOP_RUNTIME -ErrorAction SilentlyContinue }
|
|
638
|
-
if ($hadDesktopPolicyEnv) { $env:DEADBYTE_DESKTOP_POLICY = $previousDesktopPolicyEnv }
|
|
639
|
-
else { Remove-Item Env:DEADBYTE_DESKTOP_POLICY -ErrorAction SilentlyContinue }
|
|
640
|
-
if ($hadCodingPolicyEnv) { $env:DEADBYTE_CODING_POLICY = $previousCodingPolicyEnv }
|
|
641
|
-
else { Remove-Item Env:DEADBYTE_CODING_POLICY -ErrorAction SilentlyContinue }
|
|
642
|
-
if ($hadSemanticConfigEnv) { $env:DEADBYTE_SEMANTIC_LSP_CONFIG = $previousSemanticLspConfigEnv }
|
|
643
|
-
else { Remove-Item Env:DEADBYTE_SEMANTIC_LSP_CONFIG -ErrorAction SilentlyContinue }
|
|
644
|
-
if ($hadSemanticConfigShaEnv) { $env:DEADBYTE_SEMANTIC_LSP_CONFIG_SHA256 = $previousSemanticLspConfigShaEnv }
|
|
645
|
-
else { Remove-Item Env:DEADBYTE_SEMANTIC_LSP_CONFIG_SHA256 -ErrorAction SilentlyContinue }
|
|
646
|
-
if ($hadAutonomousRuntimeEnv) { $env:DEADBYTE_AUTONOMOUS_RUNTIME = $previousAutonomousRuntimeEnv }
|
|
647
|
-
else { Remove-Item Env:DEADBYTE_AUTONOMOUS_RUNTIME -ErrorAction SilentlyContinue }
|
|
648
|
-
if ($hadAutonomousPolicyEnv) { $env:DEADBYTE_AUTONOMOUS_POLICY = $previousAutonomousPolicyEnv }
|
|
649
|
-
else { Remove-Item Env:DEADBYTE_AUTONOMOUS_POLICY -ErrorAction SilentlyContinue }
|
|
650
|
-
if ($hadSupervisorOnceEnv) { $env:DEADBYTE_SUPERVISOR_ONCE = $previousSupervisorOnceEnv }
|
|
651
|
-
else { Remove-Item Env:DEADBYTE_SUPERVISOR_ONCE -ErrorAction SilentlyContinue }
|
|
652
|
-
if ($hadSupervisorMarkerEnv) { $env:DEADBYTE_SUPERVISOR_MARKER = $previousSupervisorMarkerEnv }
|
|
653
|
-
else { Remove-Item Env:DEADBYTE_SUPERVISOR_MARKER -ErrorAction SilentlyContinue }
|
|
654
|
-
if ($hadAutonomousSupervisorModeEnv) { $env:DEADBYTE_AUTONOMOUS_SUPERVISOR_MODE = $previousAutonomousSupervisorModeEnv }
|
|
655
|
-
else { Remove-Item Env:DEADBYTE_AUTONOMOUS_SUPERVISOR_MODE -ErrorAction SilentlyContinue }
|
|
656
|
-
if ($hadCapabilityProfileEnv) { $env:DEADBYTE_CAPABILITY_PROFILE = $previousCapabilityProfileEnv }
|
|
657
|
-
else { Remove-Item Env:DEADBYTE_CAPABILITY_PROFILE -ErrorAction SilentlyContinue }
|
|
658
|
-
}
|
|
659
|
-
Start-Sleep -Milliseconds 150
|
|
660
|
-
$obs = Get-ProcessObservation ([int]$proc.Id)
|
|
661
|
-
if (-not $obs) { Fail "$Name exited before identity capture; see $stderr" }
|
|
662
|
-
$expected = [System.IO.Path]::GetFullPath([string]$Spec.FilePath)
|
|
663
|
-
if (-not [string]::Equals($obs.image_path, $expected, [System.StringComparison]::OrdinalIgnoreCase)) {
|
|
664
|
-
try { Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue } catch {}
|
|
665
|
-
Fail "$Name spawned a wrapper instead of the configured long-lived executable; observed '$($obs.image_path)'"
|
|
666
|
-
}
|
|
667
|
-
if ($obs.image_sha256 -ne (Get-ExecutableHash $expected)) {
|
|
668
|
-
try { Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue } catch {}
|
|
669
|
-
Fail "$Name executable changed during start"
|
|
670
|
-
}
|
|
671
|
-
Write-Ledger $Name $obs $nonce $Spec
|
|
672
|
-
return Test-OwnedInstance $Name
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
function Stop-OwnedComponent([string]$Name) {
|
|
676
|
-
$owned = Test-OwnedInstance $Name
|
|
677
|
-
if (-not $owned.Ledger) { return }
|
|
678
|
-
if (-not $owned.Alive) {
|
|
679
|
-
if ($owned.Reason -eq 'process-missing') { Remove-Item -LiteralPath (Ledger-Path $Name) -Force -ErrorAction SilentlyContinue; return }
|
|
680
|
-
if ($owned.Reason -eq 'pid-reused') { Reconcile-PidReusedLedger $Name $owned | Out-Null; return }
|
|
681
|
-
Fail "$Name is not provably owned ($($owned.Reason)); refusing Stop-Process"
|
|
682
|
-
}
|
|
683
|
-
Stop-Process -Id ([int]$owned.Ledger.pid) -Force -ErrorAction Stop
|
|
684
|
-
try { Wait-Process -Id ([int]$owned.Ledger.pid) -Timeout 5 -ErrorAction SilentlyContinue } catch {}
|
|
685
|
-
Remove-Item -LiteralPath (Ledger-Path $Name) -Force -ErrorAction SilentlyContinue
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
function Get-LocalHealth($Cfg) {
|
|
689
|
-
$response = $null
|
|
690
|
-
try {
|
|
691
|
-
$request = [System.Net.HttpWebRequest]::Create([string]$Cfg.LocalReadyUri)
|
|
692
|
-
$request.Timeout = [int]$Cfg.ProbeTimeoutSeconds * 1000
|
|
693
|
-
$request.ReadWriteTimeout = [int]$Cfg.ProbeTimeoutSeconds * 1000
|
|
694
|
-
$response = $request.GetResponse()
|
|
695
|
-
} catch [System.Net.WebException] {
|
|
696
|
-
$response = $_.Exception.Response
|
|
697
|
-
if ($null -eq $response) { return [pscustomobject]@{ State='UNREACHABLE'; StatusCode=0; Diagnostic='readiness endpoint unreachable' } }
|
|
698
|
-
} catch { return [pscustomobject]@{ State='UNREACHABLE'; StatusCode=0; Diagnostic='readiness probe failed' } }
|
|
699
|
-
try {
|
|
700
|
-
$status = [int]$response.StatusCode
|
|
701
|
-
$reader = New-Object System.IO.StreamReader($response.GetResponseStream())
|
|
702
|
-
try { $body = $reader.ReadToEnd() } finally { $reader.Dispose() }
|
|
703
|
-
try { $parsed = $body | ConvertFrom-Json } catch { return [pscustomobject]@{ State='INVALID'; StatusCode=$status; Diagnostic='readiness response JSON invalid' } }
|
|
704
|
-
if ($parsed.schema -ne 'deadbyte.bridge-health.v1' -or [string]$parsed.state -notin @('STARTING','READY','DEGRADED','RECONCILING','STOPPING')) {
|
|
705
|
-
return [pscustomobject]@{ State='INVALID'; StatusCode=$status; Diagnostic='readiness response schema invalid' }
|
|
706
|
-
}
|
|
707
|
-
return [pscustomobject]@{ State=[string]$parsed.state; StatusCode=$status; Diagnostic=[string]$parsed.diagnostic }
|
|
708
|
-
} finally { if ($null -ne $response) { $response.Dispose() } }
|
|
709
|
-
}
|
|
710
|
-
function Test-LocalReady($Cfg) {
|
|
711
|
-
$health = Get-LocalHealth $Cfg
|
|
712
|
-
return ($health.State -eq 'READY' -and $health.StatusCode -ge 200 -and $health.StatusCode -lt 300)
|
|
713
|
-
}
|
|
714
|
-
function Get-LocalReadyListenerOwners($Cfg) {
|
|
715
|
-
try { $uri = [Uri]([string]$Cfg.LocalReadyUri) } catch { Fail 'LocalReadyUri is invalid' }
|
|
716
|
-
if (-not $uri.IsLoopback) { Fail 'LocalReadyUri must be loopback for owned readiness attribution' }
|
|
717
|
-
try {
|
|
718
|
-
$listeners = @(Get-NetTCPConnection -State Listen -ErrorAction Stop | Where-Object {
|
|
719
|
-
[int]$_.LocalPort -eq [int]$uri.Port -and (
|
|
720
|
-
[string]$_.LocalAddress -eq [string]$uri.Host -or
|
|
721
|
-
[string]$_.LocalAddress -eq '0.0.0.0' -or
|
|
722
|
-
[string]$_.LocalAddress -eq '::'
|
|
723
|
-
)
|
|
724
|
-
})
|
|
725
|
-
} catch { Fail "cannot inspect listener ownership for local readiness endpoint: $($_.Exception.Message)" }
|
|
726
|
-
return @($listeners | ForEach-Object { [int]$_.OwningProcess } | Sort-Object -Unique)
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
function Assert-LocalReadyPortAvailable($Cfg) {
|
|
730
|
-
$owners = @(Get-LocalReadyListenerOwners $Cfg)
|
|
731
|
-
if ($owners.Count -gt 0) {
|
|
732
|
-
Fail ("local readiness endpoint already has listener pid(s)={0}; refusing to start an unverified Bridge" -f ($owners -join ','))
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
function Test-LocalReadyOwned($Cfg, $Bridge) {
|
|
737
|
-
if (-not $Bridge -or -not $Bridge.Alive) { return $false }
|
|
738
|
-
$owners = @(Get-LocalReadyListenerOwners $Cfg)
|
|
739
|
-
if ($owners.Count -ne 1 -or $owners[0] -ne [int]$Bridge.Ledger.pid) { return $false }
|
|
740
|
-
return (Test-LocalReady $Cfg)
|
|
741
|
-
}
|
|
742
|
-
function Get-LocalHealthOwned($Cfg, $Bridge) {
|
|
743
|
-
if (-not $Bridge -or -not $Bridge.Alive) { return [pscustomobject]@{ State='UNREACHABLE'; StatusCode=0; Diagnostic='bridge not alive' } }
|
|
744
|
-
$owners = @(Get-LocalReadyListenerOwners $Cfg)
|
|
745
|
-
if ($owners.Count -ne 1 -or $owners[0] -ne [int]$Bridge.Ledger.pid) { return [pscustomobject]@{ State='UNATTRIBUTED'; StatusCode=0; Diagnostic='readiness listener ownership mismatch' } }
|
|
746
|
-
return (Get-LocalHealth $Cfg)
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
function Get-ObservedState($Cfg) {
|
|
750
|
-
$bridge = Test-OwnedInstance 'Bridge'
|
|
751
|
-
$cloud = Test-OwnedInstance 'Cloud'
|
|
752
|
-
$core = Get-DownstreamCoreObservation $Cfg $bridge
|
|
753
|
-
$health = if ($bridge.Alive -and $core.alive) { try { Get-LocalHealthOwned $Cfg $bridge } catch { [pscustomobject]@{ State='UNREACHABLE'; StatusCode=0; Diagnostic='readiness attribution failed' } } } else { [pscustomobject]@{ State='UNREACHABLE'; StatusCode=0; Diagnostic='core not alive' } }
|
|
754
|
-
$ready = $health.State -eq 'READY'
|
|
755
|
-
$state = if (-not $bridge.Alive -and -not $cloud.Alive) { 'OFFLINE' }
|
|
756
|
-
elseif (-not $bridge.Alive -and $cloud.Alive) { 'PARKED' }
|
|
757
|
-
elseif ($bridge.Alive -and $cloud.Alive -and $ready) { 'ONLINE' }
|
|
758
|
-
elseif ($bridge.Alive -and $cloud.Alive -and $core.alive -and $health.State -eq 'DEGRADED') { 'DEGRADED' }
|
|
759
|
-
elseif ($bridge.Alive -and $core.reason -eq 'startup-zero-child') { 'STARTING' }
|
|
760
|
-
else { 'FAILED' }
|
|
761
|
-
[pscustomobject]@{ State=$state; Bridge=$bridge; Core=$core; Cloud=$cloud; LocalReady=$ready; LocalHealth=$health }
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
function Wait-LocalOperational($Cfg) {
|
|
765
|
-
$deadline = [DateTime]::UtcNow.AddSeconds([int]$Cfg.StartTimeoutSeconds)
|
|
766
|
-
do {
|
|
767
|
-
$bridge = Test-OwnedInstance 'Bridge'
|
|
768
|
-
if (-not $bridge.Alive) { return $null }
|
|
769
|
-
$core = Get-DownstreamCoreObservation $Cfg $bridge
|
|
770
|
-
if (-not $core.alive -and $core.reason -ne 'startup-zero-child') { return $null }
|
|
771
|
-
if ($core.alive) {
|
|
772
|
-
$health = Get-LocalHealthOwned $Cfg $bridge
|
|
773
|
-
if ($health.State -in @('READY','DEGRADED')) { return $health }
|
|
774
|
-
}
|
|
775
|
-
if ([DateTime]::UtcNow -ge $deadline) { return $null }
|
|
776
|
-
Start-Sleep -Milliseconds 250
|
|
777
|
-
} while ([DateTime]::UtcNow -lt $deadline)
|
|
778
|
-
return $null
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
function Wait-LocalReady($Cfg) {
|
|
782
|
-
$deadline = [DateTime]::UtcNow.AddSeconds([int]$Cfg.StartTimeoutSeconds)
|
|
783
|
-
do {
|
|
784
|
-
$bridge = Test-OwnedInstance 'Bridge'
|
|
785
|
-
if (-not $bridge.Alive) { return $false }
|
|
786
|
-
$core = Get-DownstreamCoreObservation $Cfg $bridge
|
|
787
|
-
if (-not $core.alive -and $core.reason -ne 'startup-zero-child') { return $false }
|
|
788
|
-
if ($core.alive -and (Test-LocalReadyOwned $Cfg $bridge)) { return $true }
|
|
789
|
-
if ([DateTime]::UtcNow -ge $deadline) { return $false }
|
|
790
|
-
Start-Sleep -Milliseconds 250
|
|
791
|
-
} while ([DateTime]::UtcNow -lt $deadline)
|
|
792
|
-
return $false
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
function Wait-PublicDnsReady($Cfg, [string]$Url) {
|
|
796
|
-
try { $uri = [Uri]$Url } catch { Fail 'public MCP URL is invalid' }
|
|
797
|
-
if ($uri.Scheme -ne 'https') { Fail 'public MCP URL must be HTTPS' }
|
|
798
|
-
$deadline = [DateTime]::UtcNow.AddSeconds([int]$Cfg.StartTimeoutSeconds)
|
|
799
|
-
$requiredConsecutive = 3
|
|
800
|
-
$consecutive = 0
|
|
801
|
-
do {
|
|
802
|
-
try {
|
|
803
|
-
$addresses = @([System.Net.Dns]::GetHostAddresses($uri.DnsSafeHost))
|
|
804
|
-
if ($addresses.Count -gt 0) { $consecutive += 1 } else { $consecutive = 0 }
|
|
805
|
-
} catch { $consecutive = 0 }
|
|
806
|
-
if ($consecutive -ge $requiredConsecutive) { return }
|
|
807
|
-
Start-Sleep -Milliseconds 750
|
|
808
|
-
} while ([DateTime]::UtcNow -lt $deadline)
|
|
809
|
-
Fail 'public endpoint DNS did not become stably resolvable before gate'
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
function Get-CurrentUrl($Cfg) {
|
|
813
|
-
if ($Cfg.ContainsKey('McpUrlFile') -and -not [string]::IsNullOrWhiteSpace([string]$Cfg.McpUrlFile)) {
|
|
814
|
-
$fullFile = [string]$Cfg.McpUrlFile
|
|
815
|
-
if (-not (Test-Path -LiteralPath $fullFile -PathType Leaf)) { Fail "McpUrlFile missing: $fullFile" }
|
|
816
|
-
$transport = if ($Cfg.Cloud.ContainsKey('Transport')) { [string]$Cfg.Cloud.Transport } else { '' }
|
|
817
|
-
if ($transport -eq 'deadbyte.named-tunnel.v2') {
|
|
818
|
-
if (-not $Cfg.ContainsKey('McpUrlFileSha256') -or [string]$Cfg.McpUrlFileSha256 -cnotmatch '^[0-9a-f]{64}$') { Fail 'deadbyte.named-tunnel.v2 requires McpUrlFileSha256' }
|
|
819
|
-
$fullHash = Get-ExecutableHash $fullFile
|
|
820
|
-
if ($fullHash -ne [string]$Cfg.McpUrlFileSha256) { Fail "McpUrlFile SHA-256 mismatch: expected $($Cfg.McpUrlFileSha256), found $fullHash" }
|
|
821
|
-
}
|
|
822
|
-
$full = (Read-TextFileSafe $fullFile).Trim()
|
|
823
|
-
if ($full -notmatch '^https://') { Fail 'McpUrlFile must contain one HTTPS MCP endpoint' }
|
|
824
|
-
if ($transport -eq 'deadbyte.named-tunnel.v2') {
|
|
825
|
-
try { $fullUri = [System.Uri]$full } catch { Fail 'McpUrlFile must contain one valid HTTPS MCP endpoint' }
|
|
826
|
-
if ($fullUri.DnsSafeHost.ToLowerInvariant().EndsWith('.trycloudflare.com')) { Fail 'trycloudflare endpoint forbidden for deadbyte.named-tunnel.v2' }
|
|
827
|
-
}
|
|
828
|
-
return $full
|
|
829
|
-
}
|
|
830
|
-
$cloud = Test-OwnedInstance 'Cloud'
|
|
831
|
-
if (-not $cloud.Alive) { return $null }
|
|
832
|
-
$originFile = Join-Path $RuntimeDir 'current-origin.txt'
|
|
833
|
-
$origin = (Read-TextFileSafe $originFile).Trim()
|
|
834
|
-
if (-not $origin) {
|
|
835
|
-
$pattern = [string]$Cfg.UrlRegex
|
|
836
|
-
foreach ($file in @((Log-Path 'Cloud' 'stdout'), (Log-Path 'Cloud' 'stderr'))) {
|
|
837
|
-
$text = Read-TextFileSafe $file
|
|
838
|
-
$m = [regex]::Matches($text, $pattern)
|
|
839
|
-
if ($m.Count -gt 0) { $origin = $m[$m.Count - 1].Value }
|
|
840
|
-
}
|
|
841
|
-
if (-not $origin) { return $null }
|
|
842
|
-
Set-Content -LiteralPath $originFile -Value $origin -Encoding ASCII
|
|
843
|
-
}
|
|
844
|
-
$suffix = ''
|
|
845
|
-
if ($Cfg.ContainsKey('McpPathFile') -and -not [string]::IsNullOrWhiteSpace([string]$Cfg.McpPathFile)) {
|
|
846
|
-
$pathFile = [string]$Cfg.McpPathFile
|
|
847
|
-
if (-not (Test-Path -LiteralPath $pathFile -PathType Leaf)) { Fail "McpPathFile missing: $pathFile" }
|
|
848
|
-
$suffix = (Read-TextFileSafe $pathFile).Trim()
|
|
849
|
-
if ($suffix -and -not $suffix.StartsWith('/')) { Fail 'MCP path file must contain an absolute URL path beginning with slash' }
|
|
850
|
-
if ($suffix -match '[\r\n]') { Fail 'MCP path file contains newline data' }
|
|
851
|
-
}
|
|
852
|
-
return $origin.TrimEnd('/') + $suffix
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
function Wait-CurrentUrl($Cfg) {
|
|
856
|
-
$deadline = [DateTime]::UtcNow.AddSeconds([int]$Cfg.StartTimeoutSeconds)
|
|
857
|
-
do {
|
|
858
|
-
$cloud = Test-OwnedInstance 'Cloud'
|
|
859
|
-
if (-not $cloud.Alive) { return $null }
|
|
860
|
-
$url = Get-CurrentUrl $Cfg
|
|
861
|
-
if ($url) { return $url }
|
|
862
|
-
Start-Sleep -Milliseconds 250
|
|
863
|
-
} while ([DateTime]::UtcNow -lt $deadline)
|
|
864
|
-
return $null
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
function Invoke-CoreGate($Cfg, [string]$ScriptName, [string[]]$ExtraArguments = @()) {
|
|
868
|
-
$coreRoot = [string]$Cfg.CoreRoot
|
|
869
|
-
$script = Join-Path (Join-Path $coreRoot 'src') $ScriptName
|
|
870
|
-
if (-not (Test-Path -LiteralPath $script -PathType Leaf)) { Fail "core gate script missing: $script" }
|
|
871
|
-
$node = (Get-Command node.exe -ErrorAction Stop).Source
|
|
872
|
-
$stdout = Log-Path 'CoreGate' 'stdout'
|
|
873
|
-
$stderr = Log-Path 'CoreGate' 'stderr'
|
|
874
|
-
Remove-Item -LiteralPath $stdout,$stderr -Force -ErrorAction SilentlyContinue
|
|
875
|
-
$argv = @(('"{0}"' -f $script))
|
|
876
|
-
foreach ($arg in $ExtraArguments) {
|
|
877
|
-
if ($arg -match '[\r\n"]') { Fail 'core gate argument contains forbidden quoting/newline data' }
|
|
878
|
-
$argv += ('"{0}"' -f $arg)
|
|
879
|
-
}
|
|
880
|
-
$proc = Start-Process -FilePath $node -ArgumentList $argv -WorkingDirectory $coreRoot -Wait -PassThru -WindowStyle Hidden -RedirectStandardOutput $stdout -RedirectStandardError $stderr
|
|
881
|
-
if ($proc.ExitCode -ne 0) {
|
|
882
|
-
$detail = if (Test-Path -LiteralPath $stderr) { (Get-Content -LiteralPath $stderr -Raw -Encoding UTF8).Trim() } else { '' }
|
|
883
|
-
Fail "core gate $ScriptName failed exit=$($proc.ExitCode): $detail"
|
|
884
|
-
}
|
|
885
|
-
Write-Host (Get-Content -LiteralPath $stdout -Raw -Encoding UTF8)
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
function Start-Deadbyte($Cfg) {
|
|
889
|
-
$state = Get-ObservedState $Cfg
|
|
890
|
-
if ($state.State -in @('ONLINE','DEGRADED')) { return $state }
|
|
891
|
-
if ($state.State -in @('FAILED','STARTING')) { Fail "runtime is $($state.State); inspect status and reconcile verified owned components before restart" }
|
|
892
|
-
$cloudWasAlive = [bool]$state.Cloud.Alive
|
|
893
|
-
Assert-LocalReadyPortAvailable $Cfg
|
|
894
|
-
try {
|
|
895
|
-
Start-OwnedComponent 'Bridge' $Cfg.Bridge $Cfg | Out-Null
|
|
896
|
-
$localHealth = Wait-LocalOperational $Cfg
|
|
897
|
-
if ($null -eq $localHealth) { Fail 'bridge started but owned local readiness gate failed' }
|
|
898
|
-
Start-OwnedComponent 'Cloud' $Cfg.Cloud $Cfg | Out-Null
|
|
899
|
-
if (-not (Wait-CurrentUrl $Cfg)) { Fail 'public endpoint metadata did not become available before timeout' }
|
|
900
|
-
$final = Get-ObservedState $Cfg
|
|
901
|
-
if ($final.State -notin @('ONLINE','DEGRADED')) { Fail "start gate failed: observed $($final.State)" }
|
|
902
|
-
return $final
|
|
903
|
-
} catch {
|
|
904
|
-
try { Stop-OwnedComponent 'Bridge' } catch {}
|
|
905
|
-
if (-not $cloudWasAlive) { try { Stop-OwnedComponent 'Cloud' } catch {} }
|
|
906
|
-
throw
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
function Park-Deadbyte($Cfg) {
|
|
911
|
-
Disarm-Desktop $Cfg -Quiet
|
|
912
|
-
Stop-OwnedComponent 'Bridge'
|
|
913
|
-
$s = Get-ObservedState $Cfg
|
|
914
|
-
if ($s.State -notin @('PARKED','OFFLINE')) { Fail "park gate failed: observed $($s.State)" }
|
|
915
|
-
return $s
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
function Stop-Deadbyte($Cfg) {
|
|
919
|
-
Disarm-Desktop $Cfg -Quiet
|
|
920
|
-
Stop-OwnedComponent 'Bridge'
|
|
921
|
-
Stop-OwnedComponent 'Cloud'
|
|
922
|
-
Remove-Item -LiteralPath (Join-Path $RuntimeDir 'current-origin.txt') -Force -ErrorAction SilentlyContinue
|
|
923
|
-
$s = Get-ObservedState $Cfg
|
|
924
|
-
if ($s.State -ne 'OFFLINE') { Fail "stop gate failed: observed $($s.State)" }
|
|
925
|
-
return $s
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
function Assert-PolicyRootsAvailable($Policy, [string]$Plane) {
|
|
929
|
-
$rootIds = @($Policy.roots.PSObject.Properties.Name)
|
|
930
|
-
if ($rootIds.Count -lt 1) { Fail "$Plane policy requires at least one root" }
|
|
931
|
-
foreach ($rootId in $rootIds) {
|
|
932
|
-
$raw = [string]$Policy.roots.$rootId.path
|
|
933
|
-
if ([string]::IsNullOrWhiteSpace($raw)) { Fail "$Plane policy root '$rootId' path missing" }
|
|
934
|
-
try { $full = [System.IO.Path]::GetFullPath($raw) } catch { Fail "$Plane policy root '$rootId' path invalid" }
|
|
935
|
-
if (-not (Test-Path -LiteralPath $full -PathType Container)) { Fail "$Plane policy root '$rootId' missing: $full" }
|
|
936
|
-
}
|
|
937
|
-
return $rootIds
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
function Get-AuthorityEpochStatus($Cfg, [string]$Plane, [string]$GrantFile, [string]$GrantNonce) {
|
|
941
|
-
if ([string]::IsNullOrWhiteSpace($GrantNonce) -or $GrantNonce -cnotmatch '^[0-9a-f]{64}$') {
|
|
942
|
-
return [pscustomobject]@{ Trusted=$false; Reason='epoch-grant-nonce-invalid'; Seq=$null; Action=$null; EventSha256=$null }
|
|
943
|
-
}
|
|
944
|
-
try {
|
|
945
|
-
$verified = Invoke-AuthorityEpochCommand $Cfg $Plane 'status' $GrantFile
|
|
946
|
-
if (-not $verified.trusted) {
|
|
947
|
-
return [pscustomobject]@{ Trusted=$false; Reason=[string]$verified.reason; Seq=$verified.seq; Action=$verified.action; EventSha256=$verified.event_sha256 }
|
|
948
|
-
}
|
|
949
|
-
return [pscustomobject]@{ Trusted=$true; Reason=[string]$verified.reason; Seq=$verified.seq; Action=$verified.action; EventSha256=$verified.event_sha256 }
|
|
950
|
-
} catch {
|
|
951
|
-
return [pscustomobject]@{ Trusted=$false; Reason='authority-epoch-verifier-unavailable'; Seq=$null; Action=$null; EventSha256=$null }
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
function Invoke-AuthorityEpochCommand($Cfg, [string]$Plane, [string]$Action, [string]$GrantFile, [string]$UpdateId = '', [string]$ExpectedHead = '') {
|
|
956
|
-
$cli = Join-Path ([string]$Cfg.CoreRoot) 'src\authority-epoch-cli-r65.mjs'
|
|
957
|
-
if (-not (Test-Path -LiteralPath $cli -PathType Leaf)) { Fail "authority epoch CLI missing: $cli" }
|
|
958
|
-
$node = [string]$Cfg.Bridge.FilePath
|
|
959
|
-
if ([string]::IsNullOrWhiteSpace($node) -or -not (Test-Path -LiteralPath $node -PathType Leaf)) { Fail 'authority epoch Node executable unavailable' }
|
|
960
|
-
$policyFile = switch ($Plane) {
|
|
961
|
-
'process' { [string]$Cfg.ProcessPolicyFile }
|
|
962
|
-
'coding' { [string]$Cfg.CodingPolicyFile }
|
|
963
|
-
'autonomous' { [string]$Cfg.AutonomousPolicyFile }
|
|
964
|
-
'desktop' { [string]$Cfg.DesktopPolicyFile }
|
|
965
|
-
'host' { [string]$Cfg.HostPolicyFile }
|
|
966
|
-
default { Fail "authority epoch plane invalid: $Plane" }
|
|
967
|
-
}
|
|
968
|
-
if ([string]::IsNullOrWhiteSpace($policyFile) -or -not (Test-Path -LiteralPath $policyFile -PathType Leaf)) { Fail "authority epoch policy unavailable for $Plane" }
|
|
969
|
-
$args = @($cli, $Action, '--plane', $Plane, '--grant-file', $GrantFile, '--policy-file', $policyFile)
|
|
970
|
-
if ($Action -in @('transaction-revoke','transaction-restore')) {
|
|
971
|
-
if ([string]::IsNullOrWhiteSpace($UpdateId) -or $ExpectedHead -cnotmatch '^[0-9a-f]{64}$') { Fail 'authority epoch transaction binding invalid' }
|
|
972
|
-
$args += @('--update-id', $UpdateId, '--expected-head', $ExpectedHead)
|
|
973
|
-
}
|
|
974
|
-
$output = @(& $node @args)
|
|
975
|
-
$exit = $LASTEXITCODE
|
|
976
|
-
if ($exit -ne 0) { Fail "authority epoch command '$Action' failed for $Plane (exit=$exit)" }
|
|
977
|
-
try { return (($output -join [Environment]::NewLine) | ConvertFrom-Json) }
|
|
978
|
-
catch { Fail "authority epoch command '$Action' returned invalid JSON for $Plane" }
|
|
979
|
-
}
|
|
980
|
-
|
|
981
|
-
function Get-HostPolicyMetadata($Cfg) {
|
|
982
|
-
if ([string]::IsNullOrWhiteSpace([string]$Cfg.HostPolicyFile)) { Fail 'privileged host plane is not configured' }
|
|
983
|
-
$policyPath = [string]$Cfg.HostPolicyFile
|
|
984
|
-
try { $policy = Get-Content -LiteralPath $policyPath -Raw -Encoding UTF8 | ConvertFrom-Json } catch { Fail "invalid host policy JSON: $policyPath" }
|
|
985
|
-
if (@('deadbyte.host-policy.v1','deadbyte.host-policy.v2') -notcontains [string]$policy.schema -or $policy.enabled -ne $true) { Fail 'host policy schema/enabled mismatch' }
|
|
986
|
-
if ([string]::IsNullOrWhiteSpace([string]$policy.arm_file)) { Fail 'host policy arm_file missing' }
|
|
987
|
-
$armFile = [System.IO.Path]::GetFullPath([string]$policy.arm_file)
|
|
988
|
-
$runtimeReal = [System.IO.Path]::GetFullPath($RuntimeDir).TrimEnd('\') + '\'
|
|
989
|
-
if (-not $armFile.StartsWith($runtimeReal, [System.StringComparison]::OrdinalIgnoreCase)) { Fail 'host policy arm_file must live under controller runtime directory' }
|
|
990
|
-
Assert-PolicyRootsAvailable $policy 'host' | Out-Null
|
|
991
|
-
[pscustomobject]@{
|
|
992
|
-
Path = $policyPath
|
|
993
|
-
Sha256 = Get-ExecutableHash $policyPath
|
|
994
|
-
ArmFile = $armFile
|
|
995
|
-
}
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
function Get-HostArmStatus($Cfg) {
|
|
999
|
-
if ([string]::IsNullOrWhiteSpace([string]$Cfg.HostPolicyFile)) { return [pscustomobject]@{ Configured=$false; Armed=$false; Reason='no-policy' } }
|
|
1000
|
-
try { $meta = Get-HostPolicyMetadata $Cfg } catch { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason=$_.Exception.Message } }
|
|
1001
|
-
if (-not (Test-Path -LiteralPath $meta.ArmFile -PathType Leaf)) { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='disarmed' } }
|
|
1002
|
-
try { $arm = Get-Content -LiteralPath $meta.ArmFile -Raw -Encoding UTF8 | ConvertFrom-Json } catch { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='invalid-grant' } }
|
|
1003
|
-
if ($arm.schema -ne 'deadbyte.host-grant.v2') { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='grant-schema' } }
|
|
1004
|
-
if ([string]$arm.policy_sha256 -ne [string]$meta.Sha256) { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='policy-digest' } }
|
|
1005
|
-
if ([string]$arm.instance_nonce -cnotmatch '^[0-9a-f]{64}$') { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='nonce-invalid' } }
|
|
1006
|
-
$epoch = Get-AuthorityEpochStatus $Cfg 'host' $meta.ArmFile ([string]$arm.instance_nonce)
|
|
1007
|
-
if (-not $epoch.Trusted) { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason=[string]$epoch.Reason } }
|
|
1008
|
-
return [pscustomobject]@{ Configured=$true; Armed=$true; Reason='standing-grant'; EpochSeq=$epoch.Seq; EpochSha256=$epoch.EventSha256 }
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
function Arm-Host($Cfg) {
|
|
1012
|
-
$meta = Get-HostPolicyMetadata $Cfg
|
|
1013
|
-
Ensure-Directory $RuntimeDir
|
|
1014
|
-
$nonceBytes = New-Object byte[] 32
|
|
1015
|
-
$rng = [System.Security.Cryptography.RandomNumberGenerator]::Create()
|
|
1016
|
-
try { $rng.GetBytes($nonceBytes) } finally { $rng.Dispose() }
|
|
1017
|
-
$nonce = -join ($nonceBytes | ForEach-Object { $_.ToString('x2') })
|
|
1018
|
-
$now = [DateTime]::UtcNow
|
|
1019
|
-
$body = [ordered]@{
|
|
1020
|
-
schema = 'deadbyte.host-grant.v2'
|
|
1021
|
-
policy_sha256 = [string]$meta.Sha256
|
|
1022
|
-
instance_nonce = $nonce
|
|
1023
|
-
granted_at_utc = $now.ToString('o')
|
|
1024
|
-
}
|
|
1025
|
-
$tmp = $meta.ArmFile + '.tmp'
|
|
1026
|
-
$body | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath $tmp -Encoding UTF8
|
|
1027
|
-
Move-Item -LiteralPath $tmp -Destination $meta.ArmFile -Force
|
|
1028
|
-
try { Invoke-AuthorityEpochCommand $Cfg 'host' 'grant' $meta.ArmFile | Out-Null }
|
|
1029
|
-
catch {
|
|
1030
|
-
Remove-Item -LiteralPath $meta.ArmFile -Force -ErrorAction SilentlyContinue
|
|
1031
|
-
throw
|
|
1032
|
-
}
|
|
1033
|
-
Write-Host 'PRIVILEGED HOST PLANE ENABLED UNTIL EXPLICIT REVOKE'
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
function Disarm-Host($Cfg) {
|
|
1037
|
-
$meta = Get-HostPolicyMetadata $Cfg
|
|
1038
|
-
$epochError = $null
|
|
1039
|
-
if (Test-Path -LiteralPath $meta.ArmFile) {
|
|
1040
|
-
try { Invoke-AuthorityEpochCommand $Cfg 'host' 'user-revoke' $meta.ArmFile | Out-Null } catch { $epochError = $_.Exception }
|
|
1041
|
-
Remove-Item -LiteralPath $meta.ArmFile -Force -ErrorAction SilentlyContinue
|
|
1042
|
-
}
|
|
1043
|
-
if (Test-Path -LiteralPath $meta.ArmFile) { throw 'Host authority revocation failed: grant remains present' }
|
|
1044
|
-
if ($null -ne $epochError) { throw $epochError }
|
|
1045
|
-
Write-Host 'PRIVILEGED HOST PLANE DISARMED'
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
|
-
function Show-HostStatus($Cfg) {
|
|
1049
|
-
$h = Get-HostArmStatus $Cfg
|
|
1050
|
-
Write-Host ("Host plane : {0} [{1}]" -f $(if ($h.Armed) { 'ARMED' } elseif ($h.Configured) { 'DISARMED' } else { 'UNCONFIGURED' }), $h.Reason)
|
|
1051
|
-
return $h
|
|
1052
|
-
}
|
|
1053
|
-
|
|
1054
|
-
function Get-ProcessPolicyMetadata($Cfg) {
|
|
1055
|
-
if ([string]::IsNullOrWhiteSpace([string]$Cfg.ProcessPolicyFile)) { Fail 'process plane is not configured' }
|
|
1056
|
-
$policyPath = [string]$Cfg.ProcessPolicyFile
|
|
1057
|
-
try { $policy = Get-Content -LiteralPath $policyPath -Raw -Encoding UTF8 | ConvertFrom-Json } catch { Fail "invalid process policy JSON: $policyPath" }
|
|
1058
|
-
if ($policy.schema -ne 'deadbyte.process-policy.v1' -or $policy.enabled -ne $true) { Fail 'process policy schema/enabled mismatch' }
|
|
1059
|
-
if ([string]::IsNullOrWhiteSpace([string]$policy.grant_file)) { Fail 'process policy grant_file missing' }
|
|
1060
|
-
$grantFile = [System.IO.Path]::GetFullPath([string]$policy.grant_file)
|
|
1061
|
-
$runtimeReal = [System.IO.Path]::GetFullPath($RuntimeDir).TrimEnd('\') + '\'
|
|
1062
|
-
if (-not $grantFile.StartsWith($runtimeReal, [System.StringComparison]::OrdinalIgnoreCase)) { Fail 'process policy grant_file must live under controller runtime directory' }
|
|
1063
|
-
$profileIds = @($policy.profiles.PSObject.Properties.Name)
|
|
1064
|
-
$rootIds = @(Assert-PolicyRootsAvailable $policy 'process')
|
|
1065
|
-
if ($profileIds.Count -lt 1 -or $rootIds.Count -lt 1) { Fail 'process policy requires profiles and roots' }
|
|
1066
|
-
[pscustomobject]@{ Path=$policyPath; Sha256=Get-ExecutableHash $policyPath; GrantFile=$grantFile; ProfileIds=$profileIds; RootIds=$rootIds }
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
function Get-ProcessGrantStatus($Cfg) {
|
|
1070
|
-
if ([string]::IsNullOrWhiteSpace([string]$Cfg.ProcessPolicyFile)) { return [pscustomobject]@{ Configured=$false; Armed=$false; Reason='no-policy' } }
|
|
1071
|
-
try { $meta = Get-ProcessPolicyMetadata $Cfg } catch { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason=$_.Exception.Message } }
|
|
1072
|
-
if (-not (Test-Path -LiteralPath $meta.GrantFile -PathType Leaf)) { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='disarmed' } }
|
|
1073
|
-
try { $grant = Get-Content -LiteralPath $meta.GrantFile -Raw -Encoding UTF8 | ConvertFrom-Json } catch { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='invalid-grant' } }
|
|
1074
|
-
if ($grant.schema -ne 'deadbyte.process-grant.v1') { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='grant-schema' } }
|
|
1075
|
-
if ([string]$grant.policy_sha256 -ne [string]$meta.Sha256) { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='policy-digest' } }
|
|
1076
|
-
if ([string]$grant.instance_nonce -cnotmatch '^[0-9a-f]{64}$') { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='nonce-invalid' } }
|
|
1077
|
-
$profiles = @($grant.profile_ids); $roots = @($grant.root_ids)
|
|
1078
|
-
if ($profiles.Count -lt 1 -or @($profiles | Where-Object { $_ -notin $meta.ProfileIds }).Count -gt 0) { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='profiles-invalid' } }
|
|
1079
|
-
if ($roots.Count -lt 1 -or @($roots | Where-Object { $_ -notin $meta.RootIds }).Count -gt 0) { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='roots-invalid' } }
|
|
1080
|
-
$epoch = Get-AuthorityEpochStatus $Cfg 'process' $meta.GrantFile ([string]$grant.instance_nonce)
|
|
1081
|
-
if (-not $epoch.Trusted) { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason=[string]$epoch.Reason } }
|
|
1082
|
-
return [pscustomobject]@{ Configured=$true; Armed=$true; Reason='standing-grant'; EpochSeq=$epoch.Seq; EpochSha256=$epoch.EventSha256 }
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
|
-
function Arm-Process($Cfg) {
|
|
1086
|
-
$meta = Get-ProcessPolicyMetadata $Cfg
|
|
1087
|
-
Ensure-Directory $RuntimeDir
|
|
1088
|
-
$nonceBytes = New-Object byte[] 32
|
|
1089
|
-
$rng = [System.Security.Cryptography.RandomNumberGenerator]::Create()
|
|
1090
|
-
try { $rng.GetBytes($nonceBytes) } finally { $rng.Dispose() }
|
|
1091
|
-
$nonce = -join ($nonceBytes | ForEach-Object { $_.ToString('x2') })
|
|
1092
|
-
$body = [ordered]@{
|
|
1093
|
-
schema = 'deadbyte.process-grant.v1'
|
|
1094
|
-
policy_sha256 = [string]$meta.Sha256
|
|
1095
|
-
instance_nonce = $nonce
|
|
1096
|
-
profile_ids = @($meta.ProfileIds)
|
|
1097
|
-
root_ids = @($meta.RootIds)
|
|
1098
|
-
granted_at_utc = [DateTime]::UtcNow.ToString('o')
|
|
1099
|
-
}
|
|
1100
|
-
$tmp = $meta.GrantFile + '.tmp'
|
|
1101
|
-
$body | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath $tmp -Encoding UTF8
|
|
1102
|
-
Move-Item -LiteralPath $tmp -Destination $meta.GrantFile -Force
|
|
1103
|
-
try { Invoke-AuthorityEpochCommand $Cfg 'process' 'grant' $meta.GrantFile | Out-Null }
|
|
1104
|
-
catch {
|
|
1105
|
-
Remove-Item -LiteralPath $meta.GrantFile -Force -ErrorAction SilentlyContinue
|
|
1106
|
-
throw
|
|
1107
|
-
}
|
|
1108
|
-
Write-Host 'PROCESS PLANE ENABLED UNTIL EXPLICIT REVOKE'
|
|
1109
|
-
}
|
|
1110
|
-
|
|
1111
|
-
function Disarm-Process($Cfg) {
|
|
1112
|
-
$meta = Get-ProcessPolicyMetadata $Cfg
|
|
1113
|
-
$epochError = $null
|
|
1114
|
-
if (Test-Path -LiteralPath $meta.GrantFile -PathType Leaf) {
|
|
1115
|
-
try { Invoke-AuthorityEpochCommand $Cfg 'process' 'user-revoke' $meta.GrantFile | Out-Null } catch { $epochError = $_.Exception }
|
|
1116
|
-
Remove-Item -LiteralPath $meta.GrantFile -Force -ErrorAction SilentlyContinue
|
|
1117
|
-
}
|
|
1118
|
-
if (Test-Path -LiteralPath $meta.GrantFile) { throw 'Process authority revocation failed: grant remains present' }
|
|
1119
|
-
if ($null -ne $epochError) { throw $epochError }
|
|
1120
|
-
Write-Host 'PROCESS PLANE DISARMED'
|
|
1121
|
-
}
|
|
1122
|
-
|
|
1123
|
-
function Show-ProcessStatus($Cfg) {
|
|
1124
|
-
$p = Get-ProcessGrantStatus $Cfg
|
|
1125
|
-
Write-Host ("Process plane : {0} [{1}]" -f $(if ($p.Armed) { 'ARMED' } elseif ($p.Configured) { 'DISARMED' } else { 'UNCONFIGURED' }), $p.Reason)
|
|
1126
|
-
return $p
|
|
1127
|
-
}
|
|
1128
|
-
|
|
1129
|
-
function
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
$
|
|
1137
|
-
|
|
1138
|
-
$
|
|
1139
|
-
|
|
1140
|
-
}
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
if (
|
|
1149
|
-
if ([string]
|
|
1150
|
-
$
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
}
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
$
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
}
|
|
1172
|
-
$
|
|
1173
|
-
$
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
$
|
|
1185
|
-
$
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
}
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
}
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
}
|
|
1204
|
-
|
|
1205
|
-
function
|
|
1206
|
-
|
|
1207
|
-
$
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
if ($
|
|
1218
|
-
|
|
1219
|
-
}
|
|
1220
|
-
|
|
1221
|
-
function
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
if ($
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
$
|
|
1233
|
-
if (
|
|
1234
|
-
$
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
$
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
}
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
}
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
$
|
|
1276
|
-
|
|
1277
|
-
return
|
|
1278
|
-
}
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
if ([string]
|
|
1282
|
-
$
|
|
1283
|
-
|
|
1284
|
-
if ($
|
|
1285
|
-
if (
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
$
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
[
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
[pscustomobject]@{
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
}
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
$
|
|
1327
|
-
$
|
|
1328
|
-
if (
|
|
1329
|
-
|
|
1330
|
-
}
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
try { $
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
$
|
|
1341
|
-
$
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
}
|
|
1348
|
-
|
|
1349
|
-
function
|
|
1350
|
-
$
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
$
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
$
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
}
|
|
1372
|
-
|
|
1373
|
-
}
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
$
|
|
1377
|
-
|
|
1378
|
-
$
|
|
1379
|
-
if (
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
if (
|
|
1384
|
-
if ($
|
|
1385
|
-
if (-not $
|
|
1386
|
-
}
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
$
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
}
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
}
|
|
1408
|
-
|
|
1409
|
-
function
|
|
1410
|
-
if (
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
}
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
$
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
if (-not
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
$
|
|
1431
|
-
if ($
|
|
1432
|
-
$
|
|
1433
|
-
$
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
$
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
$
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
}
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
$
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
}
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
}
|
|
1496
|
-
|
|
1497
|
-
function
|
|
1498
|
-
$
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
}
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
$
|
|
1522
|
-
$
|
|
1523
|
-
$
|
|
1524
|
-
$
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
Write-Host
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
$
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
}
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
}
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
$
|
|
1610
|
-
if (
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
Write-Host
|
|
1614
|
-
}
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
}
|
|
1626
|
-
}
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
$
|
|
1630
|
-
|
|
1631
|
-
$
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
'
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
'
|
|
1663
|
-
|
|
1664
|
-
'
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
}
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
$
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
}
|
|
1683
|
-
|
|
1684
|
-
}
|
|
1685
|
-
|
|
1686
|
-
if ($
|
|
1687
|
-
$
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
}
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
}
|
|
556
|
+
$env:DEADBYTE_PROCESS_RUNTIME = if ([bool]$Cfg.ProcessRuntimeEnabled) { '1' } else { '0' }
|
|
557
|
+
if (-not [string]::IsNullOrWhiteSpace([string]$Cfg.ProcessPolicyFile)) {
|
|
558
|
+
$env:DEADBYTE_PROCESS_POLICY = [string]$Cfg.ProcessPolicyFile
|
|
559
|
+
}
|
|
560
|
+
$env:DEADBYTE_CODING_RUNTIME = if ([bool]$Cfg.CodingRuntimeEnabled) { '1' } else { '0' }
|
|
561
|
+
$env:DEADBYTE_MACHINE_FS_RUNTIME = if ([bool]$Cfg.MachineFsRuntimeEnabled) { '1' } else { '0' }
|
|
562
|
+
$env:DEADBYTE_OBSERVATION_RUNTIME = if ([bool]$Cfg.ObservationRuntimeEnabled) { '1' } else { '0' }
|
|
563
|
+
$env:DEADBYTE_DESKTOP_RUNTIME = if ([bool]$Cfg.DesktopRuntimeEnabled) { '1' } else { '0' }
|
|
564
|
+
if (-not [string]::IsNullOrWhiteSpace([string]$Cfg.DesktopPolicyFile)) {
|
|
565
|
+
$env:DEADBYTE_DESKTOP_POLICY = [string]$Cfg.DesktopPolicyFile
|
|
566
|
+
} else {
|
|
567
|
+
Remove-Item Env:DEADBYTE_DESKTOP_POLICY -ErrorAction SilentlyContinue
|
|
568
|
+
}
|
|
569
|
+
if (-not [string]::IsNullOrWhiteSpace([string]$Cfg.CodingPolicyFile)) {
|
|
570
|
+
$env:DEADBYTE_CODING_POLICY = [string]$Cfg.CodingPolicyFile
|
|
571
|
+
}
|
|
572
|
+
if (-not [string]::IsNullOrWhiteSpace([string]$Cfg.SemanticLspConfigFile)) {
|
|
573
|
+
$env:DEADBYTE_SEMANTIC_LSP_CONFIG = [string]$Cfg.SemanticLspConfigFile
|
|
574
|
+
$env:DEADBYTE_SEMANTIC_LSP_CONFIG_SHA256 = [string]$Cfg.SemanticLspConfigSha256
|
|
575
|
+
} else {
|
|
576
|
+
Remove-Item Env:DEADBYTE_SEMANTIC_LSP_CONFIG -ErrorAction SilentlyContinue
|
|
577
|
+
Remove-Item Env:DEADBYTE_SEMANTIC_LSP_CONFIG_SHA256 -ErrorAction SilentlyContinue
|
|
578
|
+
}
|
|
579
|
+
$env:DEADBYTE_AUTONOMOUS_RUNTIME = if ([bool]$Cfg.AutonomousRuntimeEnabled) { '1' } else { '0' }
|
|
580
|
+
$env:DEADBYTE_AUTONOMOUS_SUPERVISOR_MODE = if ([bool]$Cfg.AutonomousSupervisorEnabled) { 'external' } else { 'in_process' }
|
|
581
|
+
if (-not [string]::IsNullOrWhiteSpace([string]$Cfg.AutonomousPolicyFile)) {
|
|
582
|
+
$env:DEADBYTE_AUTONOMOUS_POLICY = [string]$Cfg.AutonomousPolicyFile
|
|
583
|
+
}
|
|
584
|
+
if (-not [string]::IsNullOrWhiteSpace([string]$Cfg.HostPolicyFile)) {
|
|
585
|
+
$env:DEADBYTE_PRIVILEGED_POLICY = [string]$Cfg.HostPolicyFile
|
|
586
|
+
}
|
|
587
|
+
} elseif ($Name -eq 'Supervisor' -and $Cfg) {
|
|
588
|
+
Remove-Item Env:DEADBYTE_CAPABILITY_PROFILE -ErrorAction SilentlyContinue
|
|
589
|
+
Remove-Item Env:DEADBYTE_AUDIT_RUNTIME -ErrorAction SilentlyContinue
|
|
590
|
+
Remove-Item Env:DEADBYTE_PRIVILEGED_POLICY -ErrorAction SilentlyContinue
|
|
591
|
+
Remove-Item Env:DEADBYTE_MACHINE_RUNTIME -ErrorAction SilentlyContinue
|
|
592
|
+
Remove-Item Env:DEADBYTE_AGENT_RUNTIME -ErrorAction SilentlyContinue
|
|
593
|
+
Remove-Item Env:DEADBYTE_PROCESS_RUNTIME -ErrorAction SilentlyContinue
|
|
594
|
+
Remove-Item Env:DEADBYTE_PROCESS_POLICY -ErrorAction SilentlyContinue
|
|
595
|
+
Remove-Item Env:DEADBYTE_MACHINE_FS_RUNTIME -ErrorAction SilentlyContinue
|
|
596
|
+
Remove-Item Env:DEADBYTE_OBSERVATION_RUNTIME -ErrorAction SilentlyContinue
|
|
597
|
+
Remove-Item Env:DEADBYTE_DESKTOP_RUNTIME -ErrorAction SilentlyContinue
|
|
598
|
+
Remove-Item Env:DEADBYTE_DESKTOP_POLICY -ErrorAction SilentlyContinue
|
|
599
|
+
Remove-Item Env:DEADBYTE_SEMANTIC_LSP_CONFIG -ErrorAction SilentlyContinue
|
|
600
|
+
Remove-Item Env:DEADBYTE_SEMANTIC_LSP_CONFIG_SHA256 -ErrorAction SilentlyContinue
|
|
601
|
+
Remove-Item Env:DEADBYTE_AUTONOMOUS_SUPERVISOR_MODE -ErrorAction SilentlyContinue
|
|
602
|
+
$env:DEADBYTE_CODING_RUNTIME = '1'
|
|
603
|
+
$env:DEADBYTE_AUTONOMOUS_RUNTIME = '1'
|
|
604
|
+
$env:DEADBYTE_CODING_POLICY = [string]$Cfg.CodingPolicyFile
|
|
605
|
+
$env:DEADBYTE_AUTONOMOUS_POLICY = [string]$Cfg.AutonomousPolicyFile
|
|
606
|
+
$env:DEADBYTE_SUPERVISOR_ONCE = '0'
|
|
607
|
+
$env:DEADBYTE_SUPERVISOR_MARKER = Join-Path $RuntimeDir 'autonomous-supervisor-owner.json'
|
|
608
|
+
} else {
|
|
609
|
+
Remove-Item Env:DEADBYTE_CAPABILITY_PROFILE -ErrorAction SilentlyContinue
|
|
610
|
+
Remove-Item Env:DEADBYTE_AUDIT_RUNTIME -ErrorAction SilentlyContinue
|
|
611
|
+
}
|
|
612
|
+
$proc = Start-Process @start
|
|
613
|
+
} finally {
|
|
614
|
+
if ($hadCoreRootEnv) { $env:DEADBYTE_CORE_ROOT = $previousCoreRootEnv }
|
|
615
|
+
else { Remove-Item Env:DEADBYTE_CORE_ROOT -ErrorAction SilentlyContinue }
|
|
616
|
+
if ($hadHostPolicyEnv) { $env:DEADBYTE_PRIVILEGED_POLICY = $previousHostPolicyEnv }
|
|
617
|
+
else { Remove-Item Env:DEADBYTE_PRIVILEGED_POLICY -ErrorAction SilentlyContinue }
|
|
618
|
+
if ($hadWorkspaceRootEnv) { $env:DEADBYTE_WORKSPACE_ROOT = $previousWorkspaceRootEnv }
|
|
619
|
+
else { Remove-Item Env:DEADBYTE_WORKSPACE_ROOT -ErrorAction SilentlyContinue }
|
|
620
|
+
if ($hadAuditRuntimeEnv) { $env:DEADBYTE_AUDIT_RUNTIME = $previousAuditRuntimeEnv }
|
|
621
|
+
else { Remove-Item Env:DEADBYTE_AUDIT_RUNTIME -ErrorAction SilentlyContinue }
|
|
622
|
+
if ($hadMachineRuntimeEnv) { $env:DEADBYTE_MACHINE_RUNTIME = $previousMachineRuntimeEnv }
|
|
623
|
+
else { Remove-Item Env:DEADBYTE_MACHINE_RUNTIME -ErrorAction SilentlyContinue }
|
|
624
|
+
if ($hadAgentRuntimeEnv) { $env:DEADBYTE_AGENT_RUNTIME = $previousAgentRuntimeEnv }
|
|
625
|
+
else { Remove-Item Env:DEADBYTE_AGENT_RUNTIME -ErrorAction SilentlyContinue }
|
|
626
|
+
if ($hadProcessRuntimeEnv) { $env:DEADBYTE_PROCESS_RUNTIME = $previousProcessRuntimeEnv }
|
|
627
|
+
else { Remove-Item Env:DEADBYTE_PROCESS_RUNTIME -ErrorAction SilentlyContinue }
|
|
628
|
+
if ($hadProcessPolicyEnv) { $env:DEADBYTE_PROCESS_POLICY = $previousProcessPolicyEnv }
|
|
629
|
+
else { Remove-Item Env:DEADBYTE_PROCESS_POLICY -ErrorAction SilentlyContinue }
|
|
630
|
+
if ($hadCodingRuntimeEnv) { $env:DEADBYTE_CODING_RUNTIME = $previousCodingRuntimeEnv }
|
|
631
|
+
else { Remove-Item Env:DEADBYTE_CODING_RUNTIME -ErrorAction SilentlyContinue }
|
|
632
|
+
if ($hadMachineFsRuntimeEnv) { $env:DEADBYTE_MACHINE_FS_RUNTIME = $previousMachineFsRuntimeEnv }
|
|
633
|
+
else { Remove-Item Env:DEADBYTE_MACHINE_FS_RUNTIME -ErrorAction SilentlyContinue }
|
|
634
|
+
if ($hadObservationRuntimeEnv) { $env:DEADBYTE_OBSERVATION_RUNTIME = $previousObservationRuntimeEnv }
|
|
635
|
+
else { Remove-Item Env:DEADBYTE_OBSERVATION_RUNTIME -ErrorAction SilentlyContinue }
|
|
636
|
+
if ($hadDesktopRuntimeEnv) { $env:DEADBYTE_DESKTOP_RUNTIME = $previousDesktopRuntimeEnv }
|
|
637
|
+
else { Remove-Item Env:DEADBYTE_DESKTOP_RUNTIME -ErrorAction SilentlyContinue }
|
|
638
|
+
if ($hadDesktopPolicyEnv) { $env:DEADBYTE_DESKTOP_POLICY = $previousDesktopPolicyEnv }
|
|
639
|
+
else { Remove-Item Env:DEADBYTE_DESKTOP_POLICY -ErrorAction SilentlyContinue }
|
|
640
|
+
if ($hadCodingPolicyEnv) { $env:DEADBYTE_CODING_POLICY = $previousCodingPolicyEnv }
|
|
641
|
+
else { Remove-Item Env:DEADBYTE_CODING_POLICY -ErrorAction SilentlyContinue }
|
|
642
|
+
if ($hadSemanticConfigEnv) { $env:DEADBYTE_SEMANTIC_LSP_CONFIG = $previousSemanticLspConfigEnv }
|
|
643
|
+
else { Remove-Item Env:DEADBYTE_SEMANTIC_LSP_CONFIG -ErrorAction SilentlyContinue }
|
|
644
|
+
if ($hadSemanticConfigShaEnv) { $env:DEADBYTE_SEMANTIC_LSP_CONFIG_SHA256 = $previousSemanticLspConfigShaEnv }
|
|
645
|
+
else { Remove-Item Env:DEADBYTE_SEMANTIC_LSP_CONFIG_SHA256 -ErrorAction SilentlyContinue }
|
|
646
|
+
if ($hadAutonomousRuntimeEnv) { $env:DEADBYTE_AUTONOMOUS_RUNTIME = $previousAutonomousRuntimeEnv }
|
|
647
|
+
else { Remove-Item Env:DEADBYTE_AUTONOMOUS_RUNTIME -ErrorAction SilentlyContinue }
|
|
648
|
+
if ($hadAutonomousPolicyEnv) { $env:DEADBYTE_AUTONOMOUS_POLICY = $previousAutonomousPolicyEnv }
|
|
649
|
+
else { Remove-Item Env:DEADBYTE_AUTONOMOUS_POLICY -ErrorAction SilentlyContinue }
|
|
650
|
+
if ($hadSupervisorOnceEnv) { $env:DEADBYTE_SUPERVISOR_ONCE = $previousSupervisorOnceEnv }
|
|
651
|
+
else { Remove-Item Env:DEADBYTE_SUPERVISOR_ONCE -ErrorAction SilentlyContinue }
|
|
652
|
+
if ($hadSupervisorMarkerEnv) { $env:DEADBYTE_SUPERVISOR_MARKER = $previousSupervisorMarkerEnv }
|
|
653
|
+
else { Remove-Item Env:DEADBYTE_SUPERVISOR_MARKER -ErrorAction SilentlyContinue }
|
|
654
|
+
if ($hadAutonomousSupervisorModeEnv) { $env:DEADBYTE_AUTONOMOUS_SUPERVISOR_MODE = $previousAutonomousSupervisorModeEnv }
|
|
655
|
+
else { Remove-Item Env:DEADBYTE_AUTONOMOUS_SUPERVISOR_MODE -ErrorAction SilentlyContinue }
|
|
656
|
+
if ($hadCapabilityProfileEnv) { $env:DEADBYTE_CAPABILITY_PROFILE = $previousCapabilityProfileEnv }
|
|
657
|
+
else { Remove-Item Env:DEADBYTE_CAPABILITY_PROFILE -ErrorAction SilentlyContinue }
|
|
658
|
+
}
|
|
659
|
+
Start-Sleep -Milliseconds 150
|
|
660
|
+
$obs = Get-ProcessObservation ([int]$proc.Id)
|
|
661
|
+
if (-not $obs) { Fail "$Name exited before identity capture; see $stderr" }
|
|
662
|
+
$expected = [System.IO.Path]::GetFullPath([string]$Spec.FilePath)
|
|
663
|
+
if (-not [string]::Equals($obs.image_path, $expected, [System.StringComparison]::OrdinalIgnoreCase)) {
|
|
664
|
+
try { Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue } catch {}
|
|
665
|
+
Fail "$Name spawned a wrapper instead of the configured long-lived executable; observed '$($obs.image_path)'"
|
|
666
|
+
}
|
|
667
|
+
if ($obs.image_sha256 -ne (Get-ExecutableHash $expected)) {
|
|
668
|
+
try { Stop-Process -Id $proc.Id -Force -ErrorAction SilentlyContinue } catch {}
|
|
669
|
+
Fail "$Name executable changed during start"
|
|
670
|
+
}
|
|
671
|
+
Write-Ledger $Name $obs $nonce $Spec
|
|
672
|
+
return Test-OwnedInstance $Name
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
function Stop-OwnedComponent([string]$Name) {
|
|
676
|
+
$owned = Test-OwnedInstance $Name
|
|
677
|
+
if (-not $owned.Ledger) { return }
|
|
678
|
+
if (-not $owned.Alive) {
|
|
679
|
+
if ($owned.Reason -eq 'process-missing') { Remove-Item -LiteralPath (Ledger-Path $Name) -Force -ErrorAction SilentlyContinue; return }
|
|
680
|
+
if ($owned.Reason -eq 'pid-reused') { Reconcile-PidReusedLedger $Name $owned | Out-Null; return }
|
|
681
|
+
Fail "$Name is not provably owned ($($owned.Reason)); refusing Stop-Process"
|
|
682
|
+
}
|
|
683
|
+
Stop-Process -Id ([int]$owned.Ledger.pid) -Force -ErrorAction Stop
|
|
684
|
+
try { Wait-Process -Id ([int]$owned.Ledger.pid) -Timeout 5 -ErrorAction SilentlyContinue } catch {}
|
|
685
|
+
Remove-Item -LiteralPath (Ledger-Path $Name) -Force -ErrorAction SilentlyContinue
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
function Get-LocalHealth($Cfg) {
|
|
689
|
+
$response = $null
|
|
690
|
+
try {
|
|
691
|
+
$request = [System.Net.HttpWebRequest]::Create([string]$Cfg.LocalReadyUri)
|
|
692
|
+
$request.Timeout = [int]$Cfg.ProbeTimeoutSeconds * 1000
|
|
693
|
+
$request.ReadWriteTimeout = [int]$Cfg.ProbeTimeoutSeconds * 1000
|
|
694
|
+
$response = $request.GetResponse()
|
|
695
|
+
} catch [System.Net.WebException] {
|
|
696
|
+
$response = $_.Exception.Response
|
|
697
|
+
if ($null -eq $response) { return [pscustomobject]@{ State='UNREACHABLE'; StatusCode=0; Diagnostic='readiness endpoint unreachable' } }
|
|
698
|
+
} catch { return [pscustomobject]@{ State='UNREACHABLE'; StatusCode=0; Diagnostic='readiness probe failed' } }
|
|
699
|
+
try {
|
|
700
|
+
$status = [int]$response.StatusCode
|
|
701
|
+
$reader = New-Object System.IO.StreamReader($response.GetResponseStream())
|
|
702
|
+
try { $body = $reader.ReadToEnd() } finally { $reader.Dispose() }
|
|
703
|
+
try { $parsed = $body | ConvertFrom-Json } catch { return [pscustomobject]@{ State='INVALID'; StatusCode=$status; Diagnostic='readiness response JSON invalid' } }
|
|
704
|
+
if ($parsed.schema -ne 'deadbyte.bridge-health.v1' -or [string]$parsed.state -notin @('STARTING','READY','DEGRADED','RECONCILING','STOPPING')) {
|
|
705
|
+
return [pscustomobject]@{ State='INVALID'; StatusCode=$status; Diagnostic='readiness response schema invalid' }
|
|
706
|
+
}
|
|
707
|
+
return [pscustomobject]@{ State=[string]$parsed.state; StatusCode=$status; Diagnostic=[string]$parsed.diagnostic }
|
|
708
|
+
} finally { if ($null -ne $response) { $response.Dispose() } }
|
|
709
|
+
}
|
|
710
|
+
function Test-LocalReady($Cfg) {
|
|
711
|
+
$health = Get-LocalHealth $Cfg
|
|
712
|
+
return ($health.State -eq 'READY' -and $health.StatusCode -ge 200 -and $health.StatusCode -lt 300)
|
|
713
|
+
}
|
|
714
|
+
function Get-LocalReadyListenerOwners($Cfg) {
|
|
715
|
+
try { $uri = [Uri]([string]$Cfg.LocalReadyUri) } catch { Fail 'LocalReadyUri is invalid' }
|
|
716
|
+
if (-not $uri.IsLoopback) { Fail 'LocalReadyUri must be loopback for owned readiness attribution' }
|
|
717
|
+
try {
|
|
718
|
+
$listeners = @(Get-NetTCPConnection -State Listen -ErrorAction Stop | Where-Object {
|
|
719
|
+
[int]$_.LocalPort -eq [int]$uri.Port -and (
|
|
720
|
+
[string]$_.LocalAddress -eq [string]$uri.Host -or
|
|
721
|
+
[string]$_.LocalAddress -eq '0.0.0.0' -or
|
|
722
|
+
[string]$_.LocalAddress -eq '::'
|
|
723
|
+
)
|
|
724
|
+
})
|
|
725
|
+
} catch { Fail "cannot inspect listener ownership for local readiness endpoint: $($_.Exception.Message)" }
|
|
726
|
+
return @($listeners | ForEach-Object { [int]$_.OwningProcess } | Sort-Object -Unique)
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
function Assert-LocalReadyPortAvailable($Cfg) {
|
|
730
|
+
$owners = @(Get-LocalReadyListenerOwners $Cfg)
|
|
731
|
+
if ($owners.Count -gt 0) {
|
|
732
|
+
Fail ("local readiness endpoint already has listener pid(s)={0}; refusing to start an unverified Bridge" -f ($owners -join ','))
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
function Test-LocalReadyOwned($Cfg, $Bridge) {
|
|
737
|
+
if (-not $Bridge -or -not $Bridge.Alive) { return $false }
|
|
738
|
+
$owners = @(Get-LocalReadyListenerOwners $Cfg)
|
|
739
|
+
if ($owners.Count -ne 1 -or $owners[0] -ne [int]$Bridge.Ledger.pid) { return $false }
|
|
740
|
+
return (Test-LocalReady $Cfg)
|
|
741
|
+
}
|
|
742
|
+
function Get-LocalHealthOwned($Cfg, $Bridge) {
|
|
743
|
+
if (-not $Bridge -or -not $Bridge.Alive) { return [pscustomobject]@{ State='UNREACHABLE'; StatusCode=0; Diagnostic='bridge not alive' } }
|
|
744
|
+
$owners = @(Get-LocalReadyListenerOwners $Cfg)
|
|
745
|
+
if ($owners.Count -ne 1 -or $owners[0] -ne [int]$Bridge.Ledger.pid) { return [pscustomobject]@{ State='UNATTRIBUTED'; StatusCode=0; Diagnostic='readiness listener ownership mismatch' } }
|
|
746
|
+
return (Get-LocalHealth $Cfg)
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
function Get-ObservedState($Cfg) {
|
|
750
|
+
$bridge = Test-OwnedInstance 'Bridge'
|
|
751
|
+
$cloud = Test-OwnedInstance 'Cloud'
|
|
752
|
+
$core = Get-DownstreamCoreObservation $Cfg $bridge
|
|
753
|
+
$health = if ($bridge.Alive -and $core.alive) { try { Get-LocalHealthOwned $Cfg $bridge } catch { [pscustomobject]@{ State='UNREACHABLE'; StatusCode=0; Diagnostic='readiness attribution failed' } } } else { [pscustomobject]@{ State='UNREACHABLE'; StatusCode=0; Diagnostic='core not alive' } }
|
|
754
|
+
$ready = $health.State -eq 'READY'
|
|
755
|
+
$state = if (-not $bridge.Alive -and -not $cloud.Alive) { 'OFFLINE' }
|
|
756
|
+
elseif (-not $bridge.Alive -and $cloud.Alive) { 'PARKED' }
|
|
757
|
+
elseif ($bridge.Alive -and $cloud.Alive -and $ready) { 'ONLINE' }
|
|
758
|
+
elseif ($bridge.Alive -and $cloud.Alive -and $core.alive -and $health.State -eq 'DEGRADED') { 'DEGRADED' }
|
|
759
|
+
elseif ($bridge.Alive -and $core.reason -eq 'startup-zero-child') { 'STARTING' }
|
|
760
|
+
else { 'FAILED' }
|
|
761
|
+
[pscustomobject]@{ State=$state; Bridge=$bridge; Core=$core; Cloud=$cloud; LocalReady=$ready; LocalHealth=$health }
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
function Wait-LocalOperational($Cfg) {
|
|
765
|
+
$deadline = [DateTime]::UtcNow.AddSeconds([int]$Cfg.StartTimeoutSeconds)
|
|
766
|
+
do {
|
|
767
|
+
$bridge = Test-OwnedInstance 'Bridge'
|
|
768
|
+
if (-not $bridge.Alive) { return $null }
|
|
769
|
+
$core = Get-DownstreamCoreObservation $Cfg $bridge
|
|
770
|
+
if (-not $core.alive -and $core.reason -ne 'startup-zero-child') { return $null }
|
|
771
|
+
if ($core.alive) {
|
|
772
|
+
$health = Get-LocalHealthOwned $Cfg $bridge
|
|
773
|
+
if ($health.State -in @('READY','DEGRADED')) { return $health }
|
|
774
|
+
}
|
|
775
|
+
if ([DateTime]::UtcNow -ge $deadline) { return $null }
|
|
776
|
+
Start-Sleep -Milliseconds 250
|
|
777
|
+
} while ([DateTime]::UtcNow -lt $deadline)
|
|
778
|
+
return $null
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
function Wait-LocalReady($Cfg) {
|
|
782
|
+
$deadline = [DateTime]::UtcNow.AddSeconds([int]$Cfg.StartTimeoutSeconds)
|
|
783
|
+
do {
|
|
784
|
+
$bridge = Test-OwnedInstance 'Bridge'
|
|
785
|
+
if (-not $bridge.Alive) { return $false }
|
|
786
|
+
$core = Get-DownstreamCoreObservation $Cfg $bridge
|
|
787
|
+
if (-not $core.alive -and $core.reason -ne 'startup-zero-child') { return $false }
|
|
788
|
+
if ($core.alive -and (Test-LocalReadyOwned $Cfg $bridge)) { return $true }
|
|
789
|
+
if ([DateTime]::UtcNow -ge $deadline) { return $false }
|
|
790
|
+
Start-Sleep -Milliseconds 250
|
|
791
|
+
} while ([DateTime]::UtcNow -lt $deadline)
|
|
792
|
+
return $false
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
function Wait-PublicDnsReady($Cfg, [string]$Url) {
|
|
796
|
+
try { $uri = [Uri]$Url } catch { Fail 'public MCP URL is invalid' }
|
|
797
|
+
if ($uri.Scheme -ne 'https') { Fail 'public MCP URL must be HTTPS' }
|
|
798
|
+
$deadline = [DateTime]::UtcNow.AddSeconds([int]$Cfg.StartTimeoutSeconds)
|
|
799
|
+
$requiredConsecutive = 3
|
|
800
|
+
$consecutive = 0
|
|
801
|
+
do {
|
|
802
|
+
try {
|
|
803
|
+
$addresses = @([System.Net.Dns]::GetHostAddresses($uri.DnsSafeHost))
|
|
804
|
+
if ($addresses.Count -gt 0) { $consecutive += 1 } else { $consecutive = 0 }
|
|
805
|
+
} catch { $consecutive = 0 }
|
|
806
|
+
if ($consecutive -ge $requiredConsecutive) { return }
|
|
807
|
+
Start-Sleep -Milliseconds 750
|
|
808
|
+
} while ([DateTime]::UtcNow -lt $deadline)
|
|
809
|
+
Fail 'public endpoint DNS did not become stably resolvable before gate'
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
function Get-CurrentUrl($Cfg) {
|
|
813
|
+
if ($Cfg.ContainsKey('McpUrlFile') -and -not [string]::IsNullOrWhiteSpace([string]$Cfg.McpUrlFile)) {
|
|
814
|
+
$fullFile = [string]$Cfg.McpUrlFile
|
|
815
|
+
if (-not (Test-Path -LiteralPath $fullFile -PathType Leaf)) { Fail "McpUrlFile missing: $fullFile" }
|
|
816
|
+
$transport = if ($Cfg.Cloud.ContainsKey('Transport')) { [string]$Cfg.Cloud.Transport } else { '' }
|
|
817
|
+
if ($transport -eq 'deadbyte.named-tunnel.v2') {
|
|
818
|
+
if (-not $Cfg.ContainsKey('McpUrlFileSha256') -or [string]$Cfg.McpUrlFileSha256 -cnotmatch '^[0-9a-f]{64}$') { Fail 'deadbyte.named-tunnel.v2 requires McpUrlFileSha256' }
|
|
819
|
+
$fullHash = Get-ExecutableHash $fullFile
|
|
820
|
+
if ($fullHash -ne [string]$Cfg.McpUrlFileSha256) { Fail "McpUrlFile SHA-256 mismatch: expected $($Cfg.McpUrlFileSha256), found $fullHash" }
|
|
821
|
+
}
|
|
822
|
+
$full = (Read-TextFileSafe $fullFile).Trim()
|
|
823
|
+
if ($full -notmatch '^https://') { Fail 'McpUrlFile must contain one HTTPS MCP endpoint' }
|
|
824
|
+
if ($transport -eq 'deadbyte.named-tunnel.v2') {
|
|
825
|
+
try { $fullUri = [System.Uri]$full } catch { Fail 'McpUrlFile must contain one valid HTTPS MCP endpoint' }
|
|
826
|
+
if ($fullUri.DnsSafeHost.ToLowerInvariant().EndsWith('.trycloudflare.com')) { Fail 'trycloudflare endpoint forbidden for deadbyte.named-tunnel.v2' }
|
|
827
|
+
}
|
|
828
|
+
return $full
|
|
829
|
+
}
|
|
830
|
+
$cloud = Test-OwnedInstance 'Cloud'
|
|
831
|
+
if (-not $cloud.Alive) { return $null }
|
|
832
|
+
$originFile = Join-Path $RuntimeDir 'current-origin.txt'
|
|
833
|
+
$origin = (Read-TextFileSafe $originFile).Trim()
|
|
834
|
+
if (-not $origin) {
|
|
835
|
+
$pattern = [string]$Cfg.UrlRegex
|
|
836
|
+
foreach ($file in @((Log-Path 'Cloud' 'stdout'), (Log-Path 'Cloud' 'stderr'))) {
|
|
837
|
+
$text = Read-TextFileSafe $file
|
|
838
|
+
$m = [regex]::Matches($text, $pattern)
|
|
839
|
+
if ($m.Count -gt 0) { $origin = $m[$m.Count - 1].Value }
|
|
840
|
+
}
|
|
841
|
+
if (-not $origin) { return $null }
|
|
842
|
+
Set-Content -LiteralPath $originFile -Value $origin -Encoding ASCII
|
|
843
|
+
}
|
|
844
|
+
$suffix = ''
|
|
845
|
+
if ($Cfg.ContainsKey('McpPathFile') -and -not [string]::IsNullOrWhiteSpace([string]$Cfg.McpPathFile)) {
|
|
846
|
+
$pathFile = [string]$Cfg.McpPathFile
|
|
847
|
+
if (-not (Test-Path -LiteralPath $pathFile -PathType Leaf)) { Fail "McpPathFile missing: $pathFile" }
|
|
848
|
+
$suffix = (Read-TextFileSafe $pathFile).Trim()
|
|
849
|
+
if ($suffix -and -not $suffix.StartsWith('/')) { Fail 'MCP path file must contain an absolute URL path beginning with slash' }
|
|
850
|
+
if ($suffix -match '[\r\n]') { Fail 'MCP path file contains newline data' }
|
|
851
|
+
}
|
|
852
|
+
return $origin.TrimEnd('/') + $suffix
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
function Wait-CurrentUrl($Cfg) {
|
|
856
|
+
$deadline = [DateTime]::UtcNow.AddSeconds([int]$Cfg.StartTimeoutSeconds)
|
|
857
|
+
do {
|
|
858
|
+
$cloud = Test-OwnedInstance 'Cloud'
|
|
859
|
+
if (-not $cloud.Alive) { return $null }
|
|
860
|
+
$url = Get-CurrentUrl $Cfg
|
|
861
|
+
if ($url) { return $url }
|
|
862
|
+
Start-Sleep -Milliseconds 250
|
|
863
|
+
} while ([DateTime]::UtcNow -lt $deadline)
|
|
864
|
+
return $null
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
function Invoke-CoreGate($Cfg, [string]$ScriptName, [string[]]$ExtraArguments = @()) {
|
|
868
|
+
$coreRoot = [string]$Cfg.CoreRoot
|
|
869
|
+
$script = Join-Path (Join-Path $coreRoot 'src') $ScriptName
|
|
870
|
+
if (-not (Test-Path -LiteralPath $script -PathType Leaf)) { Fail "core gate script missing: $script" }
|
|
871
|
+
$node = (Get-Command node.exe -ErrorAction Stop).Source
|
|
872
|
+
$stdout = Log-Path 'CoreGate' 'stdout'
|
|
873
|
+
$stderr = Log-Path 'CoreGate' 'stderr'
|
|
874
|
+
Remove-Item -LiteralPath $stdout,$stderr -Force -ErrorAction SilentlyContinue
|
|
875
|
+
$argv = @(('"{0}"' -f $script))
|
|
876
|
+
foreach ($arg in $ExtraArguments) {
|
|
877
|
+
if ($arg -match '[\r\n"]') { Fail 'core gate argument contains forbidden quoting/newline data' }
|
|
878
|
+
$argv += ('"{0}"' -f $arg)
|
|
879
|
+
}
|
|
880
|
+
$proc = Start-Process -FilePath $node -ArgumentList $argv -WorkingDirectory $coreRoot -Wait -PassThru -WindowStyle Hidden -RedirectStandardOutput $stdout -RedirectStandardError $stderr
|
|
881
|
+
if ($proc.ExitCode -ne 0) {
|
|
882
|
+
$detail = if (Test-Path -LiteralPath $stderr) { (Get-Content -LiteralPath $stderr -Raw -Encoding UTF8).Trim() } else { '' }
|
|
883
|
+
Fail "core gate $ScriptName failed exit=$($proc.ExitCode): $detail"
|
|
884
|
+
}
|
|
885
|
+
Write-Host (Get-Content -LiteralPath $stdout -Raw -Encoding UTF8)
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
function Start-Deadbyte($Cfg) {
|
|
889
|
+
$state = Get-ObservedState $Cfg
|
|
890
|
+
if ($state.State -in @('ONLINE','DEGRADED')) { return $state }
|
|
891
|
+
if ($state.State -in @('FAILED','STARTING')) { Fail "runtime is $($state.State); inspect status and reconcile verified owned components before restart" }
|
|
892
|
+
$cloudWasAlive = [bool]$state.Cloud.Alive
|
|
893
|
+
Assert-LocalReadyPortAvailable $Cfg
|
|
894
|
+
try {
|
|
895
|
+
Start-OwnedComponent 'Bridge' $Cfg.Bridge $Cfg | Out-Null
|
|
896
|
+
$localHealth = Wait-LocalOperational $Cfg
|
|
897
|
+
if ($null -eq $localHealth) { Fail 'bridge started but owned local readiness gate failed' }
|
|
898
|
+
Start-OwnedComponent 'Cloud' $Cfg.Cloud $Cfg | Out-Null
|
|
899
|
+
if (-not (Wait-CurrentUrl $Cfg)) { Fail 'public endpoint metadata did not become available before timeout' }
|
|
900
|
+
$final = Get-ObservedState $Cfg
|
|
901
|
+
if ($final.State -notin @('ONLINE','DEGRADED')) { Fail "start gate failed: observed $($final.State)" }
|
|
902
|
+
return $final
|
|
903
|
+
} catch {
|
|
904
|
+
try { Stop-OwnedComponent 'Bridge' } catch {}
|
|
905
|
+
if (-not $cloudWasAlive) { try { Stop-OwnedComponent 'Cloud' } catch {} }
|
|
906
|
+
throw
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
function Park-Deadbyte($Cfg) {
|
|
911
|
+
Disarm-Desktop $Cfg -Quiet
|
|
912
|
+
Stop-OwnedComponent 'Bridge'
|
|
913
|
+
$s = Get-ObservedState $Cfg
|
|
914
|
+
if ($s.State -notin @('PARKED','OFFLINE')) { Fail "park gate failed: observed $($s.State)" }
|
|
915
|
+
return $s
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
function Stop-Deadbyte($Cfg) {
|
|
919
|
+
Disarm-Desktop $Cfg -Quiet
|
|
920
|
+
Stop-OwnedComponent 'Bridge'
|
|
921
|
+
Stop-OwnedComponent 'Cloud'
|
|
922
|
+
Remove-Item -LiteralPath (Join-Path $RuntimeDir 'current-origin.txt') -Force -ErrorAction SilentlyContinue
|
|
923
|
+
$s = Get-ObservedState $Cfg
|
|
924
|
+
if ($s.State -ne 'OFFLINE') { Fail "stop gate failed: observed $($s.State)" }
|
|
925
|
+
return $s
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
function Assert-PolicyRootsAvailable($Policy, [string]$Plane) {
|
|
929
|
+
$rootIds = @($Policy.roots.PSObject.Properties.Name)
|
|
930
|
+
if ($rootIds.Count -lt 1) { Fail "$Plane policy requires at least one root" }
|
|
931
|
+
foreach ($rootId in $rootIds) {
|
|
932
|
+
$raw = [string]$Policy.roots.$rootId.path
|
|
933
|
+
if ([string]::IsNullOrWhiteSpace($raw)) { Fail "$Plane policy root '$rootId' path missing" }
|
|
934
|
+
try { $full = [System.IO.Path]::GetFullPath($raw) } catch { Fail "$Plane policy root '$rootId' path invalid" }
|
|
935
|
+
if (-not (Test-Path -LiteralPath $full -PathType Container)) { Fail "$Plane policy root '$rootId' missing: $full" }
|
|
936
|
+
}
|
|
937
|
+
return $rootIds
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
function Get-AuthorityEpochStatus($Cfg, [string]$Plane, [string]$GrantFile, [string]$GrantNonce) {
|
|
941
|
+
if ([string]::IsNullOrWhiteSpace($GrantNonce) -or $GrantNonce -cnotmatch '^[0-9a-f]{64}$') {
|
|
942
|
+
return [pscustomobject]@{ Trusted=$false; Reason='epoch-grant-nonce-invalid'; Seq=$null; Action=$null; EventSha256=$null }
|
|
943
|
+
}
|
|
944
|
+
try {
|
|
945
|
+
$verified = Invoke-AuthorityEpochCommand $Cfg $Plane 'status' $GrantFile
|
|
946
|
+
if (-not $verified.trusted) {
|
|
947
|
+
return [pscustomobject]@{ Trusted=$false; Reason=[string]$verified.reason; Seq=$verified.seq; Action=$verified.action; EventSha256=$verified.event_sha256 }
|
|
948
|
+
}
|
|
949
|
+
return [pscustomobject]@{ Trusted=$true; Reason=[string]$verified.reason; Seq=$verified.seq; Action=$verified.action; EventSha256=$verified.event_sha256 }
|
|
950
|
+
} catch {
|
|
951
|
+
return [pscustomobject]@{ Trusted=$false; Reason='authority-epoch-verifier-unavailable'; Seq=$null; Action=$null; EventSha256=$null }
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
function Invoke-AuthorityEpochCommand($Cfg, [string]$Plane, [string]$Action, [string]$GrantFile, [string]$UpdateId = '', [string]$ExpectedHead = '') {
|
|
956
|
+
$cli = Join-Path ([string]$Cfg.CoreRoot) 'src\authority-epoch-cli-r65.mjs'
|
|
957
|
+
if (-not (Test-Path -LiteralPath $cli -PathType Leaf)) { Fail "authority epoch CLI missing: $cli" }
|
|
958
|
+
$node = [string]$Cfg.Bridge.FilePath
|
|
959
|
+
if ([string]::IsNullOrWhiteSpace($node) -or -not (Test-Path -LiteralPath $node -PathType Leaf)) { Fail 'authority epoch Node executable unavailable' }
|
|
960
|
+
$policyFile = switch ($Plane) {
|
|
961
|
+
'process' { [string]$Cfg.ProcessPolicyFile }
|
|
962
|
+
'coding' { [string]$Cfg.CodingPolicyFile }
|
|
963
|
+
'autonomous' { [string]$Cfg.AutonomousPolicyFile }
|
|
964
|
+
'desktop' { [string]$Cfg.DesktopPolicyFile }
|
|
965
|
+
'host' { [string]$Cfg.HostPolicyFile }
|
|
966
|
+
default { Fail "authority epoch plane invalid: $Plane" }
|
|
967
|
+
}
|
|
968
|
+
if ([string]::IsNullOrWhiteSpace($policyFile) -or -not (Test-Path -LiteralPath $policyFile -PathType Leaf)) { Fail "authority epoch policy unavailable for $Plane" }
|
|
969
|
+
$args = @($cli, $Action, '--plane', $Plane, '--grant-file', $GrantFile, '--policy-file', $policyFile)
|
|
970
|
+
if ($Action -in @('transaction-revoke','transaction-restore')) {
|
|
971
|
+
if ([string]::IsNullOrWhiteSpace($UpdateId) -or $ExpectedHead -cnotmatch '^[0-9a-f]{64}$') { Fail 'authority epoch transaction binding invalid' }
|
|
972
|
+
$args += @('--update-id', $UpdateId, '--expected-head', $ExpectedHead)
|
|
973
|
+
}
|
|
974
|
+
$output = @(& $node @args)
|
|
975
|
+
$exit = $LASTEXITCODE
|
|
976
|
+
if ($exit -ne 0) { Fail "authority epoch command '$Action' failed for $Plane (exit=$exit)" }
|
|
977
|
+
try { return (($output -join [Environment]::NewLine) | ConvertFrom-Json) }
|
|
978
|
+
catch { Fail "authority epoch command '$Action' returned invalid JSON for $Plane" }
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
function Get-HostPolicyMetadata($Cfg) {
|
|
982
|
+
if ([string]::IsNullOrWhiteSpace([string]$Cfg.HostPolicyFile)) { Fail 'privileged host plane is not configured' }
|
|
983
|
+
$policyPath = [string]$Cfg.HostPolicyFile
|
|
984
|
+
try { $policy = Get-Content -LiteralPath $policyPath -Raw -Encoding UTF8 | ConvertFrom-Json } catch { Fail "invalid host policy JSON: $policyPath" }
|
|
985
|
+
if (@('deadbyte.host-policy.v1','deadbyte.host-policy.v2') -notcontains [string]$policy.schema -or $policy.enabled -ne $true) { Fail 'host policy schema/enabled mismatch' }
|
|
986
|
+
if ([string]::IsNullOrWhiteSpace([string]$policy.arm_file)) { Fail 'host policy arm_file missing' }
|
|
987
|
+
$armFile = [System.IO.Path]::GetFullPath([string]$policy.arm_file)
|
|
988
|
+
$runtimeReal = [System.IO.Path]::GetFullPath($RuntimeDir).TrimEnd('\') + '\'
|
|
989
|
+
if (-not $armFile.StartsWith($runtimeReal, [System.StringComparison]::OrdinalIgnoreCase)) { Fail 'host policy arm_file must live under controller runtime directory' }
|
|
990
|
+
Assert-PolicyRootsAvailable $policy 'host' | Out-Null
|
|
991
|
+
[pscustomobject]@{
|
|
992
|
+
Path = $policyPath
|
|
993
|
+
Sha256 = Get-ExecutableHash $policyPath
|
|
994
|
+
ArmFile = $armFile
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
function Get-HostArmStatus($Cfg) {
|
|
999
|
+
if ([string]::IsNullOrWhiteSpace([string]$Cfg.HostPolicyFile)) { return [pscustomobject]@{ Configured=$false; Armed=$false; Reason='no-policy' } }
|
|
1000
|
+
try { $meta = Get-HostPolicyMetadata $Cfg } catch { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason=$_.Exception.Message } }
|
|
1001
|
+
if (-not (Test-Path -LiteralPath $meta.ArmFile -PathType Leaf)) { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='disarmed' } }
|
|
1002
|
+
try { $arm = Get-Content -LiteralPath $meta.ArmFile -Raw -Encoding UTF8 | ConvertFrom-Json } catch { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='invalid-grant' } }
|
|
1003
|
+
if ($arm.schema -ne 'deadbyte.host-grant.v2') { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='grant-schema' } }
|
|
1004
|
+
if ([string]$arm.policy_sha256 -ne [string]$meta.Sha256) { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='policy-digest' } }
|
|
1005
|
+
if ([string]$arm.instance_nonce -cnotmatch '^[0-9a-f]{64}$') { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='nonce-invalid' } }
|
|
1006
|
+
$epoch = Get-AuthorityEpochStatus $Cfg 'host' $meta.ArmFile ([string]$arm.instance_nonce)
|
|
1007
|
+
if (-not $epoch.Trusted) { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason=[string]$epoch.Reason } }
|
|
1008
|
+
return [pscustomobject]@{ Configured=$true; Armed=$true; Reason='standing-grant'; EpochSeq=$epoch.Seq; EpochSha256=$epoch.EventSha256 }
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
function Arm-Host($Cfg) {
|
|
1012
|
+
$meta = Get-HostPolicyMetadata $Cfg
|
|
1013
|
+
Ensure-Directory $RuntimeDir
|
|
1014
|
+
$nonceBytes = New-Object byte[] 32
|
|
1015
|
+
$rng = [System.Security.Cryptography.RandomNumberGenerator]::Create()
|
|
1016
|
+
try { $rng.GetBytes($nonceBytes) } finally { $rng.Dispose() }
|
|
1017
|
+
$nonce = -join ($nonceBytes | ForEach-Object { $_.ToString('x2') })
|
|
1018
|
+
$now = [DateTime]::UtcNow
|
|
1019
|
+
$body = [ordered]@{
|
|
1020
|
+
schema = 'deadbyte.host-grant.v2'
|
|
1021
|
+
policy_sha256 = [string]$meta.Sha256
|
|
1022
|
+
instance_nonce = $nonce
|
|
1023
|
+
granted_at_utc = $now.ToString('o')
|
|
1024
|
+
}
|
|
1025
|
+
$tmp = $meta.ArmFile + '.tmp'
|
|
1026
|
+
$body | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath $tmp -Encoding UTF8
|
|
1027
|
+
Move-Item -LiteralPath $tmp -Destination $meta.ArmFile -Force
|
|
1028
|
+
try { Invoke-AuthorityEpochCommand $Cfg 'host' 'grant' $meta.ArmFile | Out-Null }
|
|
1029
|
+
catch {
|
|
1030
|
+
Remove-Item -LiteralPath $meta.ArmFile -Force -ErrorAction SilentlyContinue
|
|
1031
|
+
throw
|
|
1032
|
+
}
|
|
1033
|
+
Write-Host 'PRIVILEGED HOST PLANE ENABLED UNTIL EXPLICIT REVOKE'
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
function Disarm-Host($Cfg) {
|
|
1037
|
+
$meta = Get-HostPolicyMetadata $Cfg
|
|
1038
|
+
$epochError = $null
|
|
1039
|
+
if (Test-Path -LiteralPath $meta.ArmFile) {
|
|
1040
|
+
try { Invoke-AuthorityEpochCommand $Cfg 'host' 'user-revoke' $meta.ArmFile | Out-Null } catch { $epochError = $_.Exception }
|
|
1041
|
+
Remove-Item -LiteralPath $meta.ArmFile -Force -ErrorAction SilentlyContinue
|
|
1042
|
+
}
|
|
1043
|
+
if (Test-Path -LiteralPath $meta.ArmFile) { throw 'Host authority revocation failed: grant remains present' }
|
|
1044
|
+
if ($null -ne $epochError) { throw $epochError }
|
|
1045
|
+
Write-Host 'PRIVILEGED HOST PLANE DISARMED'
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
function Show-HostStatus($Cfg) {
|
|
1049
|
+
$h = Get-HostArmStatus $Cfg
|
|
1050
|
+
Write-Host ("Host plane : {0} [{1}]" -f $(if ($h.Armed) { 'ARMED' } elseif ($h.Configured) { 'DISARMED' } else { 'UNCONFIGURED' }), $h.Reason)
|
|
1051
|
+
return $h
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
function Get-ProcessPolicyMetadata($Cfg) {
|
|
1055
|
+
if ([string]::IsNullOrWhiteSpace([string]$Cfg.ProcessPolicyFile)) { Fail 'process plane is not configured' }
|
|
1056
|
+
$policyPath = [string]$Cfg.ProcessPolicyFile
|
|
1057
|
+
try { $policy = Get-Content -LiteralPath $policyPath -Raw -Encoding UTF8 | ConvertFrom-Json } catch { Fail "invalid process policy JSON: $policyPath" }
|
|
1058
|
+
if ($policy.schema -ne 'deadbyte.process-policy.v1' -or $policy.enabled -ne $true) { Fail 'process policy schema/enabled mismatch' }
|
|
1059
|
+
if ([string]::IsNullOrWhiteSpace([string]$policy.grant_file)) { Fail 'process policy grant_file missing' }
|
|
1060
|
+
$grantFile = [System.IO.Path]::GetFullPath([string]$policy.grant_file)
|
|
1061
|
+
$runtimeReal = [System.IO.Path]::GetFullPath($RuntimeDir).TrimEnd('\') + '\'
|
|
1062
|
+
if (-not $grantFile.StartsWith($runtimeReal, [System.StringComparison]::OrdinalIgnoreCase)) { Fail 'process policy grant_file must live under controller runtime directory' }
|
|
1063
|
+
$profileIds = @($policy.profiles.PSObject.Properties.Name)
|
|
1064
|
+
$rootIds = @(Assert-PolicyRootsAvailable $policy 'process')
|
|
1065
|
+
if ($profileIds.Count -lt 1 -or $rootIds.Count -lt 1) { Fail 'process policy requires profiles and roots' }
|
|
1066
|
+
[pscustomobject]@{ Path=$policyPath; Sha256=Get-ExecutableHash $policyPath; GrantFile=$grantFile; ProfileIds=$profileIds; RootIds=$rootIds }
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
function Get-ProcessGrantStatus($Cfg) {
|
|
1070
|
+
if ([string]::IsNullOrWhiteSpace([string]$Cfg.ProcessPolicyFile)) { return [pscustomobject]@{ Configured=$false; Armed=$false; Reason='no-policy' } }
|
|
1071
|
+
try { $meta = Get-ProcessPolicyMetadata $Cfg } catch { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason=$_.Exception.Message } }
|
|
1072
|
+
if (-not (Test-Path -LiteralPath $meta.GrantFile -PathType Leaf)) { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='disarmed' } }
|
|
1073
|
+
try { $grant = Get-Content -LiteralPath $meta.GrantFile -Raw -Encoding UTF8 | ConvertFrom-Json } catch { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='invalid-grant' } }
|
|
1074
|
+
if ($grant.schema -ne 'deadbyte.process-grant.v1') { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='grant-schema' } }
|
|
1075
|
+
if ([string]$grant.policy_sha256 -ne [string]$meta.Sha256) { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='policy-digest' } }
|
|
1076
|
+
if ([string]$grant.instance_nonce -cnotmatch '^[0-9a-f]{64}$') { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='nonce-invalid' } }
|
|
1077
|
+
$profiles = @($grant.profile_ids); $roots = @($grant.root_ids)
|
|
1078
|
+
if ($profiles.Count -lt 1 -or @($profiles | Where-Object { $_ -notin $meta.ProfileIds }).Count -gt 0) { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='profiles-invalid' } }
|
|
1079
|
+
if ($roots.Count -lt 1 -or @($roots | Where-Object { $_ -notin $meta.RootIds }).Count -gt 0) { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='roots-invalid' } }
|
|
1080
|
+
$epoch = Get-AuthorityEpochStatus $Cfg 'process' $meta.GrantFile ([string]$grant.instance_nonce)
|
|
1081
|
+
if (-not $epoch.Trusted) { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason=[string]$epoch.Reason } }
|
|
1082
|
+
return [pscustomobject]@{ Configured=$true; Armed=$true; Reason='standing-grant'; EpochSeq=$epoch.Seq; EpochSha256=$epoch.EventSha256 }
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
function Arm-Process($Cfg) {
|
|
1086
|
+
$meta = Get-ProcessPolicyMetadata $Cfg
|
|
1087
|
+
Ensure-Directory $RuntimeDir
|
|
1088
|
+
$nonceBytes = New-Object byte[] 32
|
|
1089
|
+
$rng = [System.Security.Cryptography.RandomNumberGenerator]::Create()
|
|
1090
|
+
try { $rng.GetBytes($nonceBytes) } finally { $rng.Dispose() }
|
|
1091
|
+
$nonce = -join ($nonceBytes | ForEach-Object { $_.ToString('x2') })
|
|
1092
|
+
$body = [ordered]@{
|
|
1093
|
+
schema = 'deadbyte.process-grant.v1'
|
|
1094
|
+
policy_sha256 = [string]$meta.Sha256
|
|
1095
|
+
instance_nonce = $nonce
|
|
1096
|
+
profile_ids = @($meta.ProfileIds)
|
|
1097
|
+
root_ids = @($meta.RootIds)
|
|
1098
|
+
granted_at_utc = [DateTime]::UtcNow.ToString('o')
|
|
1099
|
+
}
|
|
1100
|
+
$tmp = $meta.GrantFile + '.tmp'
|
|
1101
|
+
$body | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath $tmp -Encoding UTF8
|
|
1102
|
+
Move-Item -LiteralPath $tmp -Destination $meta.GrantFile -Force
|
|
1103
|
+
try { Invoke-AuthorityEpochCommand $Cfg 'process' 'grant' $meta.GrantFile | Out-Null }
|
|
1104
|
+
catch {
|
|
1105
|
+
Remove-Item -LiteralPath $meta.GrantFile -Force -ErrorAction SilentlyContinue
|
|
1106
|
+
throw
|
|
1107
|
+
}
|
|
1108
|
+
Write-Host 'PROCESS PLANE ENABLED UNTIL EXPLICIT REVOKE'
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
function Disarm-Process($Cfg) {
|
|
1112
|
+
$meta = Get-ProcessPolicyMetadata $Cfg
|
|
1113
|
+
$epochError = $null
|
|
1114
|
+
if (Test-Path -LiteralPath $meta.GrantFile -PathType Leaf) {
|
|
1115
|
+
try { Invoke-AuthorityEpochCommand $Cfg 'process' 'user-revoke' $meta.GrantFile | Out-Null } catch { $epochError = $_.Exception }
|
|
1116
|
+
Remove-Item -LiteralPath $meta.GrantFile -Force -ErrorAction SilentlyContinue
|
|
1117
|
+
}
|
|
1118
|
+
if (Test-Path -LiteralPath $meta.GrantFile) { throw 'Process authority revocation failed: grant remains present' }
|
|
1119
|
+
if ($null -ne $epochError) { throw $epochError }
|
|
1120
|
+
Write-Host 'PROCESS PLANE DISARMED'
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
function Show-ProcessStatus($Cfg) {
|
|
1124
|
+
$p = Get-ProcessGrantStatus $Cfg
|
|
1125
|
+
Write-Host ("Process plane : {0} [{1}]" -f $(if ($p.Armed) { 'ARMED' } elseif ($p.Configured) { 'DISARMED' } else { 'UNCONFIGURED' }), $p.Reason)
|
|
1126
|
+
return $p
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
function Read-ControllerPolicySnapshot([string]$PolicyPath, [string]$Kind) {
|
|
1130
|
+
try {
|
|
1131
|
+
$bytes = [System.IO.File]::ReadAllBytes($PolicyPath)
|
|
1132
|
+
$decoder = New-Object System.Text.UTF8Encoding($false, $true)
|
|
1133
|
+
$text = $decoder.GetString($bytes)
|
|
1134
|
+
if ($text.Length -gt 0 -and $text[0] -eq [char]0xFEFF) { $text = $text.Substring(1) }
|
|
1135
|
+
$policy = $text | ConvertFrom-Json
|
|
1136
|
+
} catch { Fail "invalid $Kind policy JSON: $PolicyPath" }
|
|
1137
|
+
$sha = [System.Security.Cryptography.SHA256]::Create()
|
|
1138
|
+
try { $digest = ([System.BitConverter]::ToString($sha.ComputeHash($bytes))).Replace('-', '').ToLowerInvariant() }
|
|
1139
|
+
finally { $sha.Dispose() }
|
|
1140
|
+
return [pscustomobject]@{ Policy=$policy; Sha256=$digest }
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
function Get-CodingPolicyMetadata($Cfg) {
|
|
1144
|
+
if ([string]::IsNullOrWhiteSpace([string]$Cfg.CodingPolicyFile)) { Fail 'coding plane is not configured' }
|
|
1145
|
+
$policyPath = [string]$Cfg.CodingPolicyFile
|
|
1146
|
+
$snapshot = Read-ControllerPolicySnapshot $policyPath 'coding'
|
|
1147
|
+
$policy = $snapshot.Policy
|
|
1148
|
+
if ($policy.schema -ne 'deadbyte.coding-policy.v1' -or $policy.enabled -ne $true) { Fail 'coding policy schema/enabled mismatch' }
|
|
1149
|
+
if ([string]::IsNullOrWhiteSpace([string]$policy.lease_file)) { Fail 'coding policy lease_file missing' }
|
|
1150
|
+
$leaseFile = [System.IO.Path]::GetFullPath([string]$policy.lease_file)
|
|
1151
|
+
$runtimeReal = [System.IO.Path]::GetFullPath($RuntimeDir).TrimEnd('\') + '\'
|
|
1152
|
+
if (-not $leaseFile.StartsWith($runtimeReal, [System.StringComparison]::OrdinalIgnoreCase)) { Fail 'coding policy lease_file must live under controller runtime directory' }
|
|
1153
|
+
$rootIds = @(Assert-PolicyRootsAvailable $policy 'coding')
|
|
1154
|
+
$rootPaths = @{}
|
|
1155
|
+
$rootAccess = @{}
|
|
1156
|
+
foreach ($rootId in $rootIds) {
|
|
1157
|
+
$root = $policy.roots.$rootId
|
|
1158
|
+
$rootPaths[$rootId] = [System.IO.Path]::GetFullPath([string]$root.path)
|
|
1159
|
+
$rootAccess[$rootId] = [pscustomobject]@{ Read=($root.read -eq $true); Write=($root.write -eq $true); Exec=($root.exec -eq $true) }
|
|
1160
|
+
}
|
|
1161
|
+
[pscustomobject]@{ Path=$policyPath; Sha256=$snapshot.Sha256; LeaseFile=$leaseFile; RootIds=$rootIds; RootPaths=$rootPaths; RootAccess=$rootAccess }
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
function Get-CodingLeaseStatus($Cfg) {
|
|
1165
|
+
if ([string]::IsNullOrWhiteSpace([string]$Cfg.CodingPolicyFile)) { return [pscustomobject]@{ Configured=$false; Armed=$false; Reason='no-policy' } }
|
|
1166
|
+
try { $meta = Get-CodingPolicyMetadata $Cfg } catch { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason=$_.Exception.Message } }
|
|
1167
|
+
if (-not (Test-Path -LiteralPath $meta.LeaseFile -PathType Leaf)) { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='disarmed' } }
|
|
1168
|
+
try { $lease = Get-Content -LiteralPath $meta.LeaseFile -Raw -Encoding UTF8 | ConvertFrom-Json } catch { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='invalid-grant' } }
|
|
1169
|
+
if ($lease.schema -ne 'deadbyte.coding-grant.v2') { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='grant-schema' } }
|
|
1170
|
+
if ([string]$lease.policy_sha256 -ne [string]$meta.Sha256) { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='policy-digest' } }
|
|
1171
|
+
if ([string]$lease.instance_nonce -cnotmatch '^[0-9a-f]{64}$') { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='nonce-invalid' } }
|
|
1172
|
+
$roots = @($lease.root_ids)
|
|
1173
|
+
if ($roots.Count -lt 1 -or @($roots | Where-Object { $_ -notin $meta.RootIds }).Count -gt 0) { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='roots-invalid' } }
|
|
1174
|
+
$epoch = Get-AuthorityEpochStatus $Cfg 'coding' $meta.LeaseFile ([string]$lease.instance_nonce)
|
|
1175
|
+
if (-not $epoch.Trusted) { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason=[string]$epoch.Reason } }
|
|
1176
|
+
return [pscustomobject]@{ Configured=$true; Armed=$true; Reason='standing-grant'; RootIds=$roots; EpochSeq=$epoch.Seq; EpochSha256=$epoch.EventSha256 }
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
function Arm-Coding($Cfg) {
|
|
1180
|
+
$meta = Get-CodingPolicyMetadata $Cfg
|
|
1181
|
+
Ensure-Directory $RuntimeDir
|
|
1182
|
+
$nonceBytes = New-Object byte[] 32
|
|
1183
|
+
$rng = [System.Security.Cryptography.RandomNumberGenerator]::Create()
|
|
1184
|
+
try { $rng.GetBytes($nonceBytes) } finally { $rng.Dispose() }
|
|
1185
|
+
$nonce = -join ($nonceBytes | ForEach-Object { $_.ToString('x2') })
|
|
1186
|
+
$now = [DateTime]::UtcNow
|
|
1187
|
+
$body = [ordered]@{
|
|
1188
|
+
schema = 'deadbyte.coding-grant.v2'
|
|
1189
|
+
policy_sha256 = [string]$meta.Sha256
|
|
1190
|
+
instance_nonce = $nonce
|
|
1191
|
+
root_ids = @($meta.RootIds)
|
|
1192
|
+
granted_at_utc = $now.ToString('o')
|
|
1193
|
+
}
|
|
1194
|
+
$tmp = $meta.LeaseFile + '.tmp'
|
|
1195
|
+
$body | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath $tmp -Encoding UTF8
|
|
1196
|
+
Move-Item -LiteralPath $tmp -Destination $meta.LeaseFile -Force
|
|
1197
|
+
try { Invoke-AuthorityEpochCommand $Cfg 'coding' 'grant' $meta.LeaseFile | Out-Null }
|
|
1198
|
+
catch {
|
|
1199
|
+
Remove-Item -LiteralPath $meta.LeaseFile -Force -ErrorAction SilentlyContinue
|
|
1200
|
+
throw
|
|
1201
|
+
}
|
|
1202
|
+
Write-Host 'CODING PLANE ENABLED UNTIL EXPLICIT REVOKE'
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
function Disarm-Coding($Cfg) {
|
|
1206
|
+
$meta = Get-CodingPolicyMetadata $Cfg
|
|
1207
|
+
$epochError = $null
|
|
1208
|
+
if (Test-Path -LiteralPath $meta.LeaseFile -PathType Leaf) {
|
|
1209
|
+
try { Invoke-AuthorityEpochCommand $Cfg 'coding' 'user-revoke' $meta.LeaseFile | Out-Null } catch { $epochError = $_.Exception }
|
|
1210
|
+
Remove-Item -LiteralPath $meta.LeaseFile -Force -ErrorAction SilentlyContinue
|
|
1211
|
+
}
|
|
1212
|
+
try { Disarm-Autonomous $Cfg -Quiet } catch {
|
|
1213
|
+
if ($null -eq $epochError) { $epochError = $_.Exception }
|
|
1214
|
+
else { Write-Warning "autonomous lease cleanup failed after coding revocation: $($_.Exception.Message)" }
|
|
1215
|
+
}
|
|
1216
|
+
if (Test-Path -LiteralPath $meta.LeaseFile) { throw 'Coding authority revocation failed: grant remains present' }
|
|
1217
|
+
if ($null -ne $epochError) { throw $epochError }
|
|
1218
|
+
Write-Host 'CODING PLANE DISARMED; AUTONOMOUS AUTHORITY REVOKED'
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
function Show-CodingStatus($Cfg) {
|
|
1222
|
+
$c = Get-CodingLeaseStatus $Cfg
|
|
1223
|
+
Write-Host ("Coding plane : {0} [{1}]" -f $(if ($c.Armed) { 'ARMED' } elseif ($c.Configured) { 'DISARMED' } else { 'UNCONFIGURED' }), $c.Reason)
|
|
1224
|
+
return $c
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
function Get-AutonomousPolicyMetadata($Cfg) {
|
|
1228
|
+
if ([string]::IsNullOrWhiteSpace([string]$Cfg.AutonomousPolicyFile)) { Fail 'autonomous plane is not configured' }
|
|
1229
|
+
$policyPath = [string]$Cfg.AutonomousPolicyFile
|
|
1230
|
+
$snapshot = Read-ControllerPolicySnapshot $policyPath 'autonomous'
|
|
1231
|
+
$policy = $snapshot.Policy
|
|
1232
|
+
if ($policy.schema -ne 'deadbyte.autonomous-policy.v1' -or $policy.enabled -ne $true) { Fail 'autonomous policy schema/enabled mismatch' }
|
|
1233
|
+
if ([string]::IsNullOrWhiteSpace([string]$policy.lease_file)) { Fail 'autonomous policy lease_file missing' }
|
|
1234
|
+
$leaseFile = [System.IO.Path]::GetFullPath([string]$policy.lease_file)
|
|
1235
|
+
$runtimeReal = [System.IO.Path]::GetFullPath($RuntimeDir).TrimEnd('\') + '\'
|
|
1236
|
+
if (-not $leaseFile.StartsWith($runtimeReal, [System.StringComparison]::OrdinalIgnoreCase)) { Fail 'autonomous policy lease_file must live under controller runtime directory' }
|
|
1237
|
+
$codingMeta = Get-CodingPolicyMetadata $Cfg
|
|
1238
|
+
if ([string]$policy.coding_policy_sha256 -ne [string]$codingMeta.Sha256) { Fail 'autonomous policy coding_policy_sha256 mismatch' }
|
|
1239
|
+
$rootIds = @($policy.root_ids)
|
|
1240
|
+
if ($rootIds.Count -lt 1) { Fail 'autonomous policy requires at least one root' }
|
|
1241
|
+
foreach ($rootId in $rootIds) {
|
|
1242
|
+
if ([string]$rootId -cnotin @($codingMeta.RootIds)) { Fail "autonomous policy root '$rootId' outside coding policy" }
|
|
1243
|
+
$rootPath = [string]$codingMeta.RootPaths[[string]$rootId]
|
|
1244
|
+
if ([string]::IsNullOrWhiteSpace($rootPath)) { Fail "autonomous policy root '$rootId' path missing" }
|
|
1245
|
+
$fullRoot = [System.IO.Path]::GetFullPath($rootPath).TrimEnd('\')
|
|
1246
|
+
$volumeRoot = [System.IO.Path]::GetPathRoot($rootPath).TrimEnd('\')
|
|
1247
|
+
if ($fullRoot.Equals($volumeRoot, [System.StringComparison]::OrdinalIgnoreCase)) { Fail 'autonomous bootstrap requires a project-scoped root' }
|
|
1248
|
+
try { $rootItem = Get-Item -LiteralPath $rootPath -Force -ErrorAction Stop }
|
|
1249
|
+
catch { Fail "autonomous policy root '$rootId' must be a real project directory" }
|
|
1250
|
+
if (-not $rootItem.PSIsContainer) { Fail "autonomous policy root '$rootId' must be a real project directory" }
|
|
1251
|
+
$ancestor = [System.IO.DirectoryInfo]$rootItem
|
|
1252
|
+
while ($null -ne $ancestor) {
|
|
1253
|
+
if (($ancestor.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0) { Fail "autonomous policy root '$rootId' must be a real project directory" }
|
|
1254
|
+
$ancestor = $ancestor.Parent
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
if ($null -eq $codingMeta.RootAccess) { Fail 'coding policy root access metadata unavailable' }
|
|
1258
|
+
foreach ($codingRootId in @($codingMeta.RootIds)) {
|
|
1259
|
+
$access = $codingMeta.RootAccess[[string]$codingRootId]
|
|
1260
|
+
if ($null -eq $access) { Fail "coding policy root '$codingRootId' access metadata unavailable" }
|
|
1261
|
+
if ($access.Read -eq $true -or $access.Write -eq $true -or $access.Exec -eq $true) {
|
|
1262
|
+
$candidate = [string]$codingMeta.RootPaths[[string]$codingRootId]
|
|
1263
|
+
if ([string]::IsNullOrWhiteSpace($candidate)) { Fail "coding policy root '$codingRootId' path missing" }
|
|
1264
|
+
$candidateRoot = [System.IO.Path]::GetFullPath($candidate).TrimEnd('\')
|
|
1265
|
+
$filesystemRoot = [System.IO.Path]::GetPathRoot($candidate).TrimEnd('\')
|
|
1266
|
+
if ($candidateRoot.Equals($filesystemRoot, [System.StringComparison]::OrdinalIgnoreCase)) { Fail 'coding policy contains a broad filesystem root' }
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
[pscustomobject]@{ Path=$policyPath; Sha256=$snapshot.Sha256; LeaseFile=$leaseFile; RootIds=$rootIds; CodingPolicySha256=$codingMeta.Sha256 }
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
function Get-AutonomousLeaseStatus($Cfg) {
|
|
1273
|
+
if ([string]::IsNullOrWhiteSpace([string]$Cfg.AutonomousPolicyFile)) { return [pscustomobject]@{ Configured=$false; Armed=$false; Reason='no-policy' } }
|
|
1274
|
+
try { $meta = Get-AutonomousPolicyMetadata $Cfg } catch { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason=$_.Exception.Message } }
|
|
1275
|
+
$coding = Get-CodingLeaseStatus $Cfg
|
|
1276
|
+
if (-not $coding.Armed) { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='coding-disarmed' } }
|
|
1277
|
+
if (-not (Test-Path -LiteralPath $meta.LeaseFile -PathType Leaf)) { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='disarmed' } }
|
|
1278
|
+
try { $lease = Get-Content -LiteralPath $meta.LeaseFile -Raw -Encoding UTF8 | ConvertFrom-Json } catch { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='invalid-grant' } }
|
|
1279
|
+
if ($lease.schema -ne 'deadbyte.autonomous-grant.v2') { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='grant-schema' } }
|
|
1280
|
+
if ([string]$lease.policy_sha256 -ne [string]$meta.Sha256) { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='policy-digest' } }
|
|
1281
|
+
if ([string]$lease.coding_policy_sha256 -ne [string]$meta.CodingPolicySha256) { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='coding-policy-digest' } }
|
|
1282
|
+
if ([string]$lease.instance_nonce -cnotmatch '^[0-9a-f]{64}$') { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='nonce-invalid' } }
|
|
1283
|
+
$roots = @($lease.root_ids)
|
|
1284
|
+
if ($roots.Count -lt 1 -or @($roots | Where-Object { $_ -notin $meta.RootIds }).Count -gt 0) { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='roots-invalid' } }
|
|
1285
|
+
if (@($roots | Where-Object { $_ -cnotin @($coding.RootIds) }).Count -gt 0) { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='coding-roots' } }
|
|
1286
|
+
# R70 optional owner-bound expiry for v2 signed standing grants.
|
|
1287
|
+
# Legacy grants without an expiry field remain subject to explicit revoke.
|
|
1288
|
+
if ($null -ne $lease.PSObject.Properties['expires_at_utc']) {
|
|
1289
|
+
$issuedText = [string]$lease.granted_at_utc
|
|
1290
|
+
$expiryText = [string]$lease.expires_at_utc
|
|
1291
|
+
$timePattern = '^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d\.\d{3,7}Z$'
|
|
1292
|
+
if ($issuedText -cnotmatch $timePattern -or $expiryText -cnotmatch $timePattern) {
|
|
1293
|
+
return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='expiry-invalid' }
|
|
1294
|
+
}
|
|
1295
|
+
try {
|
|
1296
|
+
$issuedAt = [DateTimeOffset]::Parse($issuedText,[System.Globalization.CultureInfo]::InvariantCulture,[System.Globalization.DateTimeStyles]::RoundtripKind)
|
|
1297
|
+
$expiresAt = [DateTimeOffset]::Parse($expiryText,[System.Globalization.CultureInfo]::InvariantCulture,[System.Globalization.DateTimeStyles]::RoundtripKind)
|
|
1298
|
+
} catch {
|
|
1299
|
+
return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='expiry-invalid' }
|
|
1300
|
+
}
|
|
1301
|
+
$duration = ($expiresAt - $issuedAt).TotalMilliseconds
|
|
1302
|
+
if ($duration -le 0 -or $duration -gt 3600000) {
|
|
1303
|
+
return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='expiry-window-invalid' }
|
|
1304
|
+
}
|
|
1305
|
+
if ([DateTimeOffset]::UtcNow -lt $issuedAt) {
|
|
1306
|
+
return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='not-yet-valid' }
|
|
1307
|
+
}
|
|
1308
|
+
if ([DateTimeOffset]::UtcNow -ge $expiresAt) {
|
|
1309
|
+
return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='expired' }
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
$epoch = Get-AuthorityEpochStatus $Cfg 'autonomous' $meta.LeaseFile ([string]$lease.instance_nonce)
|
|
1313
|
+
if (-not $epoch.Trusted) { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason=[string]$epoch.Reason } }
|
|
1314
|
+
return [pscustomobject]@{ Configured=$true; Armed=$true; Reason='standing-grant'; EpochSeq=$epoch.Seq; EpochSha256=$epoch.EventSha256 }
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
function Arm-Autonomous($Cfg) {
|
|
1318
|
+
$coding = Get-CodingLeaseStatus $Cfg
|
|
1319
|
+
if (-not $coding.Armed) { Fail 'autonomous plane requires an active coding lease; run armcoding first' }
|
|
1320
|
+
$meta = Get-AutonomousPolicyMetadata $Cfg
|
|
1321
|
+
if (@($meta.RootIds | Where-Object { $_ -cnotin @($coding.RootIds) }).Count -gt 0) { Fail 'autonomous roots exceed active coding grant' }
|
|
1322
|
+
# R70 migration fence: updater approval is NOT controller-side grant approval.
|
|
1323
|
+
# Until independent owner enrollment is verified at grant issuance, only
|
|
1324
|
+
# the byte-identical pre-R70 known-good Autonomous policy can be armed.
|
|
1325
|
+
# Root labels and self-asserted approval flags are never consent evidence.
|
|
1326
|
+
$legacyOwnerPolicySha256 = '6274492644c303b38117541cc22a59e88e6ff5fdef7051a5d57ad336f45765f5'
|
|
1327
|
+
$selectedRoots = @($meta.RootIds)
|
|
1328
|
+
if ([string]$meta.Sha256 -cne $legacyOwnerPolicySha256 -or $selectedRoots.Count -ne 1 -or [string]$selectedRoots[0] -cne 'core') {
|
|
1329
|
+
Fail 'R70_OWNER_ENROLLMENT_REQUIRED: project/successor Autonomous grant needs independent owner authorization before arm'
|
|
1330
|
+
}
|
|
1331
|
+
Ensure-Directory $RuntimeDir
|
|
1332
|
+
$nonceBytes = New-Object byte[] 32
|
|
1333
|
+
$rng = [System.Security.Cryptography.RandomNumberGenerator]::Create()
|
|
1334
|
+
try { $rng.GetBytes($nonceBytes) } finally { $rng.Dispose() }
|
|
1335
|
+
$nonce = -join ($nonceBytes | ForEach-Object { $_.ToString('x2') })
|
|
1336
|
+
$now = [DateTime]::UtcNow
|
|
1337
|
+
$body = [ordered]@{ schema='deadbyte.autonomous-grant.v2'; policy_sha256=[string]$meta.Sha256; coding_policy_sha256=[string]$meta.CodingPolicySha256; instance_nonce=$nonce; root_ids=@($meta.RootIds); granted_at_utc=$now.ToString('o'); expires_at_utc=$now.AddHours(1).ToString('o') }
|
|
1338
|
+
$tmp = $meta.LeaseFile + '.tmp'
|
|
1339
|
+
$body | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath $tmp -Encoding UTF8
|
|
1340
|
+
Move-Item -LiteralPath $tmp -Destination $meta.LeaseFile -Force
|
|
1341
|
+
try { Invoke-AuthorityEpochCommand $Cfg 'autonomous' 'grant' $meta.LeaseFile | Out-Null }
|
|
1342
|
+
catch {
|
|
1343
|
+
Remove-Item -LiteralPath $meta.LeaseFile -Force -ErrorAction SilentlyContinue
|
|
1344
|
+
throw
|
|
1345
|
+
}
|
|
1346
|
+
Write-Host 'AUTONOMOUS PLANE ENABLED UNTIL EXPLICIT REVOKE'
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
function Disarm-Autonomous($Cfg, [switch]$Quiet) {
|
|
1350
|
+
if ([string]::IsNullOrWhiteSpace([string]$Cfg.AutonomousPolicyFile)) { return }
|
|
1351
|
+
$meta = Get-AutonomousPolicyMetadata $Cfg
|
|
1352
|
+
$epochError = $null
|
|
1353
|
+
if (Test-Path -LiteralPath $meta.LeaseFile -PathType Leaf) {
|
|
1354
|
+
try { Invoke-AuthorityEpochCommand $Cfg 'autonomous' 'user-revoke' $meta.LeaseFile | Out-Null } catch { $epochError = $_.Exception }
|
|
1355
|
+
Remove-Item -LiteralPath $meta.LeaseFile -Force -ErrorAction SilentlyContinue
|
|
1356
|
+
}
|
|
1357
|
+
if (Test-Path -LiteralPath $meta.LeaseFile) { throw 'Autonomous authority revocation failed: grant remains present' }
|
|
1358
|
+
if ($null -ne $epochError) { throw $epochError }
|
|
1359
|
+
if (-not $Quiet) { Write-Host 'AUTONOMOUS PLANE DISARMED' }
|
|
1360
|
+
}
|
|
1361
|
+
|
|
1362
|
+
function Show-AutonomousStatus($Cfg) {
|
|
1363
|
+
$a = Get-AutonomousLeaseStatus $Cfg
|
|
1364
|
+
Write-Host ("Autonomous : {0} [{1}]" -f $(if ($a.Armed) { 'ARMED' } elseif ($a.Configured) { 'DISARMED' } else { 'UNCONFIGURED' }), $a.Reason)
|
|
1365
|
+
return $a
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
function Get-DesktopPolicyMetadata($Cfg) {
|
|
1369
|
+
if ([string]::IsNullOrWhiteSpace([string]$Cfg.DesktopPolicyFile)) { Fail 'desktop plane is not configured' }
|
|
1370
|
+
$policyPath = [string]$Cfg.DesktopPolicyFile
|
|
1371
|
+
try { $policy = Get-Content -LiteralPath $policyPath -Raw -Encoding UTF8 | ConvertFrom-Json } catch { Fail "invalid desktop policy JSON: $policyPath" }
|
|
1372
|
+
if ($policy.schema -ne 'deadbyte.desktop-policy.v1' -or $policy.enabled -ne $true) { Fail 'desktop policy schema/enabled mismatch' }
|
|
1373
|
+
if ([string]::IsNullOrWhiteSpace([string]$policy.grant_file)) { Fail 'desktop policy grant_file missing' }
|
|
1374
|
+
if ([string]::IsNullOrWhiteSpace([string]$policy.evidence_dir)) { Fail 'desktop policy evidence_dir missing' }
|
|
1375
|
+
$grantFile = [System.IO.Path]::GetFullPath([string]$policy.grant_file)
|
|
1376
|
+
$evidenceDir = [System.IO.Path]::GetFullPath([string]$policy.evidence_dir)
|
|
1377
|
+
$runtimeReal = [System.IO.Path]::GetFullPath($RuntimeDir).TrimEnd('\') + '\'
|
|
1378
|
+
if (-not $grantFile.StartsWith($runtimeReal, [System.StringComparison]::OrdinalIgnoreCase)) { Fail 'desktop policy grant_file must live under controller runtime directory' }
|
|
1379
|
+
if (-not ($evidenceDir.TrimEnd('\') + '\').StartsWith($runtimeReal, [System.StringComparison]::OrdinalIgnoreCase)) { Fail 'desktop policy evidence_dir must live under controller runtime directory' }
|
|
1380
|
+
$knownActions = @('pointer_move','click_primary','click_secondary','double_click_primary','scroll','key_chord','text_input')
|
|
1381
|
+
$actionKinds = @($policy.allowed_actions)
|
|
1382
|
+
$uniqueActions = @($actionKinds | Sort-Object -Unique)
|
|
1383
|
+
if ($actionKinds.Count -lt 1 -or $actionKinds.Count -ne $uniqueActions.Count -or @($actionKinds | Where-Object { $_ -notin $knownActions }).Count -gt 0) { Fail 'desktop policy allowed_actions invalid' }
|
|
1384
|
+
if (-not $policy.observer -or [string]::IsNullOrWhiteSpace([string]$policy.observer.executable) -or [string]$policy.observer.sha256 -cnotmatch '^[0-9a-f]{64}$') { Fail 'desktop observer metadata invalid' }
|
|
1385
|
+
if (-not $policy.input -or [string]::IsNullOrWhiteSpace([string]$policy.input.executable) -or [string]$policy.input.sha256 -cnotmatch '^[0-9a-f]{64}$') { Fail 'desktop input metadata invalid' }
|
|
1386
|
+
if (-not $policy.grounder -or [string]::IsNullOrWhiteSpace([string]$policy.grounder.executable) -or [string]$policy.grounder.sha256 -cnotmatch '^[0-9a-f]{64}$') { Fail 'desktop grounder metadata invalid' }
|
|
1387
|
+
$observerPath = [System.IO.Path]::GetFullPath([string]$policy.observer.executable)
|
|
1388
|
+
$inputPath = [System.IO.Path]::GetFullPath([string]$policy.input.executable)
|
|
1389
|
+
$grounderPath = [System.IO.Path]::GetFullPath([string]$policy.grounder.executable)
|
|
1390
|
+
foreach ($provider in @(
|
|
1391
|
+
[pscustomobject]@{ Name='observer'; Path=$observerPath; Sha256=([string]$policy.observer.sha256).ToLowerInvariant() },
|
|
1392
|
+
[pscustomobject]@{ Name='input'; Path=$inputPath; Sha256=([string]$policy.input.sha256).ToLowerInvariant() },
|
|
1393
|
+
[pscustomobject]@{ Name='grounder'; Path=$grounderPath; Sha256=([string]$policy.grounder.sha256).ToLowerInvariant() }
|
|
1394
|
+
)) {
|
|
1395
|
+
if (-not (Test-Path -LiteralPath $provider.Path -PathType Leaf)) { Fail "desktop $($provider.Name) executable missing: $($provider.Path)" }
|
|
1396
|
+
$item = Get-Item -LiteralPath $provider.Path -Force
|
|
1397
|
+
if (($item.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0) { Fail "desktop $($provider.Name) executable must not be a reparse point" }
|
|
1398
|
+
$actual = Get-ExecutableHash $provider.Path
|
|
1399
|
+
if ($actual -ne $provider.Sha256) { Fail "desktop $($provider.Name) executable SHA-256 mismatch: expected $($provider.Sha256), found $actual" }
|
|
1400
|
+
}
|
|
1401
|
+
[pscustomobject]@{
|
|
1402
|
+
Path=$policyPath; Sha256=Get-ExecutableHash $policyPath; GrantFile=$grantFile; EvidenceDir=$evidenceDir;
|
|
1403
|
+
ActionKinds=$actionKinds; ObserverPath=$observerPath; ObserverSha256=([string]$policy.observer.sha256).ToLowerInvariant();
|
|
1404
|
+
InputPath=$inputPath; InputSha256=([string]$policy.input.sha256).ToLowerInvariant();
|
|
1405
|
+
GrounderPath=$grounderPath; GrounderSha256=([string]$policy.grounder.sha256).ToLowerInvariant()
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
function Get-DesktopGrantFileForRevoke($Cfg) {
|
|
1410
|
+
if ([string]::IsNullOrWhiteSpace([string]$Cfg.DesktopPolicyFile)) { return '' }
|
|
1411
|
+
$policyPath = [string]$Cfg.DesktopPolicyFile
|
|
1412
|
+
try { $policy = Get-Content -LiteralPath $policyPath -Raw -Encoding UTF8 | ConvertFrom-Json } catch { Fail "invalid desktop policy JSON during revoke: $policyPath" }
|
|
1413
|
+
if ([string]::IsNullOrWhiteSpace([string]$policy.grant_file)) { Fail 'desktop policy grant_file missing during revoke' }
|
|
1414
|
+
$grantFile = [System.IO.Path]::GetFullPath([string]$policy.grant_file)
|
|
1415
|
+
$runtimeReal = [System.IO.Path]::GetFullPath($RuntimeDir).TrimEnd('\') + '\'
|
|
1416
|
+
if (-not $grantFile.StartsWith($runtimeReal, [System.StringComparison]::OrdinalIgnoreCase)) { Fail 'desktop policy grant_file must live under controller runtime directory' }
|
|
1417
|
+
return $grantFile
|
|
1418
|
+
}
|
|
1419
|
+
|
|
1420
|
+
function Get-DesktopGrantStatus($Cfg) {
|
|
1421
|
+
if ([string]::IsNullOrWhiteSpace([string]$Cfg.DesktopPolicyFile)) { return [pscustomobject]@{ Configured=$false; Armed=$false; Reason='no-policy'; ActionKinds=@() } }
|
|
1422
|
+
try { $meta = Get-DesktopPolicyMetadata $Cfg } catch { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason=$_.Exception.Message; ActionKinds=@() } }
|
|
1423
|
+
if (-not (Test-Path -LiteralPath $meta.GrantFile -PathType Leaf)) { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='disarmed'; ActionKinds=@() } }
|
|
1424
|
+
try { $grant = Get-Content -LiteralPath $meta.GrantFile -Raw -Encoding UTF8 | ConvertFrom-Json } catch { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='invalid-grant'; ActionKinds=@() } }
|
|
1425
|
+
if ($grant.schema -ne 'deadbyte.desktop-grant.v1') { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='grant-schema'; ActionKinds=@() } }
|
|
1426
|
+
if ([string]$grant.policy_sha256 -ne [string]$meta.Sha256) { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='policy-digest'; ActionKinds=@() } }
|
|
1427
|
+
if ([string]$grant.instance_nonce -cnotmatch '^[0-9a-f]{64}$') { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='nonce-invalid'; ActionKinds=@() } }
|
|
1428
|
+
$actions = @($grant.action_kinds)
|
|
1429
|
+
$unique = @($actions | Sort-Object -Unique)
|
|
1430
|
+
if ($actions.Count -lt 1 -or $actions.Count -ne $unique.Count) { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='actions-invalid'; ActionKinds=@() } }
|
|
1431
|
+
if (@($actions | Where-Object { $_ -notin $meta.ActionKinds }).Count -gt 0) { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason='actions-outside-policy'; ActionKinds=@() } }
|
|
1432
|
+
$epoch = Get-AuthorityEpochStatus $Cfg 'desktop' $meta.GrantFile ([string]$grant.instance_nonce)
|
|
1433
|
+
if (-not $epoch.Trusted) { return [pscustomobject]@{ Configured=$true; Armed=$false; Reason=[string]$epoch.Reason; ActionKinds=$actions } }
|
|
1434
|
+
return [pscustomobject]@{ Configured=$true; Armed=$true; Reason='standing-grant'; ActionKinds=$actions; EpochSeq=$epoch.Seq; EpochSha256=$epoch.EventSha256 }
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
function Arm-Desktop($Cfg) {
|
|
1438
|
+
$meta = Get-DesktopPolicyMetadata $Cfg
|
|
1439
|
+
Ensure-Directory $RuntimeDir
|
|
1440
|
+
Ensure-Directory $meta.EvidenceDir
|
|
1441
|
+
$nonceBytes = New-Object byte[] 32
|
|
1442
|
+
$rng = [System.Security.Cryptography.RandomNumberGenerator]::Create()
|
|
1443
|
+
try { $rng.GetBytes($nonceBytes) } finally { $rng.Dispose() }
|
|
1444
|
+
$nonce = -join ($nonceBytes | ForEach-Object { $_.ToString('x2') })
|
|
1445
|
+
$body = [ordered]@{
|
|
1446
|
+
schema = 'deadbyte.desktop-grant.v1'
|
|
1447
|
+
policy_sha256 = [string]$meta.Sha256
|
|
1448
|
+
instance_nonce = $nonce
|
|
1449
|
+
action_kinds = @($meta.ActionKinds)
|
|
1450
|
+
granted_at_utc = [DateTime]::UtcNow.ToString('o')
|
|
1451
|
+
}
|
|
1452
|
+
$tmp = $meta.GrantFile + '.tmp'
|
|
1453
|
+
$body | ConvertTo-Json -Depth 6 | Set-Content -LiteralPath $tmp -Encoding UTF8
|
|
1454
|
+
Move-Item -LiteralPath $tmp -Destination $meta.GrantFile -Force
|
|
1455
|
+
try { Invoke-AuthorityEpochCommand $Cfg 'desktop' 'grant' $meta.GrantFile | Out-Null }
|
|
1456
|
+
catch {
|
|
1457
|
+
Remove-Item -LiteralPath $meta.GrantFile -Force -ErrorAction SilentlyContinue
|
|
1458
|
+
throw
|
|
1459
|
+
}
|
|
1460
|
+
Write-Host ("DESKTOP PLANE ENABLED UNTIL EXPLICIT REVOKE [{0}]" -f ($meta.ActionKinds -join ','))
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
function Disarm-Desktop($Cfg, [switch]$Quiet) {
|
|
1464
|
+
$grantFile = Get-DesktopGrantFileForRevoke $Cfg
|
|
1465
|
+
if ([string]::IsNullOrWhiteSpace([string]$grantFile)) { return }
|
|
1466
|
+
$epochError = $null
|
|
1467
|
+
if (Test-Path -LiteralPath $grantFile -PathType Leaf) {
|
|
1468
|
+
try { Invoke-AuthorityEpochCommand $Cfg 'desktop' 'user-revoke' $grantFile | Out-Null } catch { $epochError = $_.Exception }
|
|
1469
|
+
Remove-Item -LiteralPath $grantFile -Force -ErrorAction SilentlyContinue
|
|
1470
|
+
}
|
|
1471
|
+
if (Test-Path -LiteralPath $grantFile) { throw 'Desktop authority revocation failed: grant remains present' }
|
|
1472
|
+
if ($null -ne $epochError) { throw $epochError }
|
|
1473
|
+
if (-not $Quiet) { Write-Host 'DESKTOP PLANE DISARMED' }
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
function Show-DesktopStatus($Cfg) {
|
|
1477
|
+
$d = Get-DesktopGrantStatus $Cfg
|
|
1478
|
+
Write-Host ("Desktop plane : {0} [{1}]" -f $(if ($d.Armed) { 'ARMED' } elseif ($d.Configured) { 'DISARMED' } else { 'UNCONFIGURED' }), $d.Reason)
|
|
1479
|
+
return $d
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
function Get-AutonomousSupervisorStatus($Cfg) {
|
|
1483
|
+
if (-not [bool]$Cfg.AutonomousSupervisorEnabled) { return [pscustomobject]@{ Configured=$false; Running=$false; Reason='disabled'; Owned=$null } }
|
|
1484
|
+
$owned = Test-OwnedInstance 'Supervisor'
|
|
1485
|
+
[pscustomobject]@{ Configured=$true; Running=[bool]$owned.Alive; Reason=[string]$owned.Reason; Owned=$owned }
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
function Start-AutonomousSupervisor($Cfg) {
|
|
1489
|
+
if (-not [bool]$Cfg.AutonomousSupervisorEnabled) { Fail 'autonomous supervisor is disabled by trusted config' }
|
|
1490
|
+
if (-not [bool]$Cfg.AutonomousRuntimeEnabled) { Fail 'autonomous supervisor requires AutonomousRuntimeEnabled' }
|
|
1491
|
+
Start-OwnedComponent 'Supervisor' $Cfg.Supervisor $Cfg | Out-Null
|
|
1492
|
+
$status = Get-AutonomousSupervisorStatus $Cfg
|
|
1493
|
+
if (-not $status.Running) { Fail "autonomous supervisor start failed: $($status.Reason)" }
|
|
1494
|
+
return $status
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
function Stop-AutonomousSupervisor($Cfg) {
|
|
1498
|
+
if (-not [bool]$Cfg.AutonomousSupervisorEnabled) { return Get-AutonomousSupervisorStatus $Cfg }
|
|
1499
|
+
Stop-OwnedComponent 'Supervisor'
|
|
1500
|
+
return Get-AutonomousSupervisorStatus $Cfg
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
function Show-AutonomousSupervisorStatus($Cfg) {
|
|
1504
|
+
$s = Get-AutonomousSupervisorStatus $Cfg
|
|
1505
|
+
$label = if (-not $s.Configured) { 'DISABLED' } elseif ($s.Running) { 'RUNNING' } else { 'STOPPED' }
|
|
1506
|
+
Write-Host ("Loop supervisor: {0} [{1}]" -f $label, $s.Reason)
|
|
1507
|
+
return $s
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
function Recover-AutonomousSupervisor($Cfg) {
|
|
1511
|
+
if (-not [bool]$Cfg.AutonomousSupervisorEnabled) { Fail 'autonomous supervisor recovery requires AutonomousSupervisorEnabled' }
|
|
1512
|
+
Disarm-Desktop $Cfg -Quiet
|
|
1513
|
+
Start-Deadbyte $Cfg | Out-Null
|
|
1514
|
+
try { Disarm-Autonomous $Cfg -Quiet } catch {}
|
|
1515
|
+
try { Disarm-Coding $Cfg } catch {}
|
|
1516
|
+
try { Disarm-Process $Cfg } catch {}
|
|
1517
|
+
try { Disarm-Host $Cfg } catch {}
|
|
1518
|
+
$runtime = Get-ObservedState $Cfg
|
|
1519
|
+
if ($runtime.State -ne 'ONLINE') { Fail "supervisor recovery requires ONLINE runtime; observed $($runtime.State)" }
|
|
1520
|
+
$process = Get-ProcessGrantStatus $Cfg
|
|
1521
|
+
$coding = Get-CodingLeaseStatus $Cfg
|
|
1522
|
+
$autonomous = Get-AutonomousLeaseStatus $Cfg
|
|
1523
|
+
$desktop = Get-DesktopGrantStatus $Cfg
|
|
1524
|
+
$hostStatus = Get-HostArmStatus $Cfg
|
|
1525
|
+
if ($process.Armed -or $coding.Armed -or $autonomous.Armed -or $desktop.Armed -or $hostStatus.Armed) { Fail 'supervisor recovery final authority is not fully disarmed' }
|
|
1526
|
+
$supervisor = Start-AutonomousSupervisor $Cfg
|
|
1527
|
+
Write-Host 'AUTONOMOUS SUPERVISOR RECOVERY PASS: runtime ONLINE; privileged planes DISARMED.'
|
|
1528
|
+
return [pscustomobject]@{ Runtime=$runtime; Supervisor=$supervisor; Process=$process; Coding=$coding; Autonomous=$autonomous; Desktop=$desktop; Host=$hostStatus }
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
function Get-BoundedObservationReason($Value) {
|
|
1532
|
+
$reason = [string]$Value
|
|
1533
|
+
if ($reason -cmatch '^[a-z][a-z0-9-]{0,63}$') { return $reason }
|
|
1534
|
+
return 'unverified'
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
function Convert-OwnedObservation($Owned) {
|
|
1538
|
+
$pidValue = $null; $birth = $null
|
|
1539
|
+
if ($Owned.Alive -and $Owned.Ledger) {
|
|
1540
|
+
$pidValue = [int]$Owned.Ledger.pid
|
|
1541
|
+
$birth = [string]$Owned.Ledger.creation_time_utc
|
|
1542
|
+
}
|
|
1543
|
+
[ordered]@{ alive=[bool]$Owned.Alive; reason=(Get-BoundedObservationReason $Owned.Reason); pid=$pidValue; creation_time_utc=$birth }
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
function Convert-AuthorityObservation($Authority) {
|
|
1547
|
+
$out = [ordered]@{ configured=[bool]$Authority.Configured; armed=[bool]$Authority.Armed; reason=(Get-BoundedObservationReason $Authority.Reason) }
|
|
1548
|
+
$seqProp = $Authority.PSObject.Properties['EpochSeq']
|
|
1549
|
+
$shaProp = $Authority.PSObject.Properties['EpochSha256']
|
|
1550
|
+
if ($null -ne $seqProp -or $null -ne $shaProp) {
|
|
1551
|
+
if ($null -eq $seqProp -or $null -eq $shaProp -or [int64]$seqProp.Value -lt 1 -or [string]$shaProp.Value -cnotmatch '^[0-9a-f]{64}$') {
|
|
1552
|
+
Fail 'authority epoch observation incomplete or invalid'
|
|
1553
|
+
}
|
|
1554
|
+
$out['epoch_seq'] = [int64]$seqProp.Value
|
|
1555
|
+
$out['epoch_sha256'] = [string]$shaProp.Value
|
|
1556
|
+
}
|
|
1557
|
+
return $out
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
function Get-DownstreamCoreObservation($Cfg, $Bridge) {
|
|
1561
|
+
$out = [ordered]@{ alive=$false; reason='bridge-unavailable'; pid=$null; parent_pid=$null; creation_time_utc=$null; identity_basis=$null }
|
|
1562
|
+
if (-not $Bridge.Alive) { return $out }
|
|
1563
|
+
$bridgePid = [int]$Bridge.Ledger.pid
|
|
1564
|
+
if (-not [string]::Equals([IO.Path]::GetFullPath([string]$Bridge.Ledger.script_path), [IO.Path]::GetFullPath([string]$Cfg.Bridge.ScriptPath), [StringComparison]::OrdinalIgnoreCase) -or [string]$Bridge.Ledger.script_sha256 -ne [string]$Cfg.Bridge.ScriptSha256) {
|
|
1565
|
+
$out.reason='bridge-config-mismatch'; return $out
|
|
1566
|
+
}
|
|
1567
|
+
try {
|
|
1568
|
+
$children = @(Get-CimInstance Win32_Process -Filter ("ParentProcessId={0} AND Name='node.exe'" -f $bridgePid) -ErrorAction Stop)
|
|
1569
|
+
if ($children.Count -eq 0) { $out.reason='startup-zero-child'; return $out }
|
|
1570
|
+
if ($children.Count -ne 1) { $out.reason='ambiguous-core'; return $out }
|
|
1571
|
+
$child = $children[0]
|
|
1572
|
+
if ([int]$child.ParentProcessId -ne $bridgePid -or [int]$child.ProcessId -lt 1 -or $null -eq $child.CreationDate) { $out.reason='core-identity-incomplete'; return $out }
|
|
1573
|
+
if (-not [string]::IsNullOrWhiteSpace([string]$child.ExecutablePath) -and -not [string]::Equals([string]$child.ExecutablePath,[string]$Cfg.Bridge.FilePath,[StringComparison]::OrdinalIgnoreCase)) { $out.reason='core-image-mismatch'; return $out }
|
|
1574
|
+
$command = [string]$child.CommandLine
|
|
1575
|
+
$needle = (Join-Path ([string]$Cfg.CoreRoot) 'src\mcp-server.mjs').Replace('/','\').ToLowerInvariant()
|
|
1576
|
+
if ([string]::IsNullOrWhiteSpace($command)) { $basis='verified-bridge-single-opaque-node-child' }
|
|
1577
|
+
elseif ($command.Replace('/','\').ToLowerInvariant().Contains($needle)) { $basis='command-line' }
|
|
1578
|
+
else { $out.reason='wrong-core-generation'; return $out }
|
|
1579
|
+
$out.alive=$true; $out.reason='verified'; $out.pid=[int]$child.ProcessId; $out.parent_pid=$bridgePid
|
|
1580
|
+
$out.creation_time_utc=$child.CreationDate.ToUniversalTime().ToString('o'); $out.identity_basis=$basis
|
|
1581
|
+
return $out
|
|
1582
|
+
} catch { $out.reason='core-observation-failed'; return $out }
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
function Get-StructuredObservation($Cfg) {
|
|
1586
|
+
$s = Get-ObservedState $Cfg
|
|
1587
|
+
$supervisor = Get-AutonomousSupervisorStatus $Cfg
|
|
1588
|
+
[ordered]@{
|
|
1589
|
+
schema='deadbyte.controller-observation.v1'
|
|
1590
|
+
observed_at_utc=[DateTime]::UtcNow.ToString('o')
|
|
1591
|
+
state=[string]$s.State
|
|
1592
|
+
core_root=[string]$Cfg.CoreRoot
|
|
1593
|
+
local_ready=[bool]$s.LocalReady
|
|
1594
|
+
bridge=(Convert-OwnedObservation $s.Bridge)
|
|
1595
|
+
core=$s.Core
|
|
1596
|
+
cloud=(Convert-OwnedObservation $s.Cloud)
|
|
1597
|
+
supervisor=[ordered]@{ configured=[bool]$supervisor.Configured; running=[bool]$supervisor.Running; reason=(Get-BoundedObservationReason $supervisor.Reason) }
|
|
1598
|
+
authority=[ordered]@{
|
|
1599
|
+
process=(Convert-AuthorityObservation (Get-ProcessGrantStatus $Cfg))
|
|
1600
|
+
coding=(Convert-AuthorityObservation (Get-CodingLeaseStatus $Cfg))
|
|
1601
|
+
autonomous=(Convert-AuthorityObservation (Get-AutonomousLeaseStatus $Cfg))
|
|
1602
|
+
desktop=(Convert-AuthorityObservation (Get-DesktopGrantStatus $Cfg))
|
|
1603
|
+
host=(Convert-AuthorityObservation (Get-HostArmStatus $Cfg))
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
function Show-State($Cfg) {
|
|
1609
|
+
$s = Get-ObservedState $Cfg
|
|
1610
|
+
$bp = if ($s.Bridge.Alive) { $s.Bridge.Ledger.pid } else { '-' }
|
|
1611
|
+
$cp = if ($s.Cloud.Alive) { $s.Cloud.Ledger.pid } else { '-' }
|
|
1612
|
+
$url = Get-CurrentUrl $Cfg
|
|
1613
|
+
Write-Host ("DEADBYTE MCP = {0}" -f $s.State)
|
|
1614
|
+
Write-Host ("Bridge PID : {0} [{1}]" -f $bp, $s.Bridge.Reason)
|
|
1615
|
+
Write-Host ("Core PID : {0} [{1}]" -f $(if ($s.Core.alive) { $s.Core.pid } else { '-' }), $s.Core.reason)
|
|
1616
|
+
Write-Host ("Cloud PID : {0} [{1}]" -f $cp, $s.Cloud.Reason)
|
|
1617
|
+
Write-Host ("Local ready : {0}" -f $s.LocalReady)
|
|
1618
|
+
Write-Host ("URL : {0}" -f $(if ($url) { $url } else { '<unknown>' }))
|
|
1619
|
+
Write-Host ("Machine runtime: {0}" -f $(if ([bool]$Cfg.MachineRuntimeEnabled) { 'ENABLED' } else { 'DISABLED' }))
|
|
1620
|
+
Write-Host ("Agent runtime : {0}" -f $(if ([bool]$Cfg.AgentRuntimeEnabled) { 'ENABLED' } else { 'DISABLED' }))
|
|
1621
|
+
Write-Host ("Process rt : {0}" -f $(if ([bool]$Cfg.ProcessRuntimeEnabled) { 'ENABLED' } else { 'DISABLED' }))
|
|
1622
|
+
Write-Host ("Coding runtime: {0}" -f $(if ([bool]$Cfg.CodingRuntimeEnabled) { 'ENABLED' } else { 'DISABLED' }))
|
|
1623
|
+
Write-Host ("Machine FS rt : {0}" -f $(if ([bool]$Cfg.MachineFsRuntimeEnabled) { 'ENABLED' } else { 'DISABLED' }))
|
|
1624
|
+
Write-Host ("Observation rt: {0}" -f $(if ([bool]$Cfg.ObservationRuntimeEnabled) { 'ENABLED' } else { 'DISABLED' }))
|
|
1625
|
+
Write-Host ("Desktop runtime: {0}" -f $(if ([bool]$Cfg.DesktopRuntimeEnabled) { 'ENABLED' } else { 'DISABLED' }))
|
|
1626
|
+
Write-Host ("Autonomous rt : {0}" -f $(if ([bool]$Cfg.AutonomousRuntimeEnabled) { 'ENABLED' } else { 'DISABLED' }))
|
|
1627
|
+
Show-AutonomousSupervisorStatus $Cfg | Out-Null
|
|
1628
|
+
Show-ProcessStatus $Cfg | Out-Null
|
|
1629
|
+
Show-CodingStatus $Cfg | Out-Null
|
|
1630
|
+
Show-AutonomousStatus $Cfg | Out-Null
|
|
1631
|
+
Show-DesktopStatus $Cfg | Out-Null
|
|
1632
|
+
Show-HostStatus $Cfg | Out-Null
|
|
1633
|
+
return $s
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
function Invoke-MachineSmoke($Cfg) {
|
|
1637
|
+
if (-not [bool]$Cfg.MachineRuntimeEnabled) { Fail 'machinetest requires MachineRuntimeEnabled' }
|
|
1638
|
+
$s = Get-ObservedState $Cfg
|
|
1639
|
+
if ($s.State -ne 'ONLINE') { Fail "machinetest requires ONLINE; observed $($s.State)" }
|
|
1640
|
+
$url = Get-CurrentUrl $Cfg
|
|
1641
|
+
if (-not $url) { Fail 'public MCP URL unavailable' }
|
|
1642
|
+
Wait-PublicDnsReady $Cfg $url
|
|
1643
|
+
$probeArgs = @($url)
|
|
1644
|
+
if (-not [string]::IsNullOrWhiteSpace([string]$env:DEADBYTE_REMOTE_PROBE_MODE)) {
|
|
1645
|
+
if ([string]$env:DEADBYTE_REMOTE_PROBE_MODE -notin @('default','desktop','host','full','revoked')) { Fail 'invalid remote enforcement probe mode' }
|
|
1646
|
+
$probeArgs += [string]$env:DEADBYTE_REMOTE_PROBE_MODE
|
|
1647
|
+
}
|
|
1648
|
+
Invoke-CoreGate $Cfg 'remote-mcp-machine-smoke-client.mjs' $probeArgs
|
|
1649
|
+
Write-Host 'MACHINE IDENTITY LIVE GATE PASS: public status/list + SPKI identity + signed Ed25519 ping verified.'
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
function Invoke-Smoke($Cfg, [bool]$Full) {
|
|
1653
|
+
$s = Get-ObservedState $Cfg
|
|
1654
|
+
if ($s.State -ne 'ONLINE') { Fail "MCP test requires ONLINE; observed $($s.State)" }
|
|
1655
|
+
if (-not (Test-LocalReady $Cfg)) { Fail 'local readiness probe failed' }
|
|
1656
|
+
$url = Get-CurrentUrl $Cfg
|
|
1657
|
+
if (-not $url) { Fail 'public MCP URL unavailable; configure McpUrlFile or McpPathFile' }
|
|
1658
|
+
Wait-PublicDnsReady $Cfg $url
|
|
1659
|
+
Invoke-CoreGate $Cfg 'remote-mcp-smoke-client.mjs' @($url, $(if ($Full) { 'full' } else { 'smoke' }))
|
|
1660
|
+
if ($Full) {
|
|
1661
|
+
Invoke-CoreGate $Cfg 'mcp-contained-smoke-client.mjs'
|
|
1662
|
+
Write-Host 'FULL GATE PASS: public modern MCP + proof + public contained proof + local canonical 3-operation AppContainer/replay/evidence gate.'
|
|
1663
|
+
} else {
|
|
1664
|
+
Write-Host 'MCP SMOKE PASS: public modern MCP + exact tool catalog + signed proof + independent verification.'
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
function Invoke-HostSmoke($Cfg) {
|
|
1669
|
+
$h = Get-HostArmStatus $Cfg
|
|
1670
|
+
if (-not $h.Armed) { Fail 'hosttest requires an active local arm lease; run armhost first' }
|
|
1671
|
+
$s = Get-ObservedState $Cfg
|
|
1672
|
+
if ($s.State -ne 'ONLINE') { Fail "hosttest requires ONLINE; observed $($s.State)" }
|
|
1673
|
+
$url = Get-CurrentUrl $Cfg
|
|
1674
|
+
if (-not $url) { Fail 'public MCP URL unavailable' }
|
|
1675
|
+
Wait-PublicDnsReady $Cfg $url
|
|
1676
|
+
Invoke-CoreGate $Cfg 'remote-mcp-host-smoke-client.mjs' @($url)
|
|
1677
|
+
Write-Host 'HOST PLANE LIVE GATE PASS: public host write + hash readback + hash-pinned exec + cleanup.'
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
function Invoke-CodingSmoke($Cfg) {
|
|
1681
|
+
$c = Get-CodingLeaseStatus $Cfg
|
|
1682
|
+
if (-not $c.Armed) { Fail 'codingtest requires an active local coding lease; run armcoding first' }
|
|
1683
|
+
$s = Get-ObservedState $Cfg
|
|
1684
|
+
if ($s.State -ne 'ONLINE') { Fail "codingtest requires ONLINE; observed $($s.State)" }
|
|
1685
|
+
$url = Get-CurrentUrl $Cfg
|
|
1686
|
+
if (-not $url) { Fail 'public MCP URL unavailable' }
|
|
1687
|
+
Wait-PublicDnsReady $Cfg $url
|
|
1688
|
+
Invoke-CoreGate $Cfg 'remote-mcp-coding-smoke-client.mjs' @($url)
|
|
1689
|
+
Write-Host 'CODING PLANE LIVE GATE PASS: public edit + command + benchmark + durable coding-task receipt verification.'
|
|
1690
|
+
}
|
|
1691
|
+
|
|
1692
|
+
function Invoke-AutonomousSmoke($Cfg) {
|
|
1693
|
+
$a = Get-AutonomousLeaseStatus $Cfg
|
|
1694
|
+
if (-not $a.Armed) { Fail 'autonomoustest requires active coding + autonomous leases; run armcoding then armautonomous' }
|
|
1695
|
+
$s = Get-ObservedState $Cfg
|
|
1696
|
+
if ($s.State -ne 'ONLINE') { Fail "autonomoustest requires ONLINE; observed $($s.State)" }
|
|
1697
|
+
$url = Get-CurrentUrl $Cfg
|
|
1698
|
+
if (-not $url) { Fail 'public MCP URL unavailable' }
|
|
1699
|
+
Wait-PublicDnsReady $Cfg $url
|
|
1700
|
+
Invoke-CoreGate $Cfg 'remote-mcp-autonomous-smoke-client.mjs' @($url)
|
|
1701
|
+
Write-Host 'AUTONOMOUS PRODUCTION LIVE GATE PASS: public sampling MRTR + native diff + fail/diagnose/replan + build/test/benchmark + signed release gate.'
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
function Clean-Stale {
|
|
1705
|
+
Ensure-Directory $RuntimeDir
|
|
1706
|
+
foreach ($name in @('Bridge','Cloud','Supervisor')) {
|
|
1707
|
+
$o = Test-OwnedInstance $name
|
|
1708
|
+
if ($o.Ledger -and -not $o.Alive -and $o.Reason -eq 'process-missing') {
|
|
1709
|
+
Remove-Item -LiteralPath (Ledger-Path $name) -Force -ErrorAction SilentlyContinue
|
|
1710
|
+
} elseif ($o.Ledger -and -not $o.Alive -and $o.Reason -ne 'no-ledger') {
|
|
1711
|
+
Write-Warning "$name ledger retained: $($o.Reason). No unverified process was killed."
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
function Run-CommandCore([string]$Name) {
|
|
1717
|
+
$cfg = Get-Config
|
|
1718
|
+
Assert-ControllerRootBound $cfg
|
|
1719
|
+
$readOnly = $Name -in @('status','url','hoststatus','processstatus','codingstatus','autonomousstatus','desktopstatus','supervisorstatus')
|
|
1720
|
+
if (-not $readOnly) { Ensure-Directory $RuntimeDir; Ensure-Directory $LogDir }
|
|
1721
|
+
switch ($Name) {
|
|
1722
|
+
'start' { Start-Deadbyte $cfg | Out-Null; if ([bool]$cfg.AutonomousSupervisorEnabled) { Start-AutonomousSupervisor $cfg | Out-Null }; Show-State $cfg | Out-Null }
|
|
1723
|
+
'resume' { Start-Deadbyte $cfg | Out-Null; if ([bool]$cfg.AutonomousSupervisorEnabled) { Start-AutonomousSupervisor $cfg | Out-Null }; Show-State $cfg | Out-Null }
|
|
1724
|
+
'park' { Park-Deadbyte $cfg | Out-Null; Show-State $cfg | Out-Null }
|
|
1725
|
+
'stop' { if ([bool]$cfg.AutonomousSupervisorEnabled) { Stop-AutonomousSupervisor $cfg | Out-Null }; Stop-Deadbyte $cfg | Out-Null; Show-State $cfg | Out-Null }
|
|
1726
|
+
'restart' { if ([bool]$cfg.AutonomousSupervisorEnabled) { Stop-AutonomousSupervisor $cfg | Out-Null }; Stop-Deadbyte $cfg | Out-Null; Start-Deadbyte $cfg | Out-Null; if ([bool]$cfg.AutonomousSupervisorEnabled) { Start-AutonomousSupervisor $cfg | Out-Null }; Show-State $cfg | Out-Null }
|
|
1727
|
+
'status' { if ($Json) { Get-StructuredObservation $cfg | ConvertTo-Json -Depth 6 -Compress | Write-Output } else { Show-State $cfg | Out-Null } }
|
|
1728
|
+
'url' { $u=Get-CurrentUrl $cfg; if (-not $u) { Fail 'URL unavailable' }; Write-Output $u }
|
|
1729
|
+
'test' { Invoke-Smoke $cfg $false }
|
|
1730
|
+
'fulltest'{ Invoke-Smoke $cfg $true }
|
|
1731
|
+
'toggle' { $s=Get-ObservedState $cfg; if ($s.State -eq 'ONLINE') { Park-Deadbyte $cfg | Out-Null } elseif ($s.State -in @('PARKED','OFFLINE')) { Start-Deadbyte $cfg | Out-Null } else { Fail 'toggle refuses DEGRADED state' }; Show-State $cfg | Out-Null }
|
|
1732
|
+
'clean' { Clean-Stale; Show-State $cfg | Out-Null }
|
|
1733
|
+
'machinetest' { Invoke-MachineSmoke $cfg }
|
|
1734
|
+
'armhost' { Arm-Host $cfg; Show-HostStatus $cfg | Out-Null }
|
|
1735
|
+
'disarmhost' { Disarm-Host $cfg; Show-HostStatus $cfg | Out-Null }
|
|
1736
|
+
'hoststatus' { Show-HostStatus $cfg | Out-Null }
|
|
1737
|
+
'hosttest' { Invoke-HostSmoke $cfg }
|
|
1738
|
+
'armprocess' { Arm-Process $cfg; Show-ProcessStatus $cfg | Out-Null }
|
|
1739
|
+
'disarmprocess' { Disarm-Process $cfg; Show-ProcessStatus $cfg | Out-Null }
|
|
1740
|
+
'processstatus' { Show-ProcessStatus $cfg | Out-Null }
|
|
1741
|
+
'armcoding' { Arm-Coding $cfg; Show-CodingStatus $cfg | Out-Null }
|
|
1742
|
+
'disarmcoding' { Disarm-Coding $cfg; Show-CodingStatus $cfg | Out-Null; Show-AutonomousStatus $cfg | Out-Null }
|
|
1743
|
+
'codingstatus' { Show-CodingStatus $cfg | Out-Null }
|
|
1744
|
+
'codingtest' { Invoke-CodingSmoke $cfg }
|
|
1745
|
+
'armautonomous' { Arm-Autonomous $cfg; Show-AutonomousStatus $cfg | Out-Null }
|
|
1746
|
+
'disarmautonomous' { Disarm-Autonomous $cfg; Show-AutonomousStatus $cfg | Out-Null }
|
|
1747
|
+
'autonomousstatus' { Show-AutonomousStatus $cfg | Out-Null }
|
|
1748
|
+
'autonomoustest' { Invoke-AutonomousSmoke $cfg }
|
|
1749
|
+
'armdesktop' { Arm-Desktop $cfg; Show-DesktopStatus $cfg | Out-Null }
|
|
1750
|
+
'disarmdesktop' { Disarm-Desktop $cfg; Show-DesktopStatus $cfg | Out-Null }
|
|
1751
|
+
'desktopstatus' { Show-DesktopStatus $cfg | Out-Null }
|
|
1752
|
+
'supervisorstart' { Start-AutonomousSupervisor $cfg | Out-Null; Show-AutonomousSupervisorStatus $cfg | Out-Null }
|
|
1753
|
+
'supervisorstop' { Stop-AutonomousSupervisor $cfg | Out-Null; Show-AutonomousSupervisorStatus $cfg | Out-Null }
|
|
1754
|
+
'supervisorstatus' { Show-AutonomousSupervisorStatus $cfg | Out-Null }
|
|
1755
|
+
'supervisorrecover' { Recover-AutonomousSupervisor $cfg | Out-Null; Show-State $cfg | Out-Null }
|
|
1756
|
+
default { Fail "unknown command '$Name'" }
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
|
|
1760
|
+
function Run-Command([string]$Name) {
|
|
1761
|
+
$mutating = $Name -in @(
|
|
1762
|
+
'start','resume','park','stop','restart','toggle','clean',
|
|
1763
|
+
'armhost','disarmhost','armprocess','disarmprocess','armcoding','disarmcoding',
|
|
1764
|
+
'armautonomous','disarmautonomous','armdesktop','disarmdesktop',
|
|
1765
|
+
'supervisorstart','supervisorstop','supervisorrecover'
|
|
1766
|
+
)
|
|
1767
|
+
if (-not $mutating) {
|
|
1768
|
+
Run-CommandCore $Name
|
|
1769
|
+
return
|
|
1770
|
+
}
|
|
1771
|
+
Invoke-WithControllerMutationLock $Name { Run-CommandCore $Name }
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1774
|
+
if ($Command -ne 'menu') {
|
|
1775
|
+
$recordsStartFailure = $Command -in @('start','resume','restart')
|
|
1776
|
+
if ($recordsStartFailure) { Remove-Item -LiteralPath (Controller-StartFailurePath) -Force -ErrorAction SilentlyContinue }
|
|
1777
|
+
try {
|
|
1778
|
+
Run-Command $Command
|
|
1779
|
+
exit 0
|
|
1780
|
+
} catch {
|
|
1781
|
+
if ($recordsStartFailure) {
|
|
1782
|
+
try { Write-ControllerStartFailure $Command $_.Exception } catch {}
|
|
1783
|
+
}
|
|
1784
|
+
throw
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
while ($true) {
|
|
1789
|
+
Clear-Host
|
|
1790
|
+
$cfg = Get-Config
|
|
1791
|
+
Assert-ControllerRootBound $cfg
|
|
1792
|
+
Write-Host 'DEADBYTE MCP ON-DEMAND CONTROLLER V0.15.5 / R46'
|
|
1793
|
+
Write-Host '======================================'
|
|
1794
|
+
Show-State $cfg | Out-Null
|
|
1795
|
+
Write-Host ''
|
|
1796
|
+
Write-Host '[1] Start / Resume'
|
|
1797
|
+
Write-Host '[2] Park'
|
|
1798
|
+
Write-Host '[3] Full Stop'
|
|
1799
|
+
Write-Host '[4] Restart'
|
|
1800
|
+
Write-Host '[5] Transport smoke test'
|
|
1801
|
+
Write-Host '[6] Public transport gate'
|
|
1802
|
+
Write-Host '[7] Show current URL'
|
|
1803
|
+
Write-Host '[8] Clean stale ledgers'
|
|
1804
|
+
Write-Host '[9] Enable privileged host plane (standing grant)'
|
|
1805
|
+
Write-Host '[10] Revoke privileged host plane'
|
|
1806
|
+
Write-Host '[11] Host plane status'
|
|
1807
|
+
Write-Host '[12] Host plane LIVE gate (requires grant)'
|
|
1808
|
+
Write-Host '[13] Enable coding plane (standing grant)'
|
|
1809
|
+
Write-Host '[14] Revoke coding plane'
|
|
1810
|
+
Write-Host '[15] Coding plane status'
|
|
1811
|
+
Write-Host '[16] Coding plane LIVE gate (requires grant)'
|
|
1812
|
+
Write-Host '[17] Enable autonomous plane (standing grant)'
|
|
1813
|
+
Write-Host '[18] Revoke autonomous plane'
|
|
1814
|
+
Write-Host '[19] Autonomous plane status'
|
|
1815
|
+
Write-Host '[20] Autonomous production LIVE gate'
|
|
1816
|
+
Write-Host '[21] Machine identity LIVE gate (non-mutating)'
|
|
1817
|
+
Write-Host '[22] Enable process plane (standing grant)'
|
|
1818
|
+
Write-Host '[23] Revoke process plane'
|
|
1819
|
+
Write-Host '[24] Process plane status'
|
|
1820
|
+
Write-Host '[25] Enable desktop plane (standing grant; never auto-armed)'
|
|
1821
|
+
Write-Host '[26] Revoke desktop plane'
|
|
1822
|
+
Write-Host '[27] Desktop plane status'
|
|
1823
|
+
Write-Host '[0] Exit controller (runtime stays as-is)'
|
|
1824
|
+
$choice = Read-Host 'Select'
|
|
1825
|
+
try {
|
|
1826
|
+
switch ($choice) {
|
|
1827
|
+
'1' { Run-Command 'start' }
|
|
1828
|
+
'2' { Run-Command 'park' }
|
|
1829
|
+
'3' { Run-Command 'stop' }
|
|
1830
|
+
'4' { Run-Command 'restart' }
|
|
1831
|
+
'5' { Run-Command 'test' }
|
|
1832
|
+
'6' { Run-Command 'fulltest' }
|
|
1833
|
+
'7' { Run-Command 'url' }
|
|
1834
|
+
'8' { Run-Command 'clean' }
|
|
1835
|
+
'9' { Run-Command 'armhost' }
|
|
1836
|
+
'10' { Run-Command 'disarmhost' }
|
|
1837
|
+
'11' { Run-Command 'hoststatus' }
|
|
1838
|
+
'12' { Run-Command 'hosttest' }
|
|
1839
|
+
'13' { Run-Command 'armcoding' }
|
|
1840
|
+
'14' { Run-Command 'disarmcoding' }
|
|
1841
|
+
'15' { Run-Command 'codingstatus' }
|
|
1842
|
+
'16' { Run-Command 'codingtest' }
|
|
1843
|
+
'17' { Run-Command 'armautonomous' }
|
|
1844
|
+
'18' { Run-Command 'disarmautonomous' }
|
|
1845
|
+
'19' { Run-Command 'autonomousstatus' }
|
|
1846
|
+
'20' { Run-Command 'autonomoustest' }
|
|
1847
|
+
'21' { Run-Command 'machinetest' }
|
|
1848
|
+
'22' { Run-Command 'armprocess' }
|
|
1849
|
+
'23' { Run-Command 'disarmprocess' }
|
|
1850
|
+
'24' { Run-Command 'processstatus' }
|
|
1851
|
+
'25' { Run-Command 'armdesktop' }
|
|
1852
|
+
'26' { Run-Command 'disarmdesktop' }
|
|
1853
|
+
'27' { Run-Command 'desktopstatus' }
|
|
1854
|
+
'0' { exit 0 }
|
|
1855
|
+
default { Write-Warning 'invalid selection' }
|
|
1856
|
+
}
|
|
1857
|
+
} catch { Write-Host $_.Exception.Message -ForegroundColor Red }
|
|
1858
|
+
if ($choice -ne '0') { [void](Read-Host 'Press Enter') }
|
|
1859
|
+
}
|