$ErrorActionPreference = 'Stop' $PSNativeCommandUseErrorActionPreference = $true $projectRoot = Split-Path -Parent $PSScriptRoot Set-Location -LiteralPath $projectRoot $api = 'http://localhost:58080' $deviceId = '10000000-0000-0000-0000-000000000511' $deviceSecret = [Text.Encoding]::UTF8.GetBytes('local-simulated-device-secret-32-bytes-minimum') $conversationId = $null $createKey = 'hardware-create-' + [Guid]::NewGuid().ToString('N') $deviceInternalId = (docker compose exec -T postgres psql -U voice -d voice_platform -t -A -c "SELECT id FROM device WHERE public_id='$deviceId'::uuid" | Out-String).Trim() $existingHardwareBinding = (docker compose exec -T postgres psql -U voice -d voice_platform -t -A -c "SELECT count(*) FROM hardware_agent_device_binding WHERE device_id='$deviceInternalId'::uuid" | Out-String).Trim() if ($existingHardwareBinding -ne '0') { throw 'smoke device already has a hardware binding; refusing to overwrite it' } "UPDATE device_twin SET desired_config=desired_config||'{`"voice_runtime`":`"volc_hardware_agent`",`"hardware_agent_transport`":`"high_quality`",`"minimum_sdk_version`":`"1.0.0`"}'::jsonb WHERE device_id='$deviceInternalId'::uuid; INSERT INTO hardware_agent_device_binding(tenant_id,device_id,bot_id,transport,minimum_sdk_version,status) SELECT tenant_id,id,'local-hardware-agent-bot','high_quality','1.0.0','ACTIVE' FROM device WHERE id='$deviceInternalId'::uuid;" | docker compose exec -T postgres psql -U voice -d voice_platform -v ON_ERROR_STOP=1 -q | Out-Null function Invoke-SignedPost([string]$Path, [object]$Payload, [string]$IdempotencyKey = '') { $body = $Payload | ConvertTo-Json -Depth 12 -Compress $bodyBytes = [Text.Encoding]::UTF8.GetBytes($body) $sha = [Security.Cryptography.SHA256]::Create() try { $digest = ([BitConverter]::ToString($sha.ComputeHash($bodyBytes))).Replace('-','').ToLowerInvariant() } finally { $sha.Dispose() } $timestamp = [DateTime]::UtcNow.ToString('yyyy-MM-ddTHH:mm:ssZ') $requestNonce = [Guid]::NewGuid().ToString('N') $canonical = "POST`n$Path`n$timestamp`n$requestNonce`n$digest" $hmac = [Security.Cryptography.HMACSHA256]::new($deviceSecret) try { $signature = [Convert]::ToBase64String($hmac.ComputeHash([Text.Encoding]::UTF8.GetBytes($canonical))).TrimEnd('=').Replace('+','-').Replace('/','_') } finally { $hmac.Dispose() } $headers = @{ 'X-Device-Id'=$deviceId; 'X-Device-Timestamp'=$timestamp; 'X-Device-Nonce'=$requestNonce 'X-Device-Content-SHA256'=$digest; 'X-Device-Signature'=$signature } if ($IdempotencyKey) { $headers['Idempotency-Key'] = $IdempotencyKey } return (Invoke-RestMethod -Method Post -Uri ($api+$Path) -ContentType 'application/json' -Headers $headers -Body $body).data } try { $baseCapabilities = @{hardware_agent=$true;sdk_version='1.2.0';transports=@('high_quality','low_load');input_audio_format='pcm_s16le_16000_mono';output_audio_format='pcm_s16le_24000_mono';server_event_callback=$true;transcript_upload=$true;dynamic_session_config=$true;credential_rotation=$true;near_pcm_recording=$false;far_pcm_recording=$false} $oldCapabilities = $baseCapabilities.Clone() $oldCapabilities.sdk_version = '0.9.0' try { Invoke-SignedPost '/device/v3/agent-sessions' @{ device_time=[DateTime]::UtcNow.ToString('o');monotonic_ms=1;config_revision=2;capabilities=$oldCapabilities network=@{type='local-smoke'};requested_goal='open_conversation' } ('hardware-old-sdk-'+[Guid]::NewGuid().ToString('N')) | Out-Null throw 'old hardware SDK unexpectedly passed the admission gate' } catch { if ($_.Exception.Response.StatusCode.value__ -ne 426) { throw } } $created = Invoke-SignedPost '/device/v3/agent-sessions' @{ device_time=[DateTime]::UtcNow.ToString('o'); monotonic_ms=1; config_revision=2 capabilities=$baseCapabilities network=@{type='local-smoke'}; requested_goal='open_conversation' } $createKey $conversationId = $created.conversation_id if ($created.status -ne 'SESSION_READY' -or $created.runtime_type -ne 'volc_hardware_agent') { throw 'hardware prefetch state is invalid' } if (-not $created.runtime.session_nonce -or -not $created.runtime.config_digest -or -not $created.session_config.authorized_context -or -not $created.session_config.tool_url -or -not $created.session_config.tool_scope_token) { throw 'hardware frozen configuration or scoped memory tool is incomplete' } if ($created.privacy.retention_mode -eq 'transcript_and_audio' -or $created.privacy.record_near -or $created.privacy.record_far -or $created.privacy.upload_allowed) { throw 'hardware privacy was not downgraded for missing dual PCM recording capability' } $common = @{ provider_session_ref=$created.runtime.provider_session_ref; session_nonce=$created.runtime.session_nonce } try { Invoke-SignedPost "/device/v3/agent-sessions/$conversationId/events" ($common + @{ event_id='bad-start-'+[Guid]::NewGuid().ToString('N');sequence_no=1;event_type='session_started';occurred_at=[DateTime]::UtcNow.ToString('o');provider_connection_ref='volc-smoke-session';bot_id=$created.runtime.bot_id;transport=$created.runtime.transport;config_digest=$created.runtime.config_digest;dynamic_config_applied=$false }) | Out-Null throw 'session unexpectedly activated before dynamic configuration was applied' } catch { if ($_.Exception.Response.StatusCode.value__ -ne 400) { throw } } $started = Invoke-SignedPost "/device/v3/agent-sessions/$conversationId/events" ($common + @{ event_id='start-'+[Guid]::NewGuid().ToString('N');sequence_no=1;event_type='session_started';occurred_at=[DateTime]::UtcNow.ToString('o');provider_connection_ref='volc-smoke-session';bot_id=$created.runtime.bot_id;transport=$created.runtime.transport;config_digest=$created.runtime.config_digest;dynamic_config_applied=$true }) if ($started.status -ne 'ACTIVE' -or $started.action -ne 'continue') { throw 'hardware start event did not activate conversation' } $toolResult = Invoke-RestMethod -Method Post -Uri ($api+'/internal/v1/tools/memory-search') -ContentType 'application/json' -Headers @{Authorization='Bearer '+$created.session_config.tool_scope_token} -Body '{"query":"today preference","limit":1}' if ($null -eq $toolResult.data.items) { throw 'hardware scoped memory tool did not accept the active hardware session' } $now = [DateTime]::UtcNow $turns = Invoke-SignedPost "/device/v3/agent-sessions/$conversationId/turns:batch" @{ turns=@(@{turn_key='hardware-smoke-1';sequence_no=1;speaker='subject';start_at=$now.ToString('o');end_at=$now.AddSeconds(1).ToString('o');final_text='I feel good today';interrupted=$false}) } ('hardware-turns-'+[Guid]::NewGuid().ToString('N')) if ($turns.accepted_count -ne 1) { throw 'hardware transcript was not stored' } $stop = Invoke-SignedPost "/device/v2/conversations/$conversationId/stop" @{ reason='hardware_smoke_complete';device_time=[DateTime]::UtcNow.ToString('o');last_heartbeat_sequence=0;rtc_left=$true;recording=@{near_segments=0;far_segments=0} } ('hardware-stop-'+[Guid]::NewGuid().ToString('N')) if ($stop.status -ne 'STOPPING') { throw 'platform stop did not move hardware session to STOPPING' } $updated = Invoke-SignedPost "/device/v3/agent-sessions/$conversationId/events" ($common + @{ event_id='updated-'+[Guid]::NewGuid().ToString('N');sequence_no=2;event_type='session_updated';occurred_at=[DateTime]::UtcNow.ToString('o') }) if ($updated.status -ne 'STOPPING' -or $updated.action -ne 'stop_session') { throw 'hardware liveness response did not deliver stop_session' } $ended = Invoke-SignedPost "/device/v3/agent-sessions/$conversationId/events" ($common + @{ event_id='ended-'+[Guid]::NewGuid().ToString('N');sequence_no=3;event_type='session_ended';occurred_at=[DateTime]::UtcNow.ToString('o') }) if ($ended.status -ne 'COMPLETED' -or $ended.action -ne 'stop_session') { throw 'hardware end event did not close conversation' } $databaseState = (docker compose exec -T postgres psql -U voice -d voice_platform -t -A -F '|' -c "SELECT c.runtime_type||'|'||c.status||'|'||hs.status||'|'||hs.sdk_version||'|'||(hs.dynamic_config_applied_at IS NOT NULL)::text||'|'||count(DISTINCT ct.id)||'|'||(SELECT count(*) FROM web_event we WHERE we.resource_public_id=c.public_id AND we.event_type IN('conversation.started','conversation.completed')) FROM conversation c JOIN hardware_agent_session hs ON hs.conversation_id=c.id LEFT JOIN conversation_turn ct ON ct.conversation_id=c.id WHERE c.public_id='$conversationId'::uuid GROUP BY c.runtime_type,c.status,hs.status,hs.sdk_version,hs.dynamic_config_applied_at,c.public_id" | Out-String).Trim() if ($databaseState -ne 'volc_hardware_agent|COMPLETED|COMPLETED|1.2.0|true|1|2') { throw "hardware database/SaaS event state is invalid: $databaseState" } Write-Host "hardware agent smoke PASS: $conversationId" } finally { if ($conversationId -match '^[0-9a-fA-F-]{36}$') { @" BEGIN; CREATE TEMP TABLE hc AS SELECT id,context_snapshot_id,consent_snapshot_id FROM conversation WHERE public_id='$conversationId'::uuid; DELETE FROM audit_event WHERE resource_id IN(SELECT id FROM hc); DELETE FROM web_event WHERE resource_public_id='$conversationId'::uuid; DELETE FROM idempotency_record WHERE resource_id IN(SELECT id FROM hc) OR idempotency_key='$createKey'; DELETE FROM conversation_turn WHERE conversation_id IN(SELECT id FROM hc); DELETE FROM conversation_memory_use WHERE conversation_id IN(SELECT id FROM hc); DELETE FROM memory_tool_call WHERE conversation_id IN(SELECT id FROM hc); DELETE FROM hardware_agent_event WHERE conversation_id IN(SELECT id FROM hc); DELETE FROM hardware_agent_session WHERE conversation_id IN(SELECT id FROM hc); DELETE FROM conversation_usage_budget WHERE conversation_id IN(SELECT id FROM hc); DELETE FROM outbox_event WHERE aggregate_id IN(SELECT id FROM hc); DELETE FROM conversation_state_history WHERE conversation_id IN(SELECT id FROM hc); DELETE FROM conversation WHERE id IN(SELECT id FROM hc); DELETE FROM context_snapshot WHERE id IN(SELECT context_snapshot_id FROM hc); DELETE FROM consent_snapshot WHERE id IN(SELECT consent_snapshot_id FROM hc); COMMIT; "@ | docker compose exec -T postgres psql -U voice -d voice_platform -v ON_ERROR_STOP=1 -q | Out-Null } if ($deviceInternalId -match '^[0-9a-fA-F-]{36}$') { "DELETE FROM hardware_agent_device_binding WHERE device_id='$deviceInternalId'::uuid; UPDATE device_twin SET desired_config=desired_config-'voice_runtime'-'hardware_agent_transport'-'minimum_sdk_version' WHERE device_id='$deviceInternalId'::uuid;" | docker compose exec -T postgres psql -U voice -d voice_platform -v ON_ERROR_STOP=1 -q | Out-Null } }