From df26f43b770b17f6bfeb99382514c84eb56277b6 Mon Sep 17 00:00:00 2001 From: ogt Date: Sat, 11 Jul 2026 20:42:21 +0800 Subject: [PATCH] fix(agent99): validate v5 incident formatter --- agent99-synthetic-tests.ps1 | 25 ++++++++++-- ...nt99_transport_recovery_deploy_contract.py | 39 ++++++++++++++++++- 2 files changed, 60 insertions(+), 4 deletions(-) diff --git a/agent99-synthetic-tests.ps1 b/agent99-synthetic-tests.ps1 index 4c23dff99..29b0403c2 100644 --- a/agent99-synthetic-tests.ps1 +++ b/agent99-synthetic-tests.ps1 @@ -32,7 +32,18 @@ $controlPath = Join-Path $SourceRoot "agent99-control-plane.ps1" $controlSource = [IO.File]::ReadAllText($controlPath, [Text.Encoding]::UTF8) $ast = [System.Management.Automation.Language.Parser]::ParseInput($controlSource, [ref]$tokens, [ref]$errors) $formatFunctions = @{} -foreach ($functionName in @("Get-AgentObjectValue", "Limit-AgentTextLine", "Format-AgentMetricForCard", "Format-AgentTelegramText", "Convert-AgentRecoveryReadback")) { +foreach ($functionName in @( + "Convert-AgentBool", + "Get-AgentObjectValue", + "Limit-AgentTextLine", + "Format-AgentMetricForCard", + "Format-AgentTelegramLegacyText", + "Get-AgentSha256Hex", + "Get-AgentIncidentCardModel", + "Format-AgentTelegramText", + "Format-AgentTelegramCaption", + "Convert-AgentRecoveryReadback" +)) { $definition = $ast.Find({ param($node) $node -is [System.Management.Automation.Language.FunctionDefinitionAst] -and $node.Name -eq $functionName @@ -58,10 +69,18 @@ if ($formatFunctions.ContainsKey("Format-AgentTelegramText")) { status = "handled_blocked_by_trust_gate" candidatePath = "C:\Wooo\Agent99\state\pf-test.json" }) + $providerCaption = Format-AgentTelegramCaption "warning" "provider_freshness_triage" "test" ([pscustomobject]@{ + target = "provider_freshness_signal" + missingFields = @("lastSeen") + status = "handled_blocked_by_trust_gate" + candidatePath = "C:\Wooo\Agent99\state\pf-test.json" + }) $cards = "$perfCard`n$providerCard" Add-SyntheticCheck "telegram:localized" ([regex]::IsMatch($cards, "[^\x00-\x7F]")) "localized card is present" - Add-SyntheticCheck "telegram:no_blank_metrics" (-not $perfCard.Contains("load/core=, ")) "failed readback renders unavailable values" - Add-SyntheticCheck "telegram:provider_handled" ($providerCard.Contains("handled_blocked_by_trust_gate") -and $providerCard.Contains("Agent99")) "handled state and agent are visible" + Add-SyntheticCheck "telegram:no_blank_metrics" ($perfCard.Contains("數值目前不可信") -and -not $perfCard.Contains("load/core=, ")) "failed readback is explained without blank metrics" + Add-SyntheticCheck "telegram:provider_handled" ($providerCard.Contains("Provider freshness") -and $providerCard.Contains("事件:INC-") -and $providerCard.Contains("Agent99:")) "provider incident has human lifecycle fields" + Add-SyntheticCheck "telegram:no_internal_paths" (-not $cards.Contains("C:\Wooo") -and -not $cards.Contains("KM:") -and -not $cards.Contains("verifierName=")) "raw evidence and verifier fields are absent" + Add-SyntheticCheck "telegram:caption_bounded" ($providerCaption.Length -lt 900 -and $providerCaption.Contains("下一次更新:")) "visual caption is concise and actionable" Add-SyntheticCheck "telegram:no_raw_python" (-not $cards.Contains("urllib.request")) "relay source is absent from user-facing card" } else { Add-SyntheticCheck "telegram:formatter" $false "Format-AgentTelegramText not found" 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 25c589b56..30084e9b5 100644 --- a/apps/api/tests/test_agent99_transport_recovery_deploy_contract.py +++ b/apps/api/tests/test_agent99_transport_recovery_deploy_contract.py @@ -3,7 +3,6 @@ from __future__ import annotations import json from pathlib import Path - ROOT = Path(__file__).resolve().parents[3] CONTROL = ROOT / "agent99-control-plane.ps1" DEPLOY = ROOT / "agent99-deploy.ps1" @@ -76,6 +75,22 @@ def test_agent99_telegram_v5_hides_raw_evidence_and_has_operator_fields() -> Non assert 'messageFormat = "incident_card_zh_tw_v5"' in source +def test_agent99_v5_formatter_synthetic_gate_loads_model_dependencies() -> None: + synthetic = (ROOT / "agent99-synthetic-tests.ps1").read_text(encoding="utf-8") + + for function_name in ( + "Convert-AgentBool", + "Get-AgentSha256Hex", + "Get-AgentIncidentCardModel", + "Format-AgentTelegramText", + "Format-AgentTelegramCaption", + ): + assert f'"{function_name}"' in synthetic + assert 'telegram:no_internal_paths' in synthetic + assert 'telegram:caption_bounded' in synthetic + assert 'C:\\Wooo' in synthetic + + def test_agent99_telegram_threads_source_alert_and_persists_lifecycle() -> None: source = CONTROL.read_text(encoding="utf-8") inbox = SRE_INBOX.read_text(encoding="utf-8") @@ -149,6 +164,28 @@ def test_agent99_deployer_verifies_promoted_runtime_and_writes_revision() -> Non assert f'"{name}"' in bootstrap +def test_agent99_completion_callback_waits_for_durable_same_trace_readback() -> None: + source = CONTROL.read_text(encoding="utf-8") + deploy = DEPLOY.read_text(encoding="utf-8") + config = json.loads((ROOT / "agent99.config.99.example.json").read_text()) + + callback = config["completionCallback"] + assert callback["enabled"] is True + assert callback["url"].startswith("https://awoooi.wooo.work/") + assert callback["tokenEnv"] == "AGENT99_SRE_RELAY_TOKEN" + assert callback["retryAfterMinutes"] == 5 + assert 'schema_version = "agent99_completion_callback_v1"' in source + assert '"X-Agent99-Completion-Token" = $token' in source + assert '$receipt.durableReadback' in source + assert '$sameIdentity' in source + assert '$receipt.operationReceiptCount -gt 0' in source + assert 'state\\completion-callbacks\\pending' in source + assert 'Invoke-AgentPendingCompletionCallbacks' in source + assert 'Add-DefaultProperty $config "completionCallback"' in deploy + assert 'rawResponseStored = $false' in source + assert 'secretValueLogged = $false' in source + + def test_load_shedding_requires_configured_post_verifier() -> None: source = CONTROL.read_text(encoding="utf-8") function = source[source.index("function Invoke-LoadShedding") :]