156 lines
4.7 KiB
PowerShell
156 lines
4.7 KiB
PowerShell
param(
|
|
[Parameter(Mandatory = $true)][string]$AuditPath,
|
|
[Parameter(Mandatory = $true)][string]$IdempotencyPath
|
|
)
|
|
|
|
Set-StrictMode -Version 2.0
|
|
$ErrorActionPreference = "Stop"
|
|
|
|
function Read-JsonLines {
|
|
param([Parameter(Mandatory = $true)][string]$Path)
|
|
if ([string]::IsNullOrWhiteSpace($Path) -or -not (Test-Path -LiteralPath $Path)) {
|
|
return @()
|
|
}
|
|
$records = @()
|
|
$lineNo = 0
|
|
foreach ($line in Get-Content -LiteralPath $Path) {
|
|
$lineNo++
|
|
$trimmed = $line.Trim()
|
|
if ($trimmed.Length -eq 0) {
|
|
continue
|
|
}
|
|
try {
|
|
$records += ($trimmed | ConvertFrom-Json)
|
|
}
|
|
catch {
|
|
throw "failed to parse JSONL $Path line ${lineNo}: $($_.Exception.Message)"
|
|
}
|
|
}
|
|
return @($records)
|
|
}
|
|
|
|
function Get-JsonPropertyNames {
|
|
param([AllowNull()]$Value)
|
|
$names = @()
|
|
if ($null -eq $Value) {
|
|
return $names
|
|
}
|
|
if ($Value -is [System.Array]) {
|
|
foreach ($item in $Value) {
|
|
$names += Get-JsonPropertyNames -Value $item
|
|
}
|
|
return $names
|
|
}
|
|
if ($Value -is [System.Collections.IDictionary]) {
|
|
foreach ($key in $Value.Keys) {
|
|
$names += [string]$key
|
|
$names += Get-JsonPropertyNames -Value $Value[$key]
|
|
}
|
|
return $names
|
|
}
|
|
if ($Value.PSObject -and $Value.PSObject.Properties) {
|
|
foreach ($property in $Value.PSObject.Properties) {
|
|
$names += $property.Name
|
|
$names += Get-JsonPropertyNames -Value $property.Value
|
|
}
|
|
}
|
|
return $names
|
|
}
|
|
|
|
function Get-StringProperty {
|
|
param(
|
|
[Parameter(Mandatory = $true)]$Record,
|
|
[Parameter(Mandatory = $true)][string]$Name
|
|
)
|
|
$property = $Record.PSObject.Properties[$Name]
|
|
if ($null -eq $property -or $null -eq $property.Value) {
|
|
return ""
|
|
}
|
|
return [string]$property.Value
|
|
}
|
|
|
|
$auditRecords = @(Read-JsonLines -Path $AuditPath)
|
|
$idempotencyRecords = @(Read-JsonLines -Path $IdempotencyPath)
|
|
$allRecords = @($auditRecords + $idempotencyRecords)
|
|
$propertyNames = @()
|
|
foreach ($record in $allRecords) {
|
|
$propertyNames += Get-JsonPropertyNames -Value $record
|
|
}
|
|
|
|
$rawBodyKeys = @("content_text", "ContentText", "message_body", "raw_body", "raw_content_text")
|
|
$rawIdempotencyKeys = @("idempotency_key", "IdempotencyKey", "raw_idempotency_key")
|
|
$rawBodyPresent = $false
|
|
$rawIdempotencyKeyPresent = $false
|
|
foreach ($name in $propertyNames) {
|
|
foreach ($rawName in $rawBodyKeys) {
|
|
if ([string]::Equals($name, $rawName, [System.StringComparison]::OrdinalIgnoreCase)) {
|
|
$rawBodyPresent = $true
|
|
}
|
|
}
|
|
foreach ($rawName in $rawIdempotencyKeys) {
|
|
if ([string]::Equals($name, $rawName, [System.StringComparison]::OrdinalIgnoreCase)) {
|
|
$rawIdempotencyKeyPresent = $true
|
|
}
|
|
}
|
|
}
|
|
|
|
$byIdempotencyKey = @{}
|
|
foreach ($record in $auditRecords) {
|
|
$keyHash = Get-StringProperty -Record $record -Name "idempotency_key_sha256"
|
|
if ([string]::IsNullOrWhiteSpace($keyHash)) {
|
|
continue
|
|
}
|
|
$fingerprint = (Get-StringProperty -Record $record -Name "target_ref") + "|" + (Get-StringProperty -Record $record -Name "content_sha256")
|
|
if (-not $byIdempotencyKey.ContainsKey($keyHash)) {
|
|
$byIdempotencyKey[$keyHash] = @()
|
|
}
|
|
$byIdempotencyKey[$keyHash] = @($byIdempotencyKey[$keyHash] + $fingerprint)
|
|
}
|
|
|
|
$duplicateDetected = $false
|
|
$conflictDetected = $false
|
|
foreach ($keyHash in $byIdempotencyKey.Keys) {
|
|
$fingerprints = @($byIdempotencyKey[$keyHash])
|
|
if ($fingerprints.Count -lt 2) {
|
|
continue
|
|
}
|
|
$unique = @($fingerprints | Sort-Object -Unique)
|
|
if ($unique.Count -eq 1) {
|
|
$duplicateDetected = $true
|
|
}
|
|
else {
|
|
$conflictDetected = $true
|
|
$counts = @{}
|
|
foreach ($fingerprint in $fingerprints) {
|
|
if (-not $counts.ContainsKey($fingerprint)) {
|
|
$counts[$fingerprint] = 0
|
|
}
|
|
$counts[$fingerprint]++
|
|
}
|
|
foreach ($count in $counts.Values) {
|
|
if ($count -gt 1) {
|
|
$duplicateDetected = $true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$ok = -not ($rawBodyPresent -or $rawIdempotencyKeyPresent)
|
|
$summary = [ordered]@{
|
|
ok = $ok
|
|
verification = "send_audit_idempotency"
|
|
audit_records = $auditRecords.Count
|
|
idempotency_records = $idempotencyRecords.Count
|
|
duplicate_detected = $duplicateDetected
|
|
conflict_detected = $conflictDetected
|
|
raw_body_present = $rawBodyPresent
|
|
raw_idempotency_key_present = $rawIdempotencyKeyPresent
|
|
audit_path_configured = -not [string]::IsNullOrWhiteSpace($AuditPath)
|
|
idempotency_path_configured = -not [string]::IsNullOrWhiteSpace($IdempotencyPath)
|
|
}
|
|
|
|
Write-Output ($summary | ConvertTo-Json -Compress)
|
|
if (-not $ok) {
|
|
exit 1
|
|
}
|