fix(agent99): select newest evidence by mtime
All checks were successful
CD Pipeline / select-latest-carrier (push) Successful in 47s
CD Pipeline / workflow-shape (push) Successful in 1s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 4m53s
CD Pipeline / revalidate-deploy-carrier (push) Successful in 29s
CD Pipeline / build-and-deploy (push) Successful in 14m53s
AWOOOI Harbor 110 Local Repair / workflow-shape (push) Successful in 0s
AWOOOI Harbor 110 Local Repair / harbor-110-local-repair (push) Successful in 9s
CD Pipeline / revalidate-post-deploy-carrier (push) Successful in 47s
CD Pipeline / post-deploy-checks (push) Successful in 3m10s
All checks were successful
CD Pipeline / select-latest-carrier (push) Successful in 47s
CD Pipeline / workflow-shape (push) Successful in 1s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 4m53s
CD Pipeline / revalidate-deploy-carrier (push) Successful in 29s
CD Pipeline / build-and-deploy (push) Successful in 14m53s
AWOOOI Harbor 110 Local Repair / workflow-shape (push) Successful in 0s
AWOOOI Harbor 110 Local Repair / harbor-110-local-repair (push) Successful in 9s
CD Pipeline / revalidate-post-deploy-carrier (push) Successful in 47s
CD Pipeline / post-deploy-checks (push) Successful in 3m10s
This commit is contained in:
@@ -242,9 +242,10 @@ function Get-AgentBoundedNewestFiles {
|
||||
[int]$CandidateWindow = 32
|
||||
)
|
||||
|
||||
# Agent99 evidence names contain a fixed sortable timestamp and are immutable.
|
||||
# Keep only the lexically newest bounded window in a native .NET set so a
|
||||
# large evidence directory never becomes a PowerShell object-sort workload.
|
||||
# Evidence freshness is authoritative from filesystem UTC mtime. Some
|
||||
# evidence names are stable aliases and are not chronologically sortable.
|
||||
# Keep only the newest bounded window in a native .NET set so a large
|
||||
# evidence directory never becomes a PowerShell object-sort workload.
|
||||
$selected = [Collections.Generic.SortedSet[string]]::new(
|
||||
[StringComparer]::OrdinalIgnoreCase
|
||||
)
|
||||
@@ -256,16 +257,25 @@ function Get-AgentBoundedNewestFiles {
|
||||
[IO.SearchOption]::TopDirectoryOnly
|
||||
)) {
|
||||
$enumeratedCount += 1
|
||||
$null = $selected.Add($candidatePath)
|
||||
$candidate = [IO.FileInfo]::new($candidatePath)
|
||||
$sortKey = (
|
||||
$candidate.LastWriteTimeUtc.Ticks.ToString(
|
||||
"D19",
|
||||
[Globalization.CultureInfo]::InvariantCulture
|
||||
) + "|" + $candidate.FullName
|
||||
)
|
||||
$null = $selected.Add($sortKey)
|
||||
if ($selected.Count -gt $CandidateWindow) {
|
||||
$null = $selected.Remove($selected.Min)
|
||||
}
|
||||
}
|
||||
}
|
||||
$selectedKeys = [string[]]@($selected)
|
||||
[Array]::Reverse($selectedKeys)
|
||||
|
||||
return [pscustomobject]@{
|
||||
files = @($selected | Sort-Object -Descending | ForEach-Object {
|
||||
[IO.FileInfo]::new($_)
|
||||
files = @($selectedKeys | ForEach-Object {
|
||||
[IO.FileInfo]::new($_.Substring(20))
|
||||
})
|
||||
enumeratedCount = [int]$enumeratedCount
|
||||
candidateWindow = [int]$CandidateWindow
|
||||
@@ -685,7 +695,11 @@ function Get-EvidenceReadback {
|
||||
$deferredCandidates.Count -eq $candidates.Count
|
||||
)
|
||||
$ageMinutes = if ($file) { [math]::Round(((Get-Date) - $file.LastWriteTime).TotalMinutes, 1) } else { $null }
|
||||
$fresh = [bool]($file -and $ageMinutes -le $MaxAgeMinutes)
|
||||
$fresh = [bool](
|
||||
$file -and
|
||||
$ageMinutes -ge 0 -and
|
||||
$ageMinutes -le $MaxAgeMinutes
|
||||
)
|
||||
$freshnessHeadroomMinutes = if ($file) { [math]::Round($MaxAgeMinutes - $ageMinutes, 1) } else { $null }
|
||||
$freshnessReserveReady = [bool](
|
||||
$fresh -and
|
||||
|
||||
@@ -156,6 +156,11 @@ def test_warning_receipt_is_visible_without_weakening_integrity_blockers() -> No
|
||||
assert "deferredCandidateCount = [int]$deferredCandidates.Count" in source
|
||||
assert "[IO.Directory]::EnumerateFiles(" in source
|
||||
assert "[Collections.Generic.SortedSet[string]]::new(" in source
|
||||
assert "$candidate.LastWriteTimeUtc.Ticks.ToString(" in source
|
||||
assert "[Globalization.CultureInfo]::InvariantCulture" in source
|
||||
assert "[IO.FileInfo]::new($_.Substring(20))" in source
|
||||
assert "[Array]::Reverse($selectedKeys)" in source
|
||||
assert "$ageMinutes -ge 0" in source
|
||||
assert "$null = $selected.Remove($selected.Min)" in source
|
||||
assert "candidateWindowExhausted = $candidateWindowExhausted" in source
|
||||
assert "function Get-AgentControlLoopMaintenanceReadback" in source
|
||||
@@ -278,6 +283,141 @@ Get-EvidenceReadback 'self_check' 'agent99-SelfCheck-*.json' 20 $true |
|
||||
assert malformed_payload["healthy"] is False
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("name", "pattern", "stale_alias", "fresh_file"),
|
||||
[
|
||||
(
|
||||
"status",
|
||||
"agent99-Status-*.json",
|
||||
"agent99-Status-same-run-status-{index:02d}.json",
|
||||
"agent99-Status-20260722-133422.json",
|
||||
),
|
||||
(
|
||||
"performance",
|
||||
"agent99-Perf-*.json",
|
||||
"agent99-Perf-latest-summary-{index:02d}.json",
|
||||
"agent99-Perf-20260722-133722.json",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_evidence_selection_uses_mtime_when_stable_aliases_sort_after_timestamps(
|
||||
tmp_path: Path,
|
||||
name: str,
|
||||
pattern: str,
|
||||
stale_alias: str,
|
||||
fresh_file: str,
|
||||
) -> None:
|
||||
powershell = shutil.which("pwsh") or shutil.which("powershell")
|
||||
if powershell is None:
|
||||
pytest.skip("PowerShell runtime is not installed on this macOS verifier")
|
||||
|
||||
evidence = tmp_path / "evidence"
|
||||
evidence.mkdir()
|
||||
fresh = evidence / fresh_file
|
||||
fresh.write_text(json.dumps({"mode": name}), encoding="utf-8")
|
||||
base_time = fresh.stat().st_mtime
|
||||
for index in range(40):
|
||||
alias = evidence / stale_alias.format(index=index)
|
||||
alias.write_text(json.dumps({"mode": name}), encoding="utf-8")
|
||||
stale_time = base_time - 3600 - index
|
||||
os.utime(alias, (stale_time, stale_time))
|
||||
os.utime(fresh, (base_time, base_time))
|
||||
|
||||
agent_root = str(tmp_path).replace("'", "''")
|
||||
command = f"""
|
||||
{_evidence_function_source()}
|
||||
$AgentRoot = '{agent_root}'
|
||||
Get-EvidenceReadback '{name}' '{pattern}' 20 $true |
|
||||
ConvertTo-Json -Depth 8 -Compress
|
||||
"""
|
||||
result = subprocess.run(
|
||||
[powershell, "-NoProfile", "-NonInteractive", "-Command", command],
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
assert result.returncode == 0, result.stdout + result.stderr
|
||||
payload = json.loads(
|
||||
next(line for line in reversed(result.stdout.splitlines()) if line.startswith("{"))
|
||||
)
|
||||
assert payload["file"] == fresh.name
|
||||
assert payload["fresh"] is True
|
||||
assert payload["candidateWindowTruncated"] is True
|
||||
|
||||
|
||||
def test_evidence_selection_rejects_future_mtime(tmp_path: Path) -> None:
|
||||
powershell = shutil.which("pwsh") or shutil.which("powershell")
|
||||
if powershell is None:
|
||||
pytest.skip("PowerShell runtime is not installed on this macOS verifier")
|
||||
|
||||
evidence = tmp_path / "evidence"
|
||||
evidence.mkdir()
|
||||
future = evidence / "agent99-Status-20260722-140000.json"
|
||||
future.write_text(json.dumps({"mode": "Status"}), encoding="utf-8")
|
||||
future_time = future.stat().st_mtime + 120
|
||||
os.utime(future, (future_time, future_time))
|
||||
|
||||
agent_root = str(tmp_path).replace("'", "''")
|
||||
command = f"""
|
||||
{_evidence_function_source()}
|
||||
$AgentRoot = '{agent_root}'
|
||||
Get-EvidenceReadback 'status' 'agent99-Status-*.json' 20 $true |
|
||||
ConvertTo-Json -Depth 8 -Compress
|
||||
"""
|
||||
result = subprocess.run(
|
||||
[powershell, "-NoProfile", "-NonInteractive", "-Command", command],
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
assert result.returncode == 0, result.stdout + result.stderr
|
||||
payload = json.loads(
|
||||
next(line for line in reversed(result.stdout.splitlines()) if line.startswith("{"))
|
||||
)
|
||||
assert payload["file"] == future.name
|
||||
assert payload["ageMinutes"] < 0
|
||||
assert payload["fresh"] is False
|
||||
assert payload["usable"] is False
|
||||
assert payload["healthy"] is False
|
||||
|
||||
|
||||
def test_evidence_selection_equal_mtime_uses_ordinal_path_tie_break(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
powershell = shutil.which("pwsh") or shutil.which("powershell")
|
||||
if powershell is None:
|
||||
pytest.skip("PowerShell runtime is not installed on this macOS verifier")
|
||||
|
||||
evidence = tmp_path / "evidence"
|
||||
evidence.mkdir()
|
||||
lower = evidence / "agent99-Status-equal-a.json"
|
||||
higher = evidence / "agent99-Status-equal-z.json"
|
||||
lower.write_text(json.dumps({"mode": "Status"}), encoding="utf-8")
|
||||
higher.write_text(json.dumps({"mode": "Status"}), encoding="utf-8")
|
||||
equal_time = lower.stat().st_mtime
|
||||
os.utime(lower, (equal_time, equal_time))
|
||||
os.utime(higher, (equal_time, equal_time))
|
||||
|
||||
agent_root = str(tmp_path).replace("'", "''")
|
||||
command = f"""
|
||||
{_evidence_function_source()}
|
||||
$AgentRoot = '{agent_root}'
|
||||
Get-EvidenceReadback 'status' 'agent99-Status-*.json' 20 $true |
|
||||
ConvertTo-Json -Depth 8 -Compress
|
||||
"""
|
||||
result = subprocess.run(
|
||||
[powershell, "-NoProfile", "-NonInteractive", "-Command", command],
|
||||
check=False,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
assert result.returncode == 0, result.stdout + result.stderr
|
||||
payload = json.loads(
|
||||
next(line for line in reversed(result.stdout.splitlines()) if line.startswith("{"))
|
||||
)
|
||||
assert payload["file"] == higher.name
|
||||
|
||||
|
||||
def test_evidence_reserve_rejects_fresh_but_expiring_evidence_without_extending_slo(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
|
||||
Reference in New Issue
Block a user