chore: establish local platform baseline
This commit is contained in:
@@ -0,0 +1,257 @@
|
||||
param([switch]$KeepFixture)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$PSNativeCommandUseErrorActionPreference = $true
|
||||
|
||||
$projectRoot = Split-Path -Parent $PSScriptRoot
|
||||
Set-Location -LiteralPath $projectRoot
|
||||
|
||||
$api = 'http://localhost:58080'
|
||||
$tokenUrl = 'http://localhost:58180/realms/voice-local/protocol/openid-connect/token'
|
||||
$tenantId = '10000000-0000-0000-0000-000000000001'
|
||||
$householdId = '10000000-0000-0000-0000-000000000201'
|
||||
$subjectId = '10000000-0000-0000-0000-000000000401'
|
||||
$subjectPublicId = '10000000-0000-0000-0000-000000000411'
|
||||
$deviceInternalId = '10000000-0000-0000-0000-000000000501'
|
||||
$deviceId = '10000000-0000-0000-0000-000000000511'
|
||||
$ownerId = '10000000-0000-0000-0000-000000000101'
|
||||
$ownerPublicId = '10000000-0000-0000-0000-000000000111'
|
||||
$deviceSecret = [Text.Encoding]::UTF8.GetBytes('local-simulated-device-secret-32-bytes-minimum')
|
||||
$conversationId = [Guid]::NewGuid().ToString()
|
||||
$conversationInternalId = [Guid]::NewGuid().ToString()
|
||||
$consentId = [Guid]::NewGuid().ToString()
|
||||
$snapshotId = [Guid]::NewGuid().ToString()
|
||||
$contextId = [Guid]::NewGuid().ToString()
|
||||
$nearFile = Join-Path ([IO.Path]::GetTempPath()) ('media-near-' + [Guid]::NewGuid().ToString('N') + '.bin')
|
||||
$farFile = Join-Path ([IO.Path]::GetTempPath()) ('media-far-' + [Guid]::NewGuid().ToString('N') + '.bin')
|
||||
$objectRefs = @()
|
||||
|
||||
function Get-Sha256Hex([byte[]]$Bytes) {
|
||||
$sha = [Security.Cryptography.SHA256]::Create()
|
||||
try { return ([BitConverter]::ToString($sha.ComputeHash($Bytes))).Replace('-', '').ToLowerInvariant() }
|
||||
finally { $sha.Dispose() }
|
||||
}
|
||||
|
||||
function Get-AccessToken([string]$UserName) {
|
||||
return (Invoke-RestMethod -Method Post -Uri $tokenUrl -ContentType 'application/x-www-form-urlencoded' -Body @{
|
||||
grant_type = 'password'; client_id = 'voice-platform-web'; username = $UserName; password = 'LocalOnly123!'; scope = 'openid'
|
||||
}).access_token
|
||||
}
|
||||
|
||||
function New-SyntheticWav([byte]$Marker) {
|
||||
$sampleRate = 8000
|
||||
$channels = 1
|
||||
$bitsPerSample = 16
|
||||
$sampleCount = 800
|
||||
$dataSize = $sampleCount * $channels * ($bitsPerSample / 8)
|
||||
$stream = [IO.MemoryStream]::new()
|
||||
$writer = [IO.BinaryWriter]::new($stream)
|
||||
try {
|
||||
$writer.Write([Text.Encoding]::ASCII.GetBytes('RIFF'))
|
||||
$writer.Write([uint32](36 + $dataSize))
|
||||
$writer.Write([Text.Encoding]::ASCII.GetBytes('WAVE'))
|
||||
$writer.Write([Text.Encoding]::ASCII.GetBytes('fmt '))
|
||||
$writer.Write([uint32]16)
|
||||
$writer.Write([uint16]1)
|
||||
$writer.Write([uint16]$channels)
|
||||
$writer.Write([uint32]$sampleRate)
|
||||
$writer.Write([uint32]($sampleRate * $channels * ($bitsPerSample / 8)))
|
||||
$writer.Write([uint16]($channels * ($bitsPerSample / 8)))
|
||||
$writer.Write([uint16]$bitsPerSample)
|
||||
$writer.Write([Text.Encoding]::ASCII.GetBytes('data'))
|
||||
$writer.Write([uint32]$dataSize)
|
||||
$samples = [byte[]]::new($dataSize)
|
||||
$samples[0] = $Marker
|
||||
$writer.Write($samples)
|
||||
$writer.Flush()
|
||||
return $stream.ToArray()
|
||||
}
|
||||
finally { $writer.Dispose(); $stream.Dispose() }
|
||||
}
|
||||
|
||||
function Invoke-DeviceRequest([string]$Method, [string]$Path, [string]$Body = '', [hashtable]$ExtraHeaders = @{}) {
|
||||
$bodyBytes = [Text.Encoding]::UTF8.GetBytes($Body)
|
||||
$digest = Get-Sha256Hex $bodyBytes
|
||||
$timestamp = [DateTime]::UtcNow.ToString('yyyy-MM-ddTHH:mm:ssZ')
|
||||
$nonce = [Guid]::NewGuid().ToString('N')
|
||||
$canonical = "$Method`n$Path`n$timestamp`n$nonce`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' = $nonce
|
||||
'X-Device-Content-SHA256' = $digest
|
||||
'X-Device-Signature' = $signature
|
||||
}
|
||||
foreach ($entry in $ExtraHeaders.GetEnumerator()) { $headers[$entry.Key] = $entry.Value }
|
||||
$arguments = @{ UseBasicParsing = $true; Method = $Method; Uri = $api + $Path; Headers = $headers }
|
||||
if ($Body) { $arguments.ContentType = 'application/json'; $arguments.Body = $Body }
|
||||
return Invoke-WebRequest @arguments
|
||||
}
|
||||
|
||||
function Assert-HttpStatus([scriptblock]$Action, [int]$ExpectedStatus) {
|
||||
try {
|
||||
$response = & $Action
|
||||
if ($response.StatusCode -ne $ExpectedStatus) { throw "Expected HTTP $ExpectedStatus but received $($response.StatusCode)" }
|
||||
}
|
||||
catch {
|
||||
$actualStatus = $_.Exception.Response.StatusCode.value__
|
||||
if ($actualStatus -ne $ExpectedStatus) { throw }
|
||||
}
|
||||
}
|
||||
|
||||
function Wait-Batch([string]$BatchId, [string]$ExpectedStatus) {
|
||||
for ($attempt = 0; $attempt -lt 40; $attempt++) {
|
||||
$response = Invoke-DeviceRequest 'GET' "/device/v2/upload-batches/$BatchId"
|
||||
$batch = ($response.Content | ConvertFrom-Json).data
|
||||
if ($batch.status -eq $ExpectedStatus) { return $batch }
|
||||
Start-Sleep -Milliseconds 250
|
||||
}
|
||||
throw "Media batch $BatchId did not reach $ExpectedStatus"
|
||||
}
|
||||
|
||||
try {
|
||||
$nearBytes = New-SyntheticWav 1
|
||||
$farBytes = New-SyntheticWav 2
|
||||
[IO.File]::WriteAllBytes($nearFile, $nearBytes)
|
||||
[IO.File]::WriteAllBytes($farFile, $farBytes)
|
||||
$nearDigest = Get-Sha256Hex $nearBytes
|
||||
$farDigest = Get-Sha256Hex $farBytes
|
||||
|
||||
$fixtureSql = @"
|
||||
BEGIN;
|
||||
INSERT INTO consent_record(id,public_id,tenant_id,subject_id,consent_type,scope,policy_version,notice_digest,actor_user_id,evidence_ref,granted_at)
|
||||
VALUES('$consentId'::uuid,gen_random_uuid(),'$tenantId'::uuid,'$subjectId'::uuid,'raw_audio_storage','{"scope":"synthetic_media_smoke"}'::jsonb,'local-v1',repeat('e',64),'$ownerId'::uuid,'local-synthetic://media-smoke',now());
|
||||
INSERT INTO consent_snapshot(id,public_id,tenant_id,subject_id,snapshot_version,effective_consents,source_consent_ids)
|
||||
VALUES('$snapshotId'::uuid,gen_random_uuid(),'$tenantId'::uuid,'$subjectId'::uuid,999,'{"voice_chat":true,"transcript_storage":true,"raw_audio_storage":true}'::jsonb,jsonb_build_array('$consentId'));
|
||||
INSERT INTO context_snapshot(id,public_id,tenant_id,subject_id,profile_version,consent_snapshot_id,memory_versions,prompt_bundle_version,engine_mode,content_digest,sanitized_summary)
|
||||
VALUES('$contextId'::uuid,gen_random_uuid(),'$tenantId'::uuid,'$subjectId'::uuid,1,'$snapshotId'::uuid,'[]'::jsonb,'media-smoke-v1','s2s_hybrid',repeat('f',64),'synthetic media smoke');
|
||||
INSERT INTO conversation(id,public_id,tenant_id,household_id,subject_id,device_id,consent_snapshot_id,context_snapshot_id,provider_task_ref,engine_mode,retention_mode,status,allocation_expires_at,started_at,ended_at)
|
||||
VALUES('$conversationInternalId'::uuid,'$conversationId'::uuid,'$tenantId'::uuid,'$householdId'::uuid,'$subjectId'::uuid,'$deviceInternalId'::uuid,'$snapshotId'::uuid,'$contextId'::uuid,'media-smoke-$conversationId','s2s_hybrid','transcript_and_audio','COMPLETED',now()+interval '1 hour',now()-interval '2 minutes',now()-interval '1 minute');
|
||||
INSERT INTO rtc_task(conversation_id,provider,provider_task_ref,room_id,device_rtc_user_id,agent_rtc_user_id,status,lease_expires_at,last_checked_at)
|
||||
VALUES('$conversationInternalId'::uuid,'mock','media-smoke-task-$conversationId','media-smoke-room-$conversationId','device-media-smoke','agent-media-smoke','COMPLETED',now()-interval '1 minute',now());
|
||||
COMMIT;
|
||||
"@
|
||||
$fixtureSql | docker compose exec -T postgres psql -U voice -d voice_platform -v ON_ERROR_STOP=1 -q | Out-Null
|
||||
|
||||
$segments = @(
|
||||
@{ track = 'near'; sequence_no = 0; codec = 'wav_pcm_s16le'; content_type = 'audio/wav'; duration_ms = 100; expected_size = $nearBytes.Length; expected_digest = $nearDigest },
|
||||
@{ track = 'far'; sequence_no = 0; codec = 'wav_pcm_s16le'; content_type = 'audio/wav'; duration_ms = 100; expected_size = $farBytes.Length; expected_digest = $farDigest }
|
||||
)
|
||||
$body = @{ segments = $segments } | ConvertTo-Json -Compress -Depth 5
|
||||
$idempotencyKey = 'media-smoke-' + [Guid]::NewGuid().ToString('N')
|
||||
$headers = @{ 'Idempotency-Key' = $idempotencyKey }
|
||||
$preparedResponse = Invoke-DeviceRequest 'POST' "/device/v2/conversations/$conversationId/upload-batches" $body $headers
|
||||
$prepared = ($preparedResponse.Content | ConvertFrom-Json).data
|
||||
if ($prepared.status -ne 'UPLOADING' -or $prepared.segments.Count -ne 2) { throw 'Media upload batch was not prepared' }
|
||||
$replayed = ((Invoke-DeviceRequest 'POST' "/device/v2/conversations/$conversationId/upload-batches" $body $headers).Content | ConvertFrom-Json).data
|
||||
if ($replayed.id -ne $prepared.id) { throw 'Idempotent media manifest replay created another batch' }
|
||||
$conflictSegments = @($segments[0].Clone(), $segments[1].Clone())
|
||||
$conflictSegments[0].duration_ms = 5001
|
||||
$conflictBody = @{ segments = $conflictSegments } | ConvertTo-Json -Compress -Depth 5
|
||||
Assert-HttpStatus { Invoke-DeviceRequest 'POST' "/device/v2/conversations/$conversationId/upload-batches" $conflictBody $headers } 409
|
||||
|
||||
foreach ($segment in $prepared.segments) {
|
||||
if (-not $segment.upload_url) { throw "Missing upload URL for $($segment.track)" }
|
||||
$source = if ($segment.track -eq 'near') { $nearFile } else { $farFile }
|
||||
Invoke-WebRequest -UseBasicParsing -Method Put -Uri $segment.upload_url -InFile $source -ContentType $segment.content_type | Out-Null
|
||||
}
|
||||
Invoke-DeviceRequest 'POST' "/device/v2/upload-batches/$($prepared.id)/complete" | Out-Null
|
||||
$completed = Wait-Batch $prepared.id 'COMPLETED'
|
||||
if ($completed.verified_count -ne 2 -or ($completed.segments | Where-Object { $_.status -ne 'VERIFIED' -or -not $_.local_delete_after })) {
|
||||
throw 'Verified media batch did not expose delayed local deletion evidence'
|
||||
}
|
||||
|
||||
$ownerHeaders = @{ Authorization = 'Bearer ' + (Get-AccessToken 'owner') }
|
||||
$editorHeaders = @{ Authorization = 'Bearer ' + (Get-AccessToken 'editor') }
|
||||
$nearSegment = $completed.segments | Where-Object { $_.track -eq 'near' } | Select-Object -First 1
|
||||
$farSegment = $completed.segments | Where-Object { $_.track -eq 'far' } | Select-Object -First 1
|
||||
Assert-HttpStatus { Invoke-DeviceRequest 'POST' "/device/v2/media-segments/$($nearSegment.id)/local-delete-ack" (@{ status = 'DELETED' } | ConvertTo-Json -Compress) } 409
|
||||
$failedDelete = ((Invoke-DeviceRequest 'POST' "/device/v2/media-segments/$($nearSegment.id)/local-delete-ack" (@{ status = 'FAILED'; error_code = 'LOCAL_FILE_BUSY' } | ConvertTo-Json -Compress)).Content | ConvertFrom-Json).data
|
||||
if ($failedDelete.local_delete_status -ne 'FAILED' -or $failedDelete.local_delete_error_code -ne 'LOCAL_FILE_BUSY') { throw 'Device local deletion failure receipt was not persisted' }
|
||||
"UPDATE media_segment SET local_delete_after=now()-interval '1 second' WHERE public_id IN ('$($nearSegment.id)'::uuid,'$($farSegment.id)'::uuid);" | docker compose exec -T postgres psql -U voice -d voice_platform -v ON_ERROR_STOP=1 -q | Out-Null
|
||||
$deletedReceipt = ((Invoke-DeviceRequest 'POST' "/device/v2/media-segments/$($nearSegment.id)/local-delete-ack" (@{ status = 'DELETED' } | ConvertTo-Json -Compress)).Content | ConvertFrom-Json).data
|
||||
$replayedReceipt = ((Invoke-DeviceRequest 'POST' "/device/v2/media-segments/$($nearSegment.id)/local-delete-ack" (@{ status = 'DELETED' } | ConvertTo-Json -Compress)).Content | ConvertFrom-Json).data
|
||||
if ($deletedReceipt.local_delete_status -ne 'DELETED' -or -not $deletedReceipt.local_delete_reported_at -or $replayedReceipt.local_delete_reported_at -ne $deletedReceipt.local_delete_reported_at) { throw 'Device local deletion receipt was not successful and idempotent' }
|
||||
$farDeletedReceipt = ((Invoke-DeviceRequest 'POST' "/device/v2/media-segments/$($farSegment.id)/local-delete-ack" (@{ status = 'DELETED' } | ConvertTo-Json -Compress)).Content | ConvertFrom-Json).data
|
||||
if ($farDeletedReceipt.local_delete_status -ne 'DELETED') { throw 'Second track local deletion receipt was not persisted' }
|
||||
$accessBody = @{ action = 'PLAY'; purpose = 'synthetic playback verification' } | ConvertTo-Json -Compress
|
||||
Assert-HttpStatus { Invoke-WebRequest -UseBasicParsing -Method Post -Uri "$api/api/v1/media-segments/$($nearSegment.id)/access" -Headers $ownerHeaders -ContentType 'application/json' -Body $accessBody } 403
|
||||
$grantBody = @{
|
||||
grantee_user_id = $ownerPublicId
|
||||
permission = 'PLAY'
|
||||
purpose = 'synthetic media smoke'
|
||||
expires_at = [DateTime]::UtcNow.AddHours(1).ToString('o')
|
||||
} | ConvertTo-Json -Compress
|
||||
Assert-HttpStatus { Invoke-WebRequest -UseBasicParsing -Method Post -Uri "$api/api/v1/subjects/$subjectPublicId/recording-access-grants" -Headers $editorHeaders -ContentType 'application/json' -Body $grantBody } 403
|
||||
Assert-HttpStatus { Invoke-WebRequest -UseBasicParsing -Method Get -Uri "$api/api/v1/subjects/$subjectPublicId/recording-access-grants" -Headers $editorHeaders } 403
|
||||
$playGrant = ((Invoke-WebRequest -UseBasicParsing -Method Post -Uri "$api/api/v1/subjects/$subjectPublicId/recording-access-grants" -Headers $ownerHeaders -ContentType 'application/json' -Body $grantBody).Content | ConvertFrom-Json).data
|
||||
$grantList = ((Invoke-WebRequest -UseBasicParsing -Method Get -Uri "$api/api/v1/subjects/$subjectPublicId/recording-access-grants" -Headers $ownerHeaders).Content | ConvertFrom-Json).data.items
|
||||
if (-not ($grantList | Where-Object { $_.id -eq $playGrant.id })) { throw 'Owner grant list did not return the newly created explicit grant' }
|
||||
$mediaView = ((Invoke-WebRequest -UseBasicParsing -Method Get -Uri "$api/api/v1/conversations/$conversationId/media" -Headers $ownerHeaders).Content | ConvertFrom-Json).data
|
||||
if (-not $mediaView.can_play -or $mediaView.can_download -or $mediaView.segments.Count -ne 2) { throw 'Recording media view did not apply independent play/download grants' }
|
||||
$playAccess = ((Invoke-WebRequest -UseBasicParsing -Method Post -Uri "$api/api/v1/media-segments/$($nearSegment.id)/access" -Headers $ownerHeaders -ContentType 'application/json' -Body $accessBody).Content | ConvertFrom-Json).data
|
||||
$playedBytes = (Invoke-WebRequest -UseBasicParsing -Method Get -Uri $playAccess.access_url).Content
|
||||
if ($playedBytes.Length -ne $nearBytes.Length) { throw 'Short-lived playback URL did not return the expected object' }
|
||||
$downloadBody = @{ action = 'DOWNLOAD'; purpose = 'synthetic download verification' } | ConvertTo-Json -Compress
|
||||
Assert-HttpStatus { Invoke-WebRequest -UseBasicParsing -Method Post -Uri "$api/api/v1/media-segments/$($nearSegment.id)/access" -Headers $ownerHeaders -ContentType 'application/json' -Body $downloadBody } 403
|
||||
$downloadGrantBody = @{
|
||||
grantee_user_id = $ownerPublicId
|
||||
permission = 'DOWNLOAD'
|
||||
purpose = 'synthetic media smoke'
|
||||
expires_at = [DateTime]::UtcNow.AddHours(1).ToString('o')
|
||||
} | ConvertTo-Json -Compress
|
||||
Invoke-WebRequest -UseBasicParsing -Method Post -Uri "$api/api/v1/subjects/$subjectPublicId/recording-access-grants" -Headers $ownerHeaders -ContentType 'application/json' -Body $downloadGrantBody | Out-Null
|
||||
$downloadAccess = ((Invoke-WebRequest -UseBasicParsing -Method Post -Uri "$api/api/v1/media-segments/$($nearSegment.id)/access" -Headers $ownerHeaders -ContentType 'application/json' -Body $downloadBody).Content | ConvertFrom-Json).data
|
||||
if (-not $downloadAccess.access_url) { throw 'Independent download grant did not issue a short-lived URL' }
|
||||
Invoke-WebRequest -UseBasicParsing -Method Post -Uri "$api/api/v1/recording-access-grants/$($playGrant.id)/revoke" -Headers $ownerHeaders | Out-Null
|
||||
Assert-HttpStatus { Invoke-WebRequest -UseBasicParsing -Method Post -Uri "$api/api/v1/media-segments/$($nearSegment.id)/access" -Headers $ownerHeaders -ContentType 'application/json' -Body $accessBody } 403
|
||||
|
||||
$badBody = @{ segments = @(@{ track = 'near'; sequence_no = 1; codec = 'wav_pcm_s16le'; content_type = 'audio/wav'; duration_ms = 100; expected_size = $nearBytes.Length; expected_digest = (('0' * 64) -join '') }) } | ConvertTo-Json -Compress -Depth 5
|
||||
$badHeaders = @{ 'Idempotency-Key' = ('media-smoke-bad-' + [Guid]::NewGuid().ToString('N')) }
|
||||
$badPrepared = ((Invoke-DeviceRequest 'POST' "/device/v2/conversations/$conversationId/upload-batches" $badBody $badHeaders).Content | ConvertFrom-Json).data
|
||||
Invoke-WebRequest -UseBasicParsing -Method Put -Uri $badPrepared.segments[0].upload_url -InFile $nearFile -ContentType 'audio/wav' | Out-Null
|
||||
Invoke-DeviceRequest 'POST' "/device/v2/upload-batches/$($badPrepared.id)/complete" | Out-Null
|
||||
$failed = Wait-Batch $badPrepared.id 'FAILED'
|
||||
if ($failed.rejected_count -ne 1 -or $failed.segments[0].last_error_code -ne 'DIGEST_MISMATCH') { throw 'Wrong digest was not rejected deterministically' }
|
||||
|
||||
$accessCounts = (docker compose exec -T postgres psql -U voice -d voice_platform -t -A -F '|' -c "SELECT count(*) FILTER(WHERE result='SUCCESS'),count(*) FILTER(WHERE result='DENIED') FROM media_access_log WHERE conversation_id='$conversationInternalId'::uuid" | Out-String).Trim()
|
||||
if ($accessCounts -ne '2|3') { throw "Recording access audit counts were unexpected: $accessCounts" }
|
||||
Write-Host ('Media upload/access/delete-receipt smoke passed: ' + $prepared.id)
|
||||
}
|
||||
finally {
|
||||
if (-not $KeepFixture) {
|
||||
$refs = docker compose exec -T postgres psql -U voice -d voice_platform -t -A -c "SELECT ms.object_ref FROM media_segment ms WHERE ms.conversation_id='$conversationInternalId'::uuid" | Out-String
|
||||
$objectRefs = $refs -split "`r?`n" | Where-Object { $_.Trim() }
|
||||
$cleanupSql = @"
|
||||
BEGIN;
|
||||
DELETE FROM web_event WHERE resource_public_id IN (SELECT public_id FROM upload_batch WHERE conversation_id='$conversationInternalId'::uuid) OR resource_public_id=(SELECT public_id FROM conversation WHERE id='$conversationInternalId'::uuid);
|
||||
DELETE FROM audit_event WHERE resource_id IN (SELECT public_id FROM upload_batch WHERE conversation_id='$conversationInternalId'::uuid);
|
||||
DELETE FROM audit_event WHERE resource_id IN (SELECT public_id FROM media_segment WHERE conversation_id='$conversationInternalId'::uuid);
|
||||
DELETE FROM outbox_event WHERE aggregate_id IN (SELECT public_id FROM upload_batch WHERE conversation_id='$conversationInternalId'::uuid);
|
||||
DELETE FROM media_access_log WHERE conversation_id='$conversationInternalId'::uuid;
|
||||
DELETE FROM audit_event WHERE resource_id IN (SELECT public_id FROM recording_access_grant WHERE subject_id='$subjectId'::uuid AND purpose='synthetic media smoke');
|
||||
DELETE FROM recording_access_grant WHERE subject_id='$subjectId'::uuid AND purpose='synthetic media smoke';
|
||||
DELETE FROM upload_batch_segment WHERE upload_batch_id IN (SELECT id FROM upload_batch WHERE conversation_id='$conversationInternalId'::uuid);
|
||||
DELETE FROM media_segment WHERE conversation_id='$conversationInternalId'::uuid;
|
||||
DELETE FROM upload_batch WHERE conversation_id='$conversationInternalId'::uuid;
|
||||
DELETE FROM rtc_task WHERE conversation_id='$conversationInternalId'::uuid;
|
||||
DELETE FROM conversation WHERE id='$conversationInternalId'::uuid;
|
||||
DELETE FROM context_snapshot WHERE id='$contextId'::uuid;
|
||||
DELETE FROM consent_snapshot WHERE id='$snapshotId'::uuid;
|
||||
DELETE FROM consent_record WHERE id='$consentId'::uuid;
|
||||
COMMIT;
|
||||
"@
|
||||
$cleanupSql | docker compose exec -T postgres psql -U voice -d voice_platform -v ON_ERROR_STOP=1 -q | Out-Null
|
||||
if ($objectRefs.Count -gt 0) {
|
||||
docker compose exec -T minio mc alias set smoke http://localhost:9000 local_minio local_minio_password | Out-Null
|
||||
foreach ($objectRef in $objectRefs) { docker compose exec -T minio mc rm --force "smoke/private-exports/$($objectRef.Trim())" | Out-Null }
|
||||
}
|
||||
} else {
|
||||
Write-Host ('Visual fixture retained for conversation: ' + $conversationId)
|
||||
}
|
||||
if (Test-Path -LiteralPath $nearFile) { Remove-Item -LiteralPath $nearFile -Force }
|
||||
if (Test-Path -LiteralPath $farFile) { Remove-Item -LiteralPath $farFile -Force }
|
||||
}
|
||||
Reference in New Issue
Block a user