[CmdletBinding()] param( [string]$PostgresBin = 'D:\pgsql17\bin' ) Set-StrictMode -Version 3.0 $ErrorActionPreference = 'Stop' $server = $null $pgStarted = $false $testRoot = Join-Path ([IO.Path]::GetTempPath()) ('yovision-bell-131-' + [guid]::NewGuid().ToString('N')) $pgData = Join-Path $testRoot 'postgres' $pgLog = Join-Path $testRoot 'postgres.log' $pgCtlOut = Join-Path $testRoot 'pg-ctl.out.log' $pgCtlErr = Join-Path $testRoot 'pg-ctl.err.log' $serverOut = Join-Path $testRoot 'bell.out.log' $serverErr = Join-Path $testRoot 'bell.err.log' $serverExe = Join-Path $testRoot 'bell-server.exe' $serverRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\server')).Path function Get-FreeTcpPort { $listener = [Net.Sockets.TcpListener]::new([Net.IPAddress]::Loopback, 0) try { $listener.Start() return ([Net.IPEndPoint]$listener.LocalEndpoint).Port } finally { $listener.Stop() } } function Wait-Tcp([int]$Port, [bool]$Open, [int]$Attempts = 120) { for ($attempt = 0; $attempt -lt $Attempts; $attempt++) { $client = [Net.Sockets.TcpClient]::new() try { $connected = $client.ConnectAsync('127.0.0.1', $Port).Wait(250) -and $client.Connected } catch { $connected = $false } finally { $client.Dispose() } if ($connected -eq $Open) { return } Start-Sleep -Milliseconds 250 } throw "TCP port $Port did not reach open=$Open" } function Wait-Health([string]$BaseUrl) { for ($attempt = 0; $attempt -lt 100; $attempt++) { try { $health = Invoke-RestMethod -Uri "$BaseUrl/healthz" -TimeoutSec 2 -NoProxy if ($health.status -eq 'ok' -and $health.service -eq 'bell') { return } } catch {} Start-Sleep -Milliseconds 300 } throw 'Bell health endpoint did not become ready' } function Start-Bell([string]$BaseUrl, [string]$Config = 'config/settings.demo.yml') { $script:server = Start-Process -FilePath $serverExe -ArgumentList @('server', '-c', $Config) -WorkingDirectory $serverRoot -RedirectStandardOutput $serverOut -RedirectStandardError $serverErr -WindowStyle Hidden -PassThru Wait-Health $BaseUrl } function Stop-Bell { if ($null -ne $script:server -and -not $script:server.HasExited) { Stop-Process -Id $script:server.Id -Force $script:server.WaitForExit(5000) | Out-Null } $script:server = $null } New-Item -ItemType Directory -Path $testRoot | Out-Null $pgPort = Get-FreeTcpPort $bellPort = Get-FreeTcpPort $baseUrl = "http://127.0.0.1:$bellPort" $database = 'bell_131' $adminPassword = [guid]::NewGuid().ToString('N') try { foreach ($required in @('initdb.exe', 'pg_ctl.exe', 'createdb.exe', 'psql.exe')) { $path = Join-Path $PostgresBin $required if (-not (Test-Path -LiteralPath $path -PathType Leaf)) { throw "Missing PostgreSQL tool: $path" } } & (Join-Path $PostgresBin 'initdb.exe') -D $pgData -U postgres -A trust --encoding=UTF8 --no-locale | Out-Null if ($LASTEXITCODE -ne 0) { throw 'isolated PostgreSQL initdb failed' } $pgArguments = "-D `"$pgData`" -l `"$pgLog`" -o `"-p $pgPort -h 127.0.0.1`" start" Start-Process -FilePath (Join-Path $PostgresBin 'pg_ctl.exe') -ArgumentList $pgArguments -RedirectStandardOutput $pgCtlOut -RedirectStandardError $pgCtlErr -WindowStyle Hidden | Out-Null Wait-Tcp -Port $pgPort -Open $true $pgStarted = $true & (Join-Path $PostgresBin 'createdb.exe') -h 127.0.0.1 -p $pgPort -U postgres $database if ($LASTEXITCODE -ne 0) { throw 'isolated Bell database creation failed' } $env:GOTOOLCHAIN = 'go1.26.5' $env:BELL_DATABASE_URL = "host=127.0.0.1 port=$pgPort user=postgres dbname=$database sslmode=disable" $env:BELL_JWT_SECRET = [guid]::NewGuid().ToString('N') + [guid]::NewGuid().ToString('N') $env:BELL_BOOTSTRAP_USERNAME = 'bell_131_admin' $env:BELL_BOOTSTRAP_PASSWORD = $adminPassword $env:BELL_HOST = '127.0.0.1' $env:BELL_PORT = $bellPort.ToString() $env:BELL_SYNTHETIC_EVENTS_ENABLED = 'true' Push-Location $serverRoot try { go run . migrate -c config/settings.demo.yml *> (Join-Path $testRoot 'migrate.log') if ($LASTEXITCODE -ne 0) { throw 'Bell migration failed' } go build -o $serverExe . if ($LASTEXITCODE -ne 0) { throw 'Bell build failed' } } finally { Pop-Location } Start-Bell $baseUrl $unauthorized = Invoke-RestMethod -Method Post -Uri "$baseUrl/api/v1/bell/synthetic-events" -ContentType 'application/json' -Body '{}' -TimeoutSec 5 -NoProxy if ([int]$unauthorized.code -ne 401) { throw "unauthenticated request returned code $($unauthorized.code)" } $loginBody = @{ username = 'bell_131_admin'; password = $adminPassword; code = '0'; uuid = '0' } | ConvertTo-Json -Compress $login = Invoke-RestMethod -Method Post -Uri "$baseUrl/api/v1/login" -ContentType 'application/json' -Body $loginBody -TimeoutSec 5 -NoProxy if ([int]$login.code -ne 200 -or [string]::IsNullOrWhiteSpace($login.token)) { throw 'Bell login failed' } $headers = @{ Authorization = "Bearer $($login.token)" } $occurredAt = '2026-08-29T00:00:00Z' $eventBody = @{ sourceEventId = 'acceptance-001'; eventType = 'danger_area_entered'; occurredAt = $occurredAt location = '东门'; severity = 'high'; evidenceRef = 'evidence/acceptance-001' attributes = @{ rule = 'area-01'; target = 'anonymous' } } | ConvertTo-Json -Depth 6 -Compress $created = Invoke-RestMethod -Method Post -Uri "$baseUrl/api/v1/bell/synthetic-events" -Headers $headers -ContentType 'application/json; charset=utf-8' -Body $eventBody -TimeoutSec 10 -NoProxy if ([int]$created.code -ne 200 -or $created.data.duplicate -ne $false) { throw 'first synthetic event was not accepted as new' } $eventId = [string]$created.data.event.id $receiptId = [string]$created.data.receipt.id if ([string]::IsNullOrWhiteSpace($eventId) -or [string]::IsNullOrWhiteSpace($receiptId)) { throw 'event or receipt id missing' } $replay = Invoke-RestMethod -Method Post -Uri "$baseUrl/api/v1/bell/synthetic-events" -Headers $headers -ContentType 'application/json; charset=utf-8' -Body $eventBody -TimeoutSec 10 -NoProxy if ([int]$replay.code -ne 200 -or $replay.data.duplicate -ne $true -or $replay.data.event.id -ne $eventId) { throw 'idempotent replay failed' } $concurrentBody = @{ sourceEventId = 'acceptance-concurrent'; eventType = 'danger_area_entered'; occurredAt = $occurredAt location = '西门'; severity = 'medium'; evidenceRef = 'evidence/acceptance-concurrent' attributes = @{ rule = 'area-02'; target = 'anonymous' } } | ConvertTo-Json -Depth 6 -Compress $authHeader = [string]$headers.Authorization $concurrent = 1..12 | ForEach-Object -Parallel { $requestHeaders = @{ Authorization = $using:authHeader } Invoke-RestMethod -Method Post -Uri "$using:baseUrl/api/v1/bell/synthetic-events" -Headers $requestHeaders -ContentType 'application/json; charset=utf-8' -Body $using:concurrentBody -TimeoutSec 20 -NoProxy } -ThrottleLimit 12 $concurrentIds = @($concurrent | ForEach-Object { [string]$_.data.event.id } | Sort-Object -Unique) $newCount = @($concurrent | Where-Object { $_.data.duplicate -eq $false }).Count if ($concurrent.Count -ne 12 -or $concurrentIds.Count -ne 1 -or $newCount -ne 1) { throw 'concurrent idempotency did not converge to one Event' } $conflictObject = $eventBody | ConvertFrom-Json $conflictObject.severity = 'critical' $conflict = Invoke-RestMethod -Method Post -Uri "$baseUrl/api/v1/bell/synthetic-events" -Headers $headers -ContentType 'application/json; charset=utf-8' -Body ($conflictObject | ConvertTo-Json -Depth 6 -Compress) -TimeoutSec 10 -NoProxy if ([int]$conflict.code -ne 409) { throw "idempotency conflict returned code $($conflict.code)" } $malformed = Invoke-RestMethod -Method Post -Uri "$baseUrl/api/v1/bell/synthetic-events" -Headers $headers -ContentType 'application/json' -Body '{' -TimeoutSec 10 -NoProxy if ([int]$malformed.code -ne 400) { throw "malformed request returned code $($malformed.code)" } $oversized = @{ sourceEventId='too-large'; eventType='test'; occurredAt=$occurredAt; location='lab'; severity='low'; attributes=@{ blob=('x' * 70000) } } | ConvertTo-Json -Depth 5 -Compress $tooLarge = Invoke-RestMethod -Method Post -Uri "$baseUrl/api/v1/bell/synthetic-events" -Headers $headers -ContentType 'application/json' -Body $oversized -TimeoutSec 10 -NoProxy if ([int]$tooLarge.code -ne 400) { throw "oversized request returned code $($tooLarge.code)" } $psql = Join-Path $PostgresBin 'psql.exe' $counts = & $psql -h 127.0.0.1 -p $pgPort -U postgres -d $database -Atc "select count(*) from bell_events; select count(*) from bell_event_receipts; select count(*) from bell_event_ingest_audits where outcome='accepted'; select count(*) from bell_event_ingest_audits where outcome='replay'; select count(*) from bell_event_ingest_audits where outcome='conflict';" if (($counts -join ',') -ne '2,2,2,12,1') { throw "unexpected Bell fact counts: $($counts -join ',')" } $payloadAuditCount = & $psql -h 127.0.0.1 -p $pgPort -U postgres -d $database -Atc "select count(*) from sys_opera_log where oper_url='/api/v1/bell/synthetic-events' and (oper_param like '%sourceEventId%' or json_result like '%acceptance-001%');" if ([int]$payloadAuditCount -ne 0) { throw 'generic GoAdmin audit retained synthetic event payload' } & $psql -h 127.0.0.1 -p $pgPort -U postgres -d $database -v ON_ERROR_STOP=1 -c "update bell_events set severity='low' where id='$eventId'" 2>$null | Out-Null if ($LASTEXITCODE -eq 0) { throw 'immutable Event update unexpectedly succeeded' } & $psql -h 127.0.0.1 -p $pgPort -U postgres -d $database -v ON_ERROR_STOP=1 -c "delete from bell_event_receipts where id='$receiptId'" 2>$null | Out-Null if ($LASTEXITCODE -eq 0) { throw 'immutable Receipt delete unexpectedly succeeded' } Stop-Bell Start-Bell $baseUrl $afterRestart = Invoke-RestMethod -Uri "$baseUrl/api/v1/bell/events/$eventId" -Headers $headers -TimeoutSec 10 -NoProxy if ([int]$afterRestart.code -ne 200 -or $afterRestart.data.id -ne $eventId) { throw 'Event was not readable after Bell restart' } Stop-Bell Start-Bell $baseUrl 'config/settings.yml' $productionSynthetic = Invoke-WebRequest -Method Post -Uri "$baseUrl/api/v1/bell/synthetic-events" -ContentType 'application/json' -Body '{}' -TimeoutSec 10 -NoProxy -SkipHttpErrorCheck if ([int]$productionSynthetic.StatusCode -ne 404) { throw "production synthetic route returned HTTP $($productionSynthetic.StatusCode)" } Write-Output "BELL_131_SMOKE events=2 receipts=2 concurrent=12 audit=redacted immutable=true restart=true production_synthetic=404" } finally { Stop-Bell if ($pgStarted) { & (Join-Path $PostgresBin 'pg_ctl.exe') -D $pgData -m fast stop *> (Join-Path $testRoot 'pg-stop.log') } foreach ($name in @('BELL_DATABASE_URL','BELL_JWT_SECRET','BELL_BOOTSTRAP_USERNAME','BELL_BOOTSTRAP_PASSWORD','BELL_HOST','BELL_PORT','BELL_SYNTHETIC_EVENTS_ENABLED')) { Remove-Item "Env:$name" -ErrorAction SilentlyContinue } Write-Verbose "Bell #131 temporary artifacts: $testRoot" }