chore: checkpoint 第一版
ci / verify (push) Canceled after 0s

This commit is contained in:
Codex Local
2026-09-06 17:04:11 +08:00
parent a8cdec98d1
commit 0c2151ace9
659 changed files with 185855 additions and 554 deletions
@@ -0,0 +1,87 @@
param(
[string]$Version = '0.76.0-rc.hardware1',
[string]$OutputDirectory = '.deploy-artifacts'
)
$ErrorActionPreference = 'Stop'
$PSNativeCommandUseErrorActionPreference = $true
$projectRoot = (Resolve-Path -LiteralPath (Split-Path -Parent $PSScriptRoot)).Path
$outputRoot = [IO.Path]::GetFullPath((Join-Path $projectRoot $OutputDirectory))
if (-not $outputRoot.StartsWith($projectRoot + [IO.Path]::DirectorySeparatorChar, [StringComparison]::OrdinalIgnoreCase)) {
throw 'OutputDirectory must resolve inside the project workspace'
}
if ($Version -notmatch '^[0-9A-Za-z][0-9A-Za-z._-]{0,79}$') {
throw 'Version contains unsupported characters'
}
New-Item -ItemType Directory -Force -Path $outputRoot | Out-Null
$bundleName = "data-terminal-cloud-$Version"
$stagingRoot = Join-Path $outputRoot ('.staging-' + [Guid]::NewGuid().ToString('N'))
$payloadRoot = Join-Path $stagingRoot $bundleName
New-Item -ItemType Directory -Force -Path $payloadRoot | Out-Null
try {
$candidatePaths = @(& git ls-files --cached --others --exclude-standard -- `
cmd internal migrations openapi web deploy compose.yaml go.mod go.sum README.md CHANGELOG.md .env.example)
if ($LASTEXITCODE -ne 0) { throw 'git file enumeration failed' }
$candidatePaths = $candidatePaths | Where-Object {
$_ -and
$_ -notmatch '(^|/)(node_modules|dist|coverage|runtime|release)(/|$)' -and
$_ -notmatch '(^|/)\.env$' -and
$_ -notmatch '\.(pem|key|p12|pfx)$'
} | Sort-Object -Unique
if ($candidatePaths.Count -lt 20) { throw 'deployment file set is unexpectedly small' }
foreach ($relativePath in $candidatePaths) {
$sourcePath = [IO.Path]::GetFullPath((Join-Path $projectRoot $relativePath))
if (-not $sourcePath.StartsWith($projectRoot + [IO.Path]::DirectorySeparatorChar, [StringComparison]::OrdinalIgnoreCase) -or -not (Test-Path -LiteralPath $sourcePath -PathType Leaf)) {
throw "Unsafe or missing deployment source path: $relativePath"
}
$targetPath = Join-Path $payloadRoot $relativePath
New-Item -ItemType Directory -Force -Path (Split-Path -Parent $targetPath) | Out-Null
Copy-Item -LiteralPath $sourcePath -Destination $targetPath
}
$sourceCommit = (& git rev-parse HEAD).Trim()
if ($LASTEXITCODE -ne 0 -or $sourceCommit -notmatch '^[0-9a-f]{40}$') { throw 'source commit could not be resolved' }
$statusLines = @(& git status --porcelain=v1)
if ($LASTEXITCODE -ne 0) { throw 'working tree status could not be resolved' }
$files = Get-ChildItem -LiteralPath $payloadRoot -File -Recurse | Sort-Object FullName | ForEach-Object {
[ordered]@{
path = $_.FullName.Substring($payloadRoot.Length + 1).Replace('\','/')
size_bytes = $_.Length
sha256 = (Get-FileHash -LiteralPath $_.FullName -Algorithm SHA256).Hash.ToLowerInvariant()
}
}
$manifest = [ordered]@{
schema_version = 'server-deployment-bundle-v1'
candidate_version = $Version
generated_at = [DateTime]::UtcNow.ToString('o')
source_commit = $sourceCommit
working_tree_dirty = $statusLines.Count -gt 0
schema_migration_target = 52
hardware_runtime = 'volc_hardware_agent'
files = @($files)
}
$manifestPath = Join-Path $payloadRoot 'deploy-manifest.json'
[IO.File]::WriteAllText($manifestPath, (($manifest | ConvertTo-Json -Depth 8) + [Environment]::NewLine), [Text.UTF8Encoding]::new($false))
$checksumLines = Get-ChildItem -LiteralPath $payloadRoot -File -Recurse | Where-Object Name -ne 'SHA256SUMS' | Sort-Object FullName | ForEach-Object {
$relative = $_.FullName.Substring($payloadRoot.Length + 1).Replace('\','/')
"{0} {1}" -f (Get-FileHash -LiteralPath $_.FullName -Algorithm SHA256).Hash.ToLowerInvariant(), $relative
}
[IO.File]::WriteAllLines((Join-Path $payloadRoot 'SHA256SUMS'), [string[]]$checksumLines, [Text.UTF8Encoding]::new($false))
$archivePath = Join-Path $outputRoot ($bundleName + '.zip')
if (Test-Path -LiteralPath $archivePath) { Remove-Item -LiteralPath $archivePath -Force }
Compress-Archive -LiteralPath $payloadRoot -DestinationPath $archivePath -CompressionLevel Optimal
$archiveHash = (Get-FileHash -LiteralPath $archivePath -Algorithm SHA256).Hash.ToLowerInvariant()
"$archiveHash $([IO.Path]::GetFileName($archivePath))" | Set-Content -LiteralPath ($archivePath + '.sha256') -Encoding ascii
[ordered]@{ archive = $archivePath; sha256 = $archiveHash; files = $files.Count; source_commit = $sourceCommit; working_tree_dirty = $statusLines.Count -gt 0 } | ConvertTo-Json -Compress
} finally {
$resolvedStaging = [IO.Path]::GetFullPath($stagingRoot)
if ($resolvedStaging.StartsWith($outputRoot + [IO.Path]::DirectorySeparatorChar, [StringComparison]::OrdinalIgnoreCase) -and (Test-Path -LiteralPath $resolvedStaging)) {
Remove-Item -LiteralPath $resolvedStaging -Recurse -Force
}
}
+124
View File
@@ -0,0 +1,124 @@
$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
}
}
@@ -0,0 +1,54 @@
param([Parameter(Mandatory=$true)][string]$Archive)
$ErrorActionPreference = 'Stop'
$archivePath = (Resolve-Path -LiteralPath $Archive).Path
$archiveDirectory = Split-Path -Parent $archivePath
$sidecarPath = $archivePath + '.sha256'
if (-not (Test-Path -LiteralPath $sidecarPath -PathType Leaf)) { throw 'Archive checksum sidecar is missing' }
$sidecarHash = ((Get-Content -LiteralPath $sidecarPath -Raw).Trim() -split '\s+')[0]
$archiveHash = (Get-FileHash -LiteralPath $archivePath -Algorithm SHA256).Hash.ToLowerInvariant()
if ($sidecarHash -ne $archiveHash) { throw 'Archive checksum sidecar does not match' }
Add-Type -AssemblyName System.IO.Compression.FileSystem
$zip = [IO.Compression.ZipFile]::OpenRead($archivePath)
try {
$forbidden = @($zip.Entries | Where-Object {
$_.FullName -match '(^|/)(\.env|node_modules|runtime)(/|$)' -or
$_.FullName -match '\.(pem|key|p12|pfx)$'
})
if ($forbidden.Count -gt 0) { throw ('Bundle contains forbidden entries: ' + ($forbidden.FullName -join ', ')) }
$entryCount = $zip.Entries.Count
} finally {
$zip.Dispose()
}
$verificationRoot = Join-Path $archiveDirectory ('.verify-' + [Guid]::NewGuid().ToString('N'))
New-Item -ItemType Directory -Path $verificationRoot | Out-Null
try {
Expand-Archive -LiteralPath $archivePath -DestinationPath $verificationRoot
$payload = @(Get-ChildItem -LiteralPath $verificationRoot -Directory)
if ($payload.Count -ne 1) { throw 'Bundle must contain exactly one top-level directory' }
$manifestPath = Join-Path $payload[0].FullName 'deploy-manifest.json'
$checksumsPath = Join-Path $payload[0].FullName 'SHA256SUMS'
$manifest = Get-Content -LiteralPath $manifestPath -Raw | ConvertFrom-Json
if ($manifest.schema_version -ne 'server-deployment-bundle-v1' -or $manifest.schema_migration_target -ne 52 -or $manifest.hardware_runtime -ne 'volc_hardware_agent') {
throw 'Deployment manifest metadata is invalid'
}
$verifiedFiles = 0
foreach ($line in Get-Content -LiteralPath $checksumsPath) {
if ($line -notmatch '^([0-9a-f]{64}) (.+)$') { throw "Invalid checksum line: $line" }
$target = [IO.Path]::GetFullPath((Join-Path $payload[0].FullName $Matches[2]))
if (-not $target.StartsWith($payload[0].FullName + [IO.Path]::DirectorySeparatorChar, [StringComparison]::OrdinalIgnoreCase)) { throw 'Checksum target escapes bundle root' }
if (-not (Test-Path -LiteralPath $target -PathType Leaf)) { throw "Checksum target is missing: $($Matches[2])" }
if ((Get-FileHash -LiteralPath $target -Algorithm SHA256).Hash.ToLowerInvariant() -ne $Matches[1]) { throw "Checksum mismatch: $($Matches[2])" }
$verifiedFiles++
}
if ($verifiedFiles -ne ($manifest.files.Count + 1)) { throw 'Checksum coverage does not match manifest plus manifest file' }
[ordered]@{ archive = $archivePath; sha256 = $archiveHash; zip_entries = $entryCount; verified_files = $verifiedFiles; candidate_version = $manifest.candidate_version; schema_migration_target = $manifest.schema_migration_target } | ConvertTo-Json -Compress
} finally {
$resolvedVerificationRoot = [IO.Path]::GetFullPath($verificationRoot)
$resolvedArchiveDirectory = [IO.Path]::GetFullPath($archiveDirectory)
if ($resolvedVerificationRoot.StartsWith($resolvedArchiveDirectory + [IO.Path]::DirectorySeparatorChar, [StringComparison]::OrdinalIgnoreCase) -and (Test-Path -LiteralPath $resolvedVerificationRoot)) {
Remove-Item -LiteralPath $resolvedVerificationRoot -Recurse -Force
}
}