Compare commits

...
Author SHA1 Message Date
m d5cd964e51 deploy: attest production artifact revision
CI / test (push) Successful in 11s
CI / test (pull_request) Successful in 11s
2026-08-16 19:53:00 +03:00
m 959c89d47b Merge pull request 'DEV-000044: audit post-notification production state' (#14) from codex/post-notification-production-audit-v1 into main
CI / test (push) Successful in 11s
2026-08-16 16:49:26 +00:00
m 760f671077 docs: audit post-notification production state
CI / test (push) Successful in 11s
CI / test (pull_request) Successful in 11s
2026-08-16 19:48:37 +03:00
12 changed files with 191 additions and 22 deletions
+7 -6
View File
@@ -4,6 +4,7 @@ services:
build:
context: ..
dockerfile: deploy/images/postgres.Dockerfile
args: { SOURCE_REVISION: "${ASSISTANT_SOURCE_REVISION:-unknown}" }
restart: unless-stopped
environment:
POSTGRES_DB: ${POSTGRES_DB:-assistant}
@@ -31,7 +32,7 @@ services:
volumes: ["assistant_nats:/data"]
notification-receiver:
build: { context: .., dockerfile: deploy/images/runtime.Dockerfile, args: { TARGET: notification-receiver } }
build: { context: .., dockerfile: deploy/images/runtime.Dockerfile, args: { TARGET: notification-receiver, SOURCE_REVISION: "${ASSISTANT_SOURCE_REVISION:-unknown}" } }
restart: unless-stopped
environment:
LISTEN_ADDR: ":8085"
@@ -50,7 +51,7 @@ services:
retries: 10
core-runtime:
build: { context: .., dockerfile: deploy/images/runtime.Dockerfile, args: { TARGET: core-runtime } }
build: { context: .., dockerfile: deploy/images/runtime.Dockerfile, args: { TARGET: core-runtime, SOURCE_REVISION: "${ASSISTANT_SOURCE_REVISION:-unknown}" } }
restart: unless-stopped
environment:
LISTEN_ADDR: ":8080"
@@ -74,7 +75,7 @@ services:
- assistant_core_secrets:/run/secrets:ro
continuity-runtime:
build: { context: .., dockerfile: deploy/images/runtime.Dockerfile, args: { TARGET: continuity-runtime } }
build: { context: .., dockerfile: deploy/images/runtime.Dockerfile, args: { TARGET: continuity-runtime, SOURCE_REVISION: "${ASSISTANT_SOURCE_REVISION:-unknown}" } }
restart: unless-stopped
environment:
LISTEN_ADDR: ":8081"
@@ -87,7 +88,7 @@ services:
COMMIT_EXECUTOR_TOKEN: ${COMMIT_EXECUTOR_TOKEN}
commit-runtime:
build: { context: .., dockerfile: deploy/images/runtime.Dockerfile, args: { TARGET: commit-runtime } }
build: { context: .., dockerfile: deploy/images/runtime.Dockerfile, args: { TARGET: commit-runtime, SOURCE_REVISION: "${ASSISTANT_SOURCE_REVISION:-unknown}" } }
restart: unless-stopped
environment:
LISTEN_ADDR: ":8082"
@@ -109,12 +110,12 @@ services:
- assistant_notification_writer_secret:/run/notification-writer:ro
cognitive-worker:
build: { context: .., dockerfile: deploy/images/runtime.Dockerfile, args: { TARGET: cognitive-worker } }
build: { context: .., dockerfile: deploy/images/runtime.Dockerfile, args: { TARGET: cognitive-worker, SOURCE_REVISION: "${ASSISTANT_SOURCE_REVISION:-unknown}" } }
restart: unless-stopped
environment: { LISTEN_ADDR: ":8083" }
creator-web:
build: { context: .., dockerfile: deploy/images/runtime.Dockerfile, args: { TARGET: creator-web } }
build: { context: .., dockerfile: deploy/images/runtime.Dockerfile, args: { TARGET: creator-web, SOURCE_REVISION: "${ASSISTANT_SOURCE_REVISION:-unknown}" } }
restart: unless-stopped
environment:
LISTEN_ADDR: ":8084"
+3
View File
@@ -1,4 +1,7 @@
FROM postgres:18.6-alpine
ARG SOURCE_REVISION=unknown
LABEL org.opencontainers.image.revision=$SOURCE_REVISION \
org.opencontainers.image.source="https://git.cin.su/m/Assistant.git"
COPY database/bootstrap/001_roles_and_schemas.sql /docker-entrypoint-initdb.d/001_roles_and_schemas.sql
COPY deploy/images/002_owner_passwords.sh /docker-entrypoint-initdb.d/002_owner_passwords.sh
+3
View File
@@ -7,6 +7,9 @@ ARG TARGET
RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -o /out/runtime ./cmd/${TARGET}
FROM alpine:3.23
ARG SOURCE_REVISION=unknown
LABEL org.opencontainers.image.revision=$SOURCE_REVISION \
org.opencontainers.image.source="https://git.cin.su/m/Assistant.git"
RUN addgroup -S assistant && adduser -S -G assistant assistant
USER assistant
COPY --from=build /out/runtime /usr/local/bin/assistant-runtime
@@ -0,0 +1,56 @@
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[ValidatePattern('^[0-9a-f]{40}$')]
[string]$Revision,
[string]$DockerContext = 'docker-prod',
[string]$LocalManifestPath = "$env:LOCALAPPDATA\Domovoy\monitoring\deployment-manifest.json",
[string]$NasManifestPath = '\\nas\MST\codex\_monitoring\domovoy\deployment-manifest.json',
[string[]]$ExpectedContainers = @(
'assistant-postgres-1',
'assistant-notification-receiver-1',
'assistant-core-runtime-1',
'assistant-continuity-runtime-1',
'assistant-commit-runtime-1',
'assistant-cognitive-worker-1',
'assistant-creator-web-1'
)
)
$ErrorActionPreference = 'Stop'
Set-StrictMode -Version Latest
function Write-AtomicJson([string]$Path, [string]$Json) {
$directory = Split-Path -Parent $Path
New-Item -ItemType Directory -Force -Path $directory | Out-Null
$partial = Join-Path $directory ('.' + [IO.Path]::GetFileName($Path) + ".${PID}.partial")
try {
[IO.File]::WriteAllText($partial, $Json + "`n", [Text.UTF8Encoding]::new($false))
Move-Item -LiteralPath $partial -Destination $Path -Force
}
finally {
if (Test-Path -LiteralPath $partial) { Remove-Item -LiteralPath $partial -Force }
}
}
$artifacts = foreach ($container in $ExpectedContainers) {
$raw = ((& docker.exe --context $DockerContext inspect $container --format '{{json .}}') -join '').Trim()
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($raw)) { throw "Cannot inspect $container" }
$inspect = $raw | ConvertFrom-Json
$actual = [string]$inspect.Config.Labels.'org.opencontainers.image.revision'
if ($actual -ne $Revision) { throw "$container revision mismatch: expected=$Revision actual=$actual" }
if ($inspect.State.Status -ne 'running') { throw "$container is not running" }
[ordered]@{ container = $container; image_id = [string]$inspect.Image; revision = $actual }
}
$manifest = [ordered]@{
version = 1
project = 'domovoy'
revision = $Revision
promoted_at_utc = [DateTime]::UtcNow.ToString('o')
artifacts = @($artifacts)
}
$json = $manifest | ConvertTo-Json -Depth 5
Write-AtomicJson $LocalManifestPath $json
Write-AtomicJson $NasManifestPath $json
Write-Output "Production artifact confirmed: revision=$Revision containers=$($artifacts.Count)"
@@ -7,6 +7,7 @@ param(
[string]$NasBackupRoot = '\\nas\MST\codex\_backups\domovoy\postgres',
[string]$NasStatusPath = '\\nas\MST\codex\_monitoring\domovoy\status.json',
[string]$LocalStatusPath = "$env:LOCALAPPDATA\Domovoy\monitoring\status.json",
[string]$DeploymentManifestPath = "$env:LOCALAPPDATA\Domovoy\monitoring\deployment-manifest.json",
[double]$MaxProductionBackupAgeHours = 30,
[double]$MaxNasBackupAgeHours = 36,
[int]$MinimumCertificateDays = 21,
@@ -60,6 +61,21 @@ foreach ($container in $ExpectedContainers) {
catch { Add-Check "container:$container" $false $_.Exception.Message }
}
try {
$manifest = Get-Content -LiteralPath $DeploymentManifestPath -Raw | ConvertFrom-Json
if ($manifest.version -ne 1 -or $manifest.revision -notmatch '^[0-9a-f]{40}$') { throw 'invalid deployment manifest' }
if (@($manifest.artifacts).Count -ne 7) { throw 'deployment manifest artifact count mismatch' }
foreach ($artifact in $manifest.artifacts) {
$raw = ((& docker.exe --context $DockerContext inspect $artifact.container --format '{{json .}}') -join '').Trim()
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($raw)) { throw "cannot inspect $($artifact.container)" }
$inspect = $raw | ConvertFrom-Json
$label = [string]$inspect.Config.Labels.'org.opencontainers.image.revision'
if ($label -ne $manifest.revision -or [string]$inspect.Image -ne [string]$artifact.image_id) { throw "artifact drift: $($artifact.container)" }
}
Add-Check 'deployment-artifact' $true "revision=$($manifest.revision) containers=$(@($manifest.artifacts).Count)"
}
catch { Add-Check 'deployment-artifact' $false $_.Exception.Message }
try {
$response = Invoke-RestMethod -Uri $Endpoint -TimeoutSec 15
$ok = $response.status -eq 'ok'
+2 -2
View File
@@ -1,5 +1,5 @@
{
"task_file": "dev/tasks/DEV-000043-production-notification-delivery.json",
"task_id": "DEV-000043",
"task_file": "dev/tasks/DEV-000045-production-artifact-provenance.json",
"task_id": "DEV-000045",
"task_revision": 1
}
@@ -0,0 +1,32 @@
{
"task_id": "DEV-000044",
"task_revision": 3,
"architecture_revision": "ADR-0005",
"base_repo_revision": "759328972a7ae583f27eec5b97a7a8dea9df2ce7",
"risk_tier": "E",
"intent": "Re-audit the exact merged Assistant artifact after the first production notification operation, update repository status evidence, and identify remaining work without inventing unapproved product or architecture scope; docker-test remains prohibited.",
"requirement_ids": ["ADR-0005", "INV-AUTH-001", "INV-OWN-001", "INV-CONT-001", "INV-TOPO-001", "USER-DOCKER-PROD-ONLY"],
"allowed_change_scope": {
"modules": [],
"paths": [
"docs/FOUNDATION_EXIT_AUDIT.md",
"docs/PROJECT_STATUS.md",
"tests/creator_commit/verify-remote.ps1",
"dev/ACTIVE_TASK.json",
"dev/tasks/DEV-000044-post-notification-production-audit.json"
]
},
"forbidden_change_scope": {"paths": ["architecture/**", "ci/**", ".gitea/**", "contracts/**", "modules/**", "adapters/**", "cmd/**", "database/**", "deploy/**", "security/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0},
"contracts": {"consume": ["ADR-0005", "DEV-000041", "DEV-000042", "DEV-000043", "existing production verification scripts"], "may_change": []},
"semantic_owner": "none-verification",
"canonical_state": [],
"state_transitions": [],
"side_effects_and_privileges": ["read-only production verification", "authenticated smoke may create only its documented disposable test evidence through the existing Commit path"],
"persistence_and_migrations": [],
"failure_retry_restart": ["audit records failures as gaps and does not weaken checks", "no production mutation outside existing verification harness semantics"],
"assumptions": ["origin/main is the deployed exact artifact", "docker.cin.su SSH alias docker remains the production host", "docker-test remains prohibited"],
"acceptance_scenarios": ["local deterministic tests and architecture checks pass", "production stack and Windows tasks are healthy", "notification HTTPS authorization and durable receipt path pass", "Creator remote smoke passes through a Windows/Docker CLI-safe command invocation", "production artifact provenance is either proven or recorded as an explicit gap", "repository documents completed post-foundation work and remaining product decisions"],
"required_tests": ["task schema", "architecture preflight", "exact diff scope", "go test", "go vet", "production health", "authenticated Creator notification smoke", "docker-test absence"],
"terminal_gap_states": ["BLOCKED_BY_ARCHITECTURE_GAP", "BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_CONFLICT", "BLOCKED_BY_MISSING_CAPABILITY"]
}
@@ -0,0 +1,23 @@
{
"task_id": "DEV-000045",
"task_revision": 1,
"architecture_revision": "ADR-0005",
"base_repo_revision": "959c89d47bf231dbcfb7a05e7b63b0e8579e3f0c",
"risk_tier": "E",
"intent": "Stamp project-built production images with the immutable Git revision, record promoted image digests in an external deployment manifest, and make production health reject provenance drift without using docker-test.",
"requirement_ids": ["INV-TOPO-001", "DEV-000044-FINDING-ARTIFACT-PROVENANCE", "USER-DOCKER-PROD-ONLY"],
"allowed_change_scope": {"modules": [], "paths": ["deploy/docker-compose.prod.yml", "deploy/images/runtime.Dockerfile", "deploy/images/postgres.Dockerfile", "deploy/windows/Confirm-DomovoyProductionArtifact.ps1", "deploy/windows/Test-DomovoyProductionHealth.ps1", "docs/DEPLOYMENT.md", "docs/PROJECT_STATUS.md", "dev/ACTIVE_TASK.json", "dev/tasks/DEV-000045-production-artifact-provenance.json"]},
"forbidden_change_scope": {"paths": ["architecture/**", "ci/**", ".gitea/**", "contracts/**", "modules/**", "adapters/**", "cmd/**", "database/**", "security/**", "tests/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0},
"contracts": {"consume": ["existing production Compose topology", "Docker OCI image metadata"], "may_change": []},
"semantic_owner": "none-operational-verification",
"canonical_state": [],
"state_transitions": [],
"side_effects_and_privileges": ["rebuild and replace project-owned production containers with exact merged source", "write non-secret deployment manifest to local profile and private NAS monitoring directory"],
"persistence_and_migrations": ["non-secret external deployment manifest only"],
"failure_retry_restart": ["manifest is published atomically only after all expected labels and digests verify", "failed verification leaves previous manifest intact", "health becomes degraded on revision or digest drift"],
"assumptions": ["docker context docker-prod is the production endpoint", "Git HEAD is merged origin/main before promotion", "docker-test remains prohibited"],
"acceptance_scenarios": ["every project-built image has the merged Git revision OCI label", "manifest records revision and exact image digests", "health verifies running labels and digests against the manifest", "production remains healthy after exact-artifact promotion"],
"required_tests": ["PowerShell parser", "task schema", "architecture preflight", "exact diff scope", "go test", "go vet", "Compose config", "production artifact confirmation", "production health", "CI", "docker-test absence"],
"terminal_gap_states": ["BLOCKED_BY_CONFLICT", "BLOCKED_BY_MISSING_CAPABILITY"]
}
+9
View File
@@ -14,6 +14,15 @@ Temporal is intentionally **not** the canonical owner of future obligations. If/
The initial stack definition is `deploy/docker-compose.prod.yml`. Portainer may deploy it from Git after the repository has been pushed.
Project-built production images receive `org.opencontainers.image.revision` and `org.opencontainers.image.source` OCI labels. Promotion must set `ASSISTANT_SOURCE_REVISION` to the full merged `origin/main` SHA before `docker compose build/up`; the default `unknown` is intentionally rejected by artifact confirmation. After all project containers are running, execute:
```powershell
$revision = git rev-parse origin/main
powershell -ExecutionPolicy Bypass -File .\deploy\windows\Confirm-DomovoyProductionArtifact.ps1 -Revision $revision -DockerContext docker-prod
```
The confirmation script accepts the promotion only when every project-built container has that revision label, then atomically records its exact Docker image ID in `%LOCALAPPDATA%\Domovoy\monitoring\deployment-manifest.json` and the private NAS monitoring directory. Production health compares every running label and image ID to this manifest and becomes degraded on drift. The manifest is non-secret operational evidence; it is not canonical Assistant state.
The first external operation adds `assistant-notification-receiver-1` on production host port `18081`. Caddy applies `deploy/caddy/assistent.caddy`, routing only `https://assistent.cin.su/delivery/*` to that port and all other paths to Creator Web on `18080`. The receiver has an isolated durable volume and no Assistant database access. Commit success means an immutable receiver receipt exists; Windows display and human reading are not Commit outcomes.
Migrations `010_capability_notification_binding.sql` and `011_commit_notification_envelope.sql` must be applied before the new runtimes start. The initial Capability binding is owner-local for `domovoy-creator-principal`, revision 1, capability `prospective-notification-delivery`, origin `https://assistent.cin.su`, delivery path `/delivery/v1/deliveries`, receipt path `/delivery/v1/receipts`, protocol `IDEMPOTENT_RECEIPT_V1`, and secret reference `domovoy-notification-write-v1`.
+6 -4
View File
@@ -21,8 +21,10 @@ Status is based on executed repository tests and Docker-stack verification on 20
Foundation V1 is eligible for freeze. Creator session authentication was also completed as DEV-000019 and does not carry Authority.
Post-foundation product work is intentionally separate:
The three original post-foundation gates are complete:
1. Choose and specify the first real external operation before adding a production Executor credential.
2. Build Creator API/UI only through validated Identity, Authority and Commit boundaries.
3. Add exact diff-to-task scope comparison to promotion CI when the first Git baseline exists.
1. `ADR-0005` specifies the first real external operation, prospective notification delivery.
2. DEV-000024 through DEV-000032 and DEV-000040 through DEV-000043 provide the authenticated Creator API/UI and external operation only through the validated Identity, Authority, Continuity, Capability and Commit boundaries.
3. `ADR-0004` and ARCH-000004 implement exact diff-to-task scope comparison.
DEV-000044 re-ran local architecture/unit checks and the production Creator-to-receiver smoke on 2026-08-16. The deployed stack and Windows operational tasks were healthy. Current product status and explicitly uncommitted future scope are recorded in `docs/PROJECT_STATUS.md`.
+29
View File
@@ -0,0 +1,29 @@
# Assistant project status
Audit date: 2026-08-16. Active audit: DEV-000044 revision 3.
## Delivered
- Foundation V1 is frozen and all exit criteria pass.
- The single Creator account authenticates at `https://assistent.cin.su`; authentication does not imply Authority.
- Creator can inspect the bounded operational view, register and satisfy Continuity prospective items, inspect action history, and explicitly request the accepted notification operation.
- `deliver-prospective-notification-v1` executes only through Capability binding, current Authority and Commit/Executor validation. Its success evidence is an immutable receiver receipt.
- Production runs on `docker.cin.su`; the project does not use the shared test Docker host.
- PostgreSQL backup/restore, encrypted off-host backup, production health monitoring and the current-user Windows notification projection are installed and healthy.
## Evidence from DEV-000044
- `go test ./...`, `go vet ./...`, architecture preflight/checks and PowerShell parsing pass.
- `Test-DomovoyProductionHealth.ps1` reports `healthy` for the production stack, HTTPS/DNS/TLS, backups and registered Windows tasks.
- `tests/creator_commit/verify-remote.ps1` passes the authenticated Creator → Commit → notification receiver receipt flow on `docker-prod`.
- The smoke harness was corrected to preserve HTTP Authorization headers under Windows PowerShell 5.1 native-command argument handling.
## Remaining work requiring a new task decision
1. **Cognitive provider:** the cognitive worker intentionally remains the deterministic fake. Selecting a real model/provider, data policy and allowed proposal contract requires a separate capability/architecture decision; it must not gain canonical mutation authority.
2. **Product breadth:** notification delivery is the only accepted real external operation. Any additional operation needs its own proposal, authority model, idempotency/reconciliation semantics and Capability binding.
3. **Artifact and Epistemics product behavior:** their ownership boundaries exist, but no product task has yet specified durable user-facing workflows for them.
DEV-000045 closes the DEV-000044 artifact-provenance finding for new promotions by stamping the merged Git revision into OCI labels, recording exact promoted image IDs outside the repository, and making production monitoring reject label or digest drift. Images that predate DEV-000045 remain historically unverifiable from the image alone.
These are explicit scope decisions, not hidden incomplete steps in DEV-000043. No production secret is stored in the repository.
+5 -10
View File
@@ -29,7 +29,7 @@ function Creator-Post([string]$url, [object]$body, [string]$token) {
try {
[IO.File]::WriteAllText($local, $json, [Text.UTF8Encoding]::new($false))
docker --context $DockerContext cp $local "${commitContainer}:$remote" | Out-Null
$result = docker --context $DockerContext exec --env "CREATOR_TEST_SESSION=$token" $commitContainer sh -c 'wget -qO- --header="Content-Type: application/json" --header="Authorization: Bearer $CREATOR_TEST_SESSION" --post-file "$1" "$2"' sh $remote $url
$result = docker --context $DockerContext exec --env "CREATOR_TEST_SESSION=$token" $commitContainer sh -c 'auth=Authorization:\ Bearer\ $CREATOR_TEST_SESSION; IFS=; wget -qO- --header=Content-Type:application/json --header=$auth --post-file=$1 $2' sh $remote $url
if ($LASTEXITCODE -ne 0) { throw "Creator POST $url failed" }
return ($result -join "`n")
} finally {
@@ -39,7 +39,7 @@ function Creator-Post([string]$url, [object]$body, [string]$token) {
}
function Creator-Get([string]$url, [string]$token) {
$result = docker --context $DockerContext exec --env "CREATOR_TEST_SESSION=$token" $commitContainer sh -c 'wget -qO- --header="Authorization: Bearer $CREATOR_TEST_SESSION" "$1"' sh $url
$result = docker --context $DockerContext exec --env "CREATOR_TEST_SESSION=$token" $commitContainer sh -c 'auth=Authorization:\ Bearer\ $CREATOR_TEST_SESSION; IFS=; wget -qO- --header=$auth $1' sh $url
if ($LASTEXITCODE -ne 0) { throw "Creator GET $url failed" }
return ($result -join "`n")
}
@@ -53,7 +53,7 @@ function Expect-CreatorStatus([string]$url, [object]$body, [string]$token, [int]
docker --context $DockerContext cp $local "${commitContainer}:$remote" | Out-Null
$previous = $ErrorActionPreference
$ErrorActionPreference = "Continue"
$result = (docker --context $DockerContext exec --env "CREATOR_TEST_SESSION=$token" $commitContainer sh -c 'wget -S -O /dev/null --header="Content-Type: application/json" --header="Authorization: Bearer $CREATOR_TEST_SESSION" --post-file "$1" "$2"' sh $remote $url 2>&1) -join "`n"
$result = (docker --context $DockerContext exec --env "CREATOR_TEST_SESSION=$token" $commitContainer sh -c 'auth=Authorization:\ Bearer\ $CREATOR_TEST_SESSION; IFS=; wget -S -O /dev/null --header=Content-Type:application/json --header=$auth --post-file=$1 $2' sh $remote $url 2>&1) -join "`n"
$ErrorActionPreference = $previous
if ($result -notmatch "HTTP/1\.[01] $status") { throw "Creator POST $url did not return $status" }
} finally {
@@ -62,12 +62,7 @@ function Expect-CreatorStatus([string]$url, [object]$body, [string]$token, [int]
}
}
$sessionResponse = docker --context $DockerContext exec $coreContainer sh -c '
token="$CREATOR_BOOTSTRAP_TOKEN"
if [ -n "$CREATOR_BOOTSTRAP_TOKEN_FILE" ]; then token=$(cat "$CREATOR_BOOTSTRAP_TOKEN_FILE"); fi
if [ -z "$token" ]; then exit 7; fi
wget -qO- --header="Authorization: Bearer $token" --post-data="" "$1/v1/creator/sessions"
' sh http://localhost:8080
$sessionResponse = docker --context $DockerContext exec $coreContainer sh -c 'token=$CREATOR_BOOTSTRAP_TOKEN; if test x$CREATOR_BOOTSTRAP_TOKEN_FILE != x; then token=$(cat $CREATOR_BOOTSTRAP_TOKEN_FILE); fi; if test x$token = x; then exit 7; fi; header=Authorization:\ Bearer\ $token; IFS=; wget -qO- --header=$header --post-data={} http://localhost:8080/v1/creator/sessions'
if ($LASTEXITCODE -ne 0) { throw "Creator session issuance failed" }
$creatorSession = ($sessionResponse | ConvertFrom-Json).Token
@@ -120,7 +115,7 @@ $notificationAction = Creator-Post http://localhost:8082/v1/creator/continuity/n
if ($notificationAction.State -ne 'SUCCEEDED' -or -not $notificationAction.ActionIntentID) { throw 'Creator notification did not produce a successful receipt' }
$notificationItem = docker --context $DockerContext exec $continuityContainer wget -qO- "http://localhost:8081/v1/prospective-items/$notificationProspective" | ConvertFrom-Json
if ($notificationItem.State -ne 'DELIVERED' -or $notificationItem.Revision -ne 1) { throw 'Notification changed Continuity canonical state' }
$receiptRaw = docker --context $DockerContext exec $receiverContainer sh -c 'token=$(tr -d "\r\n" < /run/notification-reader/token); wget -qO- --header="Authorization: Bearer $token" "$1"' sh "http://localhost:8085/delivery/v1/receipts/$($notificationAction.ActionIntentID)"
$receiptRaw = docker --context $DockerContext exec $receiverContainer sh -c 'token=$(cat /run/notification-reader/token); auth=Authorization:\ Bearer\ $token; IFS=; wget -qO- --header=$auth $1' sh "http://localhost:8085/delivery/v1/receipts/$($notificationAction.ActionIntentID)"
if ($LASTEXITCODE -ne 0) { throw 'Notification receipt lookup failed' }
$receipt = ($receiptRaw -join "`n") | ConvertFrom-Json
if ($receipt.action_intent_id -ne $notificationAction.ActionIntentID -or $receipt.request_digest -notmatch '^[0-9a-f]{64}$') { throw 'Notification receipt evidence mismatch' }