From 6cbcccace5efbe6591bcd6dc9c37bb980fb09b80 Mon Sep 17 00:00:00 2001 From: cjharriskc-ai Date: Tue, 4 Aug 2026 01:09:48 -0500 Subject: [PATCH] Add physical Remote Assist qualifier --- README.md | 5 + docs/RELEASE_EVIDENCE_0.3.0.md | 9 +- scripts/Qualify-RampageRemoteAssist.ps1 | 224 ++++++++++++++++++++++++ 3 files changed, 237 insertions(+), 1 deletion(-) create mode 100644 scripts/Qualify-RampageRemoteAssist.ps1 diff --git a/README.md b/README.md index 4bc6285..e837dcb 100644 --- a/README.md +++ b/README.md @@ -155,6 +155,11 @@ presented as a physical two-machine control receipt. Native Windows capture/inpu durable replay-fence, protocol-digest, and UI contract tests are recorded separately. Exact physical laptop installation and interactive control remain an explicit release-evidence line item until run. +Once an opted-in worker is live, the owner can produce a fail-closed physical frame receipt with +`scripts/Qualify-RampageRemoteAssist.ps1`. The command accepts only the loopback protected API, +requires a fresh shipped Remote Assist offer, verifies the signed lease bounds and JPEG SHA-256, closes +the session, and proves the controller returns `404` for the revoked session. It never injects input. + ## OnePool: pool the work, not the address space Remote memory and VRAM do not become magically coherent across commodity networks. Rampage instead diff --git a/docs/RELEASE_EVIDENCE_0.3.0.md b/docs/RELEASE_EVIDENCE_0.3.0.md index c1cf4d3..53b5f72 100644 --- a/docs/RELEASE_EVIDENCE_0.3.0.md +++ b/docs/RELEASE_EVIDENCE_0.3.0.md @@ -28,7 +28,7 @@ the lock screen, the secure desktop, higher-integrity applications, a shell, or | Full source campaign | `scripts/Test-Rampage.ps1 -SkipOllama` | PASS — uninterrupted Rust, clippy, desktop, edge, TypeScript SDK, Python lint/type/test, fresh sidecar build, controller lifecycle, mesh/storage, and universal model-gateway campaign | | Native Windows package | `scripts/Build-Rampage.ps1 -Profile release` and `scripts/Smoke-RampageInstaller.ps1` | PASS — MSI + NSIS built; NSIS installed six payloads, created both shortcuts, started a ready local fabric, closed cleanly, uninstalled cleanly, and leaked no sidecar | | Public release artifacts | [v0.3.0-remote-assist.1](https://github.com/ObtuseAI/rampage/releases/tag/v0.3.0-remote-assist.1) | PASS — published against merged commit `26f1a6cf455ab6efb90f6c99181965d77cec924b`; all four assets independently downloaded and rehashed | -| Physical owner-to-laptop view/control | Requires 0.3.0 on both physical machines | PENDING — not replaced by the showcase image | +| Physical owner-to-laptop view/control | `scripts/Qualify-RampageRemoteAssist.ps1` after 0.3.0 is live and opted in on the laptop | PENDING — qualifier is fail-closed and ready; a real remote frame and worker-visible indicator are not replaced by the showcase image | ## Source-current showcase @@ -37,6 +37,13 @@ the lock screen, the secure desktop, higher-integrity applications, a shell, or This image was rendered from the current desktop source with labeled demonstration topology. It proves the current UI contract and presentation, not physical packet transport or Windows input injection. +The physical qualifier selects a fresh enrolled worker that advertises the shipped `view` capability, +opens a signed lease, fetches a real JPEG frame, independently verifies its byte count, bounds, JPEG +markers, and SHA-256 digest, closes the session, and requires the revoked session to return HTTP 404. +It deliberately sends no input. A PASS receipt plus observation of the worker's visible activity +indicator will close the physical view boundary; interactive control remains a separately witnessed +step so an automated test never moves another machine's pointer without an operator present. + ## Packaging and publication | Artifact | Bytes | SHA-256 | diff --git a/scripts/Qualify-RampageRemoteAssist.ps1 b/scripts/Qualify-RampageRemoteAssist.ps1 new file mode 100644 index 0000000..36b19ca --- /dev/null +++ b/scripts/Qualify-RampageRemoteAssist.ps1 @@ -0,0 +1,224 @@ +param( + [ValidatePattern('^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$')] + [string]$NodeId, + [string]$ControllerBase = 'http://127.0.0.1:47831', + [string]$DataDir = (Join-Path $env:APPDATA 'ai.obtuse.rampage\runtime'), + [ValidateRange(5, 60)] + [int]$TimeoutSeconds = 15 +) + +$ErrorActionPreference = 'Stop' +Set-StrictMode -Version Latest + +$controllerUri = $null +if (-not [Uri]::TryCreate($ControllerBase, [UriKind]::Absolute, [ref]$controllerUri) -or + $controllerUri.Scheme -ne 'http') { + throw 'ControllerBase must be an absolute HTTP loopback URL' +} +$controllerAddress = $null +$controllerIsLoopback = $controllerUri.Host -eq 'localhost' -or + ([Net.IPAddress]::TryParse($controllerUri.Host, [ref]$controllerAddress) -and + [Net.IPAddress]::IsLoopback($controllerAddress)) +if (-not $controllerIsLoopback) { + throw 'Remote Assist qualification refuses a non-loopback controller API' +} +$controllerRoot = $controllerUri.AbsoluteUri.TrimEnd('/') + +$resolvedDataDir = [IO.Path]::GetFullPath($DataDir) +$tokenPath = Join-Path $resolvedDataDir 'controller.token' +$tokenFile = Get-Item -LiteralPath $tokenPath -Force +if ($tokenFile.PSIsContainer -or + ($tokenFile.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0 -or + $tokenFile.Length -lt 32 -or $tokenFile.Length -gt 4096) { + throw "Controller token is not a bounded regular file: $tokenPath" +} +$controllerToken = (Get-Content -LiteralPath $tokenPath -Raw).Trim() +if ($controllerToken -notmatch '^[A-Za-z0-9_-]{32,512}$') { + throw 'Controller token has an invalid shape' +} + +function Invoke-RampageRequest { + param( + [Parameter(Mandatory)] + [ValidateSet('GET', 'POST')] + [string]$Method, + [Parameter(Mandatory)] + [string]$Path, + [object]$Body, + [int[]]$ExpectedStatus = @(200) + ) + + $request = @{ + Uri = "$controllerRoot$Path" + Method = $Method + Headers = @{ 'x-rampage-token' = $controllerToken } + TimeoutSec = $TimeoutSeconds + SkipHttpErrorCheck = $true + } + if ($PSBoundParameters.ContainsKey('Body')) { + $request.ContentType = 'application/json' + $request.Body = $Body | ConvertTo-Json -Depth 8 -Compress + } + $response = Invoke-WebRequest @request + $parsed = if ([string]::IsNullOrWhiteSpace($response.Content)) { + $null + } else { + $response.Content | ConvertFrom-Json + } + if ([int]$response.StatusCode -notin $ExpectedStatus) { + $detail = if ($parsed -and $parsed.error) { $parsed.error } else { $response.Content } + throw "Rampage $Method $Path returned HTTP $([int]$response.StatusCode): $detail" + } + [pscustomobject]@{ + StatusCode = [int]$response.StatusCode + Body = $parsed + } +} + +$health = (Invoke-RampageRequest -Method GET -Path '/health').Body +if ($health.status -ne 'ready' -or $health.kill_latch -eq $true) { + throw 'The owner controller is not ready or its STOP latch is active' +} +if ($health.version -ne '0.3.0') { + throw "Remote Assist qualification requires controller 0.3.0, found $($health.version)" +} + +$nodes = @((Invoke-RampageRequest -Method GET -Path '/v1/nodes').Body) +$offers = @((Invoke-RampageRequest -Method GET -Path '/v1/offers').Body) +$now = [DateTimeOffset]::UtcNow +$eligible = @($offers | Where-Object { + $offer = $_ + $capability = @($offer.workload_capabilities | Where-Object { + $_.adapter -eq 'rampage.remote-assist.v1' -and + $_.status -eq 'shipped' -and + @($_.operations) -contains 'view' + }) + $offer.node_id -and + $offer.mesh_endpoint -and + @($offer.adapters) -contains 'rampage.remote-assist.v1' -and + $offer.availability.foreground_allowed -eq $true -and + [DateTimeOffset]::Parse($offer.expires_at) -gt $now -and + $capability.Count -eq 1 +}) + +if ($NodeId) { + $eligible = @($eligible | Where-Object { $_.node_id -eq $NodeId }) + if ($eligible.Count -ne 1) { + $known = @($nodes | Where-Object { $_.node_id -eq $NodeId }).Count -eq 1 + $reason = if ($known) { + 'the enrolled worker has no fresh opted-in Remote Assist offer' + } else { + 'the node is not enrolled with this owner' + } + throw "Cannot qualify worker ${NodeId}: $reason" + } +} elseif ($eligible.Count -eq 0) { + throw 'No live paired worker is advertising opted-in Rampage 0.3.0 Remote Assist' +} elseif ($eligible.Count -gt 1) { + $choices = ($eligible.node_id | Sort-Object) -join ', ' + throw "More than one Remote Assist worker is eligible; rerun with -NodeId. Choices: $choices" +} + +$selected = $eligible[0] +$node = @($nodes | Where-Object { $_.node_id -eq $selected.node_id }) | Select-Object -First 1 +if (-not $node) { + throw 'The eligible Remote Assist offer does not belong to an enrolled node' +} + +$session = $null +$framePayload = $null +$frameBytes = $null +$closeReceipt = $null +$qualificationError = $null +$closeError = $null +try { + $opened = Invoke-RampageRequest -Method POST -Path '/v1/remote-assist/sessions' ` + -ExpectedStatus @(201) -Body @{ node_id = $selected.node_id; mode = 'view' } + $session = $opened.Body.session + $issuedAt = [DateTimeOffset]::Parse($session.issued_at) + $expiresAt = [DateTimeOffset]::Parse($session.expires_at) + $leaseSeconds = ($expiresAt - $issuedAt).TotalSeconds + if ($session.schema -ne 'rampage.remote-desktop-lease.v1' -or + $session.node_id -ne $selected.node_id -or + $session.mode -ne 'view' -or + $leaseSeconds -le 0 -or $leaseSeconds -gt 30 -or + $session.max_width -lt 1 -or $session.max_width -gt 4096 -or + $session.max_height -lt 1 -or $session.max_height -gt 4096 -or + $session.max_fps -lt 1 -or $session.max_fps -gt 15 -or + [string]::IsNullOrWhiteSpace($session.signature)) { + throw 'The controller returned an invalid Remote Assist lease' + } + + $frameResponse = Invoke-RampageRequest -Method GET ` + -Path "/v1/remote-assist/sessions/$($session.session_id)/frame" + $framePayload = $frameResponse.Body + $frame = $framePayload.frame + $frameBytes = [Convert]::FromBase64String($framePayload.data_base64) + $computedDigest = 'sha256:' + [Convert]::ToHexString( + [Security.Cryptography.SHA256]::HashData($frameBytes) + ).ToLowerInvariant() + if ($framePayload.schema -ne 'rampage.remote-desktop-frame-payload.v1' -or + $framePayload.session_id -ne $session.session_id -or + $frame.sequence -lt 1 -or + $frame.width -lt 1 -or $frame.width -gt $session.max_width -or + $frame.height -lt 1 -or $frame.height -gt $session.max_height -or + $frame.media_type -ne 'image/jpeg' -or + $frame.payload_size -ne $frameBytes.Length -or + $frameBytes.Length -lt 4 -or $frameBytes.Length -gt (4 * 1024 * 1024) -or + $frame.payload_digest -ne $computedDigest -or + $frameBytes[0] -ne 0xff -or $frameBytes[1] -ne 0xd8 -or + $frameBytes[$frameBytes.Length - 2] -ne 0xff -or + $frameBytes[$frameBytes.Length - 1] -ne 0xd9) { + throw 'The physical Remote Assist frame failed its contract or digest verification' + } +} catch { + $qualificationError = $_ +} finally { + if ($session) { + try { + $closeReceipt = (Invoke-RampageRequest -Method POST ` + -Path "/v1/remote-assist/sessions/$($session.session_id)/close").Body + } catch { + $closeError = $_ + } + } +} + +if ($qualificationError) { throw $qualificationError } +if ($closeError) { throw $closeError } +if (-not $closeReceipt.closed -or $closeReceipt.duplicate) { + throw 'Remote Assist close did not revoke the active controller session' +} +$postClose = Invoke-RampageRequest -Method GET ` + -Path "/v1/remote-assist/sessions/$($session.session_id)/frame" -ExpectedStatus @(404) + +$frame = $framePayload.frame +$leaseDuration = ([DateTimeOffset]::Parse($session.expires_at) - + [DateTimeOffset]::Parse($session.issued_at)).TotalSeconds +[pscustomobject]@{ + schema = 'rampage.remote-assist-physical-receipt.v1' + result = 'PASS' + controller_version = $health.version + controller_mesh_endpoint_id = $health.mesh_endpoint_id + node_id = $selected.node_id + node_name = $node.display_name + node_platform = $node.platform + offer_observed_at = $selected.observed_at + session_id = $session.session_id + lease_id = $session.lease_id + lease_mode = $session.mode + lease_seconds = $leaseDuration + fencing_epoch = $session.fencing_epoch + frame_sequence = $frame.sequence + frame_captured_at = $frame.captured_at + frame_width = $frame.width + frame_height = $frame.height + frame_bytes = $frameBytes.Length + frame_sha256 = $frame.payload_digest + jpeg_contract_verified = $true + session_close_confirmed = $true + post_close_http_status = $postClose.StatusCode + elevation_authority = $false + secure_desktop_authority = $false + verified_at = [DateTimeOffset]::UtcNow.ToString('o') +} | ConvertTo-Json -Depth 4