diff --git a/agent99-control-plane.ps1 b/agent99-control-plane.ps1 index 050259013..b7f7edb99 100644 --- a/agent99-control-plane.ps1 +++ b/agent99-control-plane.ps1 @@ -4149,6 +4149,14 @@ function Convert-AgentSafeKey { return $safe } +function Convert-AgentSafeFileName { + param([string]$Value) + $safe = [regex]::Replace(([string]$Value), "[^A-Za-z0-9_.-]", "_") + if (-not $safe) { return "unknown" } + if ($safe.Length -gt 140) { return $safe.Substring(0, 140) } + return $safe +} + function New-AgentOutcomeCheck { param( [string]$Name, @@ -4896,7 +4904,8 @@ function Invoke-AgentCompletionCallback { } occurred_at = (Get-Date -Format o) } - $pendingPath = Join-Path $pendingDir "$callbackId.json" + $callbackFileKey = Convert-AgentSafeFileName $callbackId + $pendingPath = Join-Path $pendingDir "$callbackFileKey.json" $payload | ConvertTo-Json -Depth 10 | Set-Content -Path $pendingPath -Encoding UTF8 } @@ -4976,7 +4985,7 @@ function Invoke-AgentCompletionCallback { (Get-Item $pendingPath).LastWriteTime = Get-Date } $receipt.ok = [bool]($receipt.status -eq "durable_verified") - $safeCallbackId = Convert-AgentSafeKey $callbackIdValue + $safeCallbackId = Convert-AgentSafeFileName $callbackIdValue $receiptPath = Join-Path $EvidenceDir "agent99-CompletionCallback-$safeCallbackId-$stamp.json" $receipt.evidenceFile = Split-Path -Leaf $receiptPath $receipt | ConvertTo-Json -Depth 10 | Set-Content -Path $receiptPath -Encoding UTF8 diff --git a/apps/api/tests/test_agent99_transport_recovery_deploy_contract.py b/apps/api/tests/test_agent99_transport_recovery_deploy_contract.py index 3d2fc4530..f850d9d70 100644 --- a/apps/api/tests/test_agent99_transport_recovery_deploy_contract.py +++ b/apps/api/tests/test_agent99_transport_recovery_deploy_contract.py @@ -242,6 +242,18 @@ def test_agent99_object_reader_preserves_ordered_callback_identity() -> None: assert "elseif ($Data -and $Data.PSObject.Properties[$Name])" in helper +def test_agent99_callback_queue_uses_windows_safe_file_keys() -> None: + source = CONTROL.read_text(encoding="utf-8") + helper = source[source.index("function Convert-AgentSafeFileName") :] + helper = helper[: helper.index("function New-AgentOutcomeCheck")] + + assert '"[^A-Za-z0-9_.-]"' in helper + assert '"[^A-Za-z0-9_.:-]"' not in helper + assert "$callbackFileKey = Convert-AgentSafeFileName $callbackId" in source + assert 'Join-Path $pendingDir "$callbackFileKey.json"' in source + assert "$safeCallbackId = Convert-AgentSafeFileName $callbackIdValue" in source + + def test_agent99_completion_token_is_bound_without_reusing_telegram_secret() -> None: api_deployment = (ROOT / "k8s/awoooi-prod/06-deployment-api.yaml").read_text() worker_deployment = (