param( [string]$DockerContext = "docker-prod", [string]$BaseURL = "http://docker.cin.su:18080" ) $ErrorActionPreference = "Stop" $coreContainer = "assistant-core-runtime-1" $continuityContainer = "assistant-continuity-runtime-1" function Post-ContinuityJson([string]$url, [object]$body) { $json = $body | ConvertTo-Json -Depth 8 -Compress $local = New-TemporaryFile $remote = "/tmp/domovoy-$([guid]::NewGuid().ToString('N')).json" try { [IO.File]::WriteAllText($local, $json, [Text.UTF8Encoding]::new($false)) docker --context $DockerContext cp $local "${continuityContainer}:$remote" | Out-Null docker --context $DockerContext exec $continuityContainer wget -qO- --header "Content-Type: application/json" --post-file $remote $url if ($LASTEXITCODE -ne 0) { throw "Continuity fixture creation failed" } } finally { docker --context $DockerContext exec --user root $continuityContainer rm -f $remote 2>$null Remove-Item -LiteralPath $local -Force } } function Post-CoreJson([string]$url, [object]$body) { $json = $body | ConvertTo-Json -Depth 8 -Compress $local = New-TemporaryFile $remote = "/tmp/domovoy-$([guid]::NewGuid().ToString('N')).json" try { [IO.File]::WriteAllText($local, $json, [Text.UTF8Encoding]::new($false)) docker --context $DockerContext cp $local "${coreContainer}:$remote" | Out-Null docker --context $DockerContext exec $coreContainer wget -qO- --header "Content-Type: application/json" --post-file $remote $url if ($LASTEXITCODE -ne 0) { throw "Core fixture creation failed" } } finally { docker --context $DockerContext exec --user root $coreContainer rm -f $remote 2>$null Remove-Item -LiteralPath $local -Force } } function Get-ContainerSecret([object[]]$environment, [string]$name) { $fileEntry = $environment | Where-Object { $_ -like "${name}_FILE=*" } | Select-Object -First 1 if ($fileEntry) { $path = $fileEntry.Substring("${name}_FILE=".Length) $value = docker --context $DockerContext exec $coreContainer sh -c "cat -- '$path'" if ($LASTEXITCODE -ne 0 -or -not $value) { throw "$name file-backed secret is not readable" } return ($value -join "`n").TrimEnd("`r", "`n") } $entry = $environment | Where-Object { $_ -like "${name}=*" } | Select-Object -First 1 if (-not $entry) { throw "$name is not configured" } return $entry.Substring("${name}=".Length) } $health = Invoke-RestMethod -Uri "$BaseURL/healthz" -Method Get if ($health.status -ne "ok" -or $health.runtime -ne "creator-web") { throw "Creator web health response is invalid" } $page = Invoke-WebRequest -Uri "$BaseURL/" -Method Get if ($page.StatusCode -ne 200 -or $page.Content -notmatch "Домовой") { throw "Creator console is not available" } if (-not $page.Headers["Content-Security-Policy"]) { throw "Creator console security headers are missing" } $denied = Invoke-WebRequest -Uri "$BaseURL/api/focus-task-commits" -Method Post -SkipHttpErrorCheck if ($denied.StatusCode -ne 404) { throw "Internal mutation route was exposed" } $continuityMutation = Invoke-WebRequest -Uri "$BaseURL/api/prospective-items/blocked" -Method Post -SkipHttpErrorCheck if ($continuityMutation.StatusCode -ne 404) { throw "Continuity mutation route was exposed" } $invalid = Invoke-WebRequest -Uri "$BaseURL/api/session/current" -Headers @{Authorization="Bearer invalid"} -SkipHttpErrorCheck if ($invalid.StatusCode -ne 401) { throw "Invalid Creator session was not denied" } $environment = docker --context $DockerContext inspect --format '{{range .Config.Env}}{{println .}}{{end}}' $coreContainer $creatorUsername = Get-ContainerSecret $environment "CREATOR_USERNAME" $creatorPassword = Get-ContainerSecret $environment "CREATOR_PASSWORD" $invalidCredentials = Invoke-WebRequest -Uri "$BaseURL/api/session" -Method Post -ContentType "application/json" -Body (@{username=$creatorUsername;password="invalid"} | ConvertTo-Json -Compress) -SkipHttpErrorCheck if ($invalidCredentials.StatusCode -ne 401) { throw "Invalid Creator credentials were not denied" } $issued = Invoke-RestMethod -Uri "$BaseURL/api/session" -Method Post -ContentType "application/json" -Body (@{username=$creatorUsername;password=$creatorPassword} | ConvertTo-Json -Compress) if (-not $issued.Token) { throw "Creator username/password session was not issued through the gateway" } $bootstrapToken = Get-ContainerSecret $environment "CREATOR_BOOTSTRAP_TOKEN" $recoveryIssued = Invoke-RestMethod -Uri "$BaseURL/api/session" -Method Post -Headers @{Authorization="Bearer $bootstrapToken"} if (-not $recoveryIssued.Token) { throw "Creator recovery session was not issued through the gateway" } $current = Invoke-RestMethod -Uri "$BaseURL/api/session/current" -Method Get -Headers @{Authorization="Bearer $($issued.Token)"} if ($current.ActorID -ne "domovoy-creator-actor") { throw "Creator identity was not preserved through the gateway" } $capability = Invoke-RestMethod -Uri "$BaseURL/api/capabilities/cognitive-proposal/health" -Method Get -Headers @{Authorization="Bearer $($issued.Token)"} if ($capability.CapabilityID -ne "cognitive-proposal") { throw "Capability health was not returned through the gateway" } $suffix = [guid]::NewGuid().ToString("N") $prospectiveID = "web-prospective-$suffix" Post-ContinuityJson "http://localhost:8081/v1/prospective-items" @{ ID=$prospectiveID;CommitmentID="web-commitment-$suffix";OwnerPrincipalID="domovoy-creator-principal" ScopeID="domovoy-global-scope";GoalID="web-goal-$suffix";TriggerAtUnixMillis=1800000000000 State="ARMED";Revision=1 } $prospective = Invoke-RestMethod -Uri "$BaseURL/api/prospective-items/$prospectiveID" -Method Get -Headers @{Authorization="Bearer $($issued.Token)"} if ($prospective.ID -ne $prospectiveID -or $prospective.State -ne "ARMED") { throw "ProspectiveItem was not returned through the gateway" } $prospectiveList = Invoke-RestMethod -Uri "$BaseURL/api/prospective-items?limit=25" -Method Get -Headers @{Authorization="Bearer $($issued.Token)"} if (-not $prospectiveList.Items -or $prospectiveList.Items.Count -gt 25) { throw "ProspectiveItem register response is invalid" } if ($prospectiveList.Items | Where-Object OwnerPrincipalID -ne "domovoy-creator-principal") { throw "ProspectiveItem register leaked another Principal" } $actionGoal = "web-action-goal-$suffix" $actionTask = "web-action-task-$suffix" $actionItem = "web-action-item-$suffix" Post-CoreJson "http://localhost:8080/v1/tasks" @{TaskID=$actionTask;GoalID=$actionGoal;ScopeID="domovoy-global-scope";Revision=1} Post-ContinuityJson "http://localhost:8081/v1/prospective-items" @{ ID=$actionItem;CommitmentID="web-action-commitment-$suffix";OwnerPrincipalID="domovoy-creator-principal" ScopeID="domovoy-global-scope";GoalID=$actionGoal;TriggerAtUnixMillis=1800000000000 State="DELIVERED";Revision=1 } $actionCommand = @{ Target=@{TaskID=$actionTask;GoalID=$actionGoal;ScopeID="domovoy-global-scope";Revision=1} ProspectiveItemID=$actionItem;ExpectedRevision=1;EvidenceRef="artifact://creator-web/$suffix" } | ConvertTo-Json -Depth 8 $actionResult = Invoke-RestMethod -Uri "$BaseURL/api/actions/satisfy-delivered" -Method Post -ContentType "application/json" -Headers @{Authorization="Bearer $($issued.Token)"} -Body $actionCommand if ($actionResult.State -ne "SUCCEEDED" -or -not $actionResult.AuthorizedActionID -or -not $actionResult.ActionAttemptID) { throw "Creator action gateway response is invalid" } $staleAction = Invoke-WebRequest -Uri "$BaseURL/api/actions/satisfy-delivered" -Method Post -ContentType "application/json" -Headers @{Authorization="Bearer $($issued.Token)"} -Body $actionCommand -SkipHttpErrorCheck if ($staleAction.StatusCode -ne 409) { throw "Stale Creator action was not rejected" } $arbitraryAction = Invoke-WebRequest -Uri "$BaseURL/api/actions/arbitrary" -Method Post -ContentType "application/json" -Headers @{Authorization="Bearer $($issued.Token)"} -Body '{}' -SkipHttpErrorCheck if ($arbitraryAction.StatusCode -ne 404) { throw "Arbitrary action route was exposed" } Write-Output "DEV-000033 Creator username/password and console gateway: PASS"