mirror of
https://github.com/boshu2/agentops.git
synced 2026-09-14 15:08:13 +08:00
17849bbc24
A failed mining-checkpoint write could truncate the saved watermark and cause retry to replay older events. The CLI also wrote evidence to external roots that status could not inspect. This batch fixes those behaviors and removes duplicate normalization from skill search. - Mining checkpoints use the existing atomic storage writer. A real partial-write regression test proves old bytes survive and retry retains stable event IDs. Existing mode bits are preserved; new checkpoints use 0600. Symlink and special-file destinations are rejected before reading. Before replacement, an empty same-directory probe checks ownership and permission metadata, including ACLs and inherited permissions. Unverifiable or different metadata returns an error and leaves the prior checkpoint intact. This is a conservative refusal, not ACL migration. A directory-sync error after rename can leave the new state visible. - `ao status --evidence-root PATH` inspects an explicit existing non-Git store, with matching text/JSON/YAML reports, no fallback on invalid roots, and no reads through evidence symlinks. Omitted-flag behavior remains unchanged. - Skill-query normalization has one implementation, preserving repetition versus first-occurrence semantics. Nine fixed shipped-catalog queries remain byte-identical against a source-pinned baseline. Validation: Go build, vet, tests, race/shuffle with atomic coverage, repository Bats and aggregate suites, regeneration, applicable gates and lint passed. Final Linux and Windows correctness CI and all required checks passed. A fresh author-distinct review verified every acceptance criterion across all 23 changed paths with no unchecked scope. Nine production-query outputs match the source-pinned baseline. The first CI attempt exposed a test-child coverage flush under its temporary file-size limit; the test now restores that limit before exit. Fresh review then exposed ACL loss despite green CI. The permission guard and native regression tests repair that defect while preserving the original access requirement. The failure cases and repair costs are retained in the evaluation.
239 lines
8.9 KiB
PowerShell
239 lines
8.9 KiB
PowerShell
# test-windows-smoke.ps1 - Native Windows smoke tests for AgentOps installers and ao.
|
|
#
|
|
# Stabilization (soc-92ka, 2026-05-03):
|
|
# - Set-StrictMode -Version Latest enforced (catches uninitialized vars,
|
|
# out-of-bounds indexing, unset properties at runtime instead of producing
|
|
# silent empty strings that flake downstream).
|
|
# - All path construction uses Join-Path or
|
|
# [System.IO.Path]::DirectorySeparatorChar; no hard-coded forward slashes
|
|
# and no embedded backslash separators in path segments.
|
|
# - Nested PowerShell invocations re-use the current $PSHOME pwsh (or
|
|
# fall back to powershell.exe) to avoid execution-policy / host drift
|
|
# between Windows PowerShell 5.1 and PowerShell 7+.
|
|
#
|
|
# POSIX-symlink-dependent checks: SKIPPED on Windows.
|
|
# This script intentionally does NOT exercise checks that require POSIX
|
|
# symlink semantics. Windows GitHub runners do not create symlinks during
|
|
# `actions/checkout@v6` by default, and the repo policy bans symlinks
|
|
# anyway (see CLAUDE.md "No symlinks. Ever."). Any future check that
|
|
# relies on a POSIX symlink (e.g. `Test-Path -PathType SymbolicLink`,
|
|
# `(Get-Item).LinkType -eq 'SymbolicLink'`, `readlink`-style traversal,
|
|
# or symlink-aware copy/sync) MUST be guarded by `if (-not $IsWindows)`
|
|
# or omitted from this script. The canonical symlink audit lives in
|
|
# the Linux-only `plugin-load-test` job, not here.
|
|
|
|
[CmdletBinding()]
|
|
param(
|
|
[string]$RepoRoot
|
|
)
|
|
|
|
Set-StrictMode -Version Latest
|
|
$ErrorActionPreference = "Stop"
|
|
$ProgressPreference = "SilentlyContinue"
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Path helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
# Resolve a chain of segments into an absolute path using Join-Path so the
|
|
# host's [System.IO.Path]::DirectorySeparatorChar is always honored. Accepts
|
|
# any number of trailing segments.
|
|
function Join-PathSegments {
|
|
param(
|
|
[Parameter(Mandatory = $true)][string]$Base,
|
|
[Parameter(ValueFromRemainingArguments = $true)][string[]]$Segments
|
|
)
|
|
$current = $Base
|
|
foreach ($segment in $Segments) {
|
|
if ([string]::IsNullOrEmpty($segment)) { continue }
|
|
$current = Join-Path -Path $current -ChildPath $segment
|
|
}
|
|
return $current
|
|
}
|
|
|
|
if ([string]::IsNullOrWhiteSpace($RepoRoot)) {
|
|
$scriptRoot = if ($PSScriptRoot) {
|
|
$PSScriptRoot
|
|
} else {
|
|
Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
}
|
|
$repoRootCandidate = Join-PathSegments -Base $scriptRoot -Segments '..', '..'
|
|
$RepoRoot = (Resolve-Path -LiteralPath $repoRootCandidate).Path
|
|
}
|
|
|
|
# Pin the PowerShell host used for nested installer runs. Prefer the same
|
|
# host this script is running under (`pwsh` on CI via `shell: pwsh`);
|
|
# fall back to Windows PowerShell only if pwsh is unavailable. Mixing
|
|
# hosts has been a recurring flake source on Windows runners.
|
|
$nestedPSHost = $null
|
|
$pshExe = Get-Command -Name 'pwsh' -ErrorAction SilentlyContinue
|
|
if ($pshExe) {
|
|
$nestedPSHost = $pshExe.Source
|
|
} else {
|
|
$winPS = Get-Command -Name 'powershell' -ErrorAction SilentlyContinue
|
|
if ($winPS) {
|
|
$nestedPSHost = $winPS.Source
|
|
} else {
|
|
throw "Neither pwsh nor powershell is available on PATH"
|
|
}
|
|
}
|
|
|
|
function Write-Step {
|
|
param([string]$Message)
|
|
Write-Host "==> $Message"
|
|
}
|
|
|
|
function Test-PowerShellSyntax {
|
|
param([string]$Path)
|
|
|
|
$errors = $null
|
|
$null = [System.Management.Automation.PSParser]::Tokenize((Get-Content -Raw -LiteralPath $Path), [ref]$errors)
|
|
if ($errors) {
|
|
$errors | ForEach-Object { Write-Error $_ }
|
|
throw "PowerShell syntax check failed: $Path"
|
|
}
|
|
}
|
|
|
|
function Invoke-GoTest {
|
|
param([string[]]$TestArgs)
|
|
|
|
$cliDir = Join-PathSegments -Base $RepoRoot -Segments 'cli'
|
|
Push-Location -LiteralPath $cliDir
|
|
try {
|
|
& go test @TestArgs
|
|
}
|
|
finally {
|
|
Pop-Location
|
|
}
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "go test failed: $($TestArgs -join ' ')"
|
|
}
|
|
}
|
|
|
|
function Invoke-NestedScript {
|
|
param(
|
|
[Parameter(Mandatory = $true)][string]$ScriptPath,
|
|
[string[]]$ScriptArgs
|
|
)
|
|
$argList = @('-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', $ScriptPath)
|
|
if ($ScriptArgs) {
|
|
$argList += $ScriptArgs
|
|
}
|
|
& $nestedPSHost @argList
|
|
}
|
|
|
|
function Remove-IfExists {
|
|
param([string]$Path)
|
|
if ($Path -and (Test-Path -LiteralPath $Path)) {
|
|
Remove-Item -LiteralPath $Path -Recurse -Force -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 1. PowerShell installer syntax
|
|
# ---------------------------------------------------------------------------
|
|
|
|
Write-Step "Checking PowerShell installer syntax"
|
|
Test-PowerShellSyntax (Join-PathSegments -Base $RepoRoot -Segments 'scripts', 'install-ao.ps1')
|
|
$codexTombstone = Join-PathSegments -Base $RepoRoot -Segments 'scripts', 'install-codex.ps1'
|
|
Test-PowerShellSyntax $codexTombstone
|
|
$tombstoneText = Get-Content -Raw -LiteralPath $codexTombstone
|
|
if ($tombstoneText -notmatch 'removed in 3\.3' -or $tombstoneText -notmatch 'ao skills link') {
|
|
throw "install-codex.ps1 must remain an AgentOps 3.3 tombstone pointing at ao skills link"
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 2. Install ao release binary into a temp directory
|
|
# ---------------------------------------------------------------------------
|
|
|
|
Write-Step "Installing ao release binary into a temp directory"
|
|
$aoInstallDir = Join-PathSegments -Base ([System.IO.Path]::GetTempPath()) -Segments ("agentops-ao-smoke-" + [Guid]::NewGuid().ToString("N"))
|
|
try {
|
|
$installAoScript = Join-PathSegments -Base $RepoRoot -Segments 'scripts', 'install-ao.ps1'
|
|
Invoke-NestedScript -ScriptPath $installAoScript -ScriptArgs @('-InstallDir', $aoInstallDir, '-NoPathUpdate')
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "install-ao.ps1 failed"
|
|
}
|
|
$releaseAO = Join-PathSegments -Base $aoInstallDir -Segments 'ao.exe'
|
|
& $releaseAO version
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "installed ao.exe version smoke failed"
|
|
}
|
|
}
|
|
finally {
|
|
Remove-IfExists -Path $aoInstallDir
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 3. Build local ao, link skills, and check the current doctor contract
|
|
# ---------------------------------------------------------------------------
|
|
|
|
Write-Step "Building local ao, linking skills, and checking doctor guidance"
|
|
$builtAO = Join-PathSegments -Base ([System.IO.Path]::GetTempPath()) -Segments ("ao-windows-smoke-" + [Guid]::NewGuid().ToString("N") + ".exe")
|
|
$linkHome = Join-PathSegments -Base ([System.IO.Path]::GetTempPath()) -Segments ("agentops-link-home-" + [Guid]::NewGuid().ToString("N"))
|
|
try {
|
|
$cliDir = Join-PathSegments -Base $RepoRoot -Segments 'cli'
|
|
$aoCmdPkg = Join-PathSegments -Base '.' -Segments 'cmd', 'ao'
|
|
Push-Location -LiteralPath $cliDir
|
|
try {
|
|
& go build -o $builtAO $aoCmdPkg
|
|
}
|
|
finally {
|
|
Pop-Location
|
|
}
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "go build failed"
|
|
}
|
|
|
|
New-Item -ItemType Directory -Force -Path $linkHome | Out-Null
|
|
$prevHome = $env:HOME
|
|
$prevUserProfile = $env:USERPROFILE
|
|
try {
|
|
$env:HOME = $linkHome
|
|
$env:USERPROFILE = $linkHome
|
|
Push-Location -LiteralPath $RepoRoot
|
|
try {
|
|
& $builtAO skills link
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "ao skills link failed"
|
|
}
|
|
}
|
|
finally {
|
|
Pop-Location
|
|
}
|
|
$agentsSkills = Join-PathSegments -Base $linkHome -Segments '.agents', 'skills'
|
|
if (-not (Test-Path -LiteralPath $agentsSkills)) {
|
|
throw "ao skills link did not create $agentsSkills"
|
|
}
|
|
}
|
|
finally {
|
|
if ($null -ne $prevHome) { $env:HOME = $prevHome } else { Remove-Item Env:HOME -ErrorAction SilentlyContinue }
|
|
if ($null -ne $prevUserProfile) { $env:USERPROFILE = $prevUserProfile }
|
|
}
|
|
|
|
$doctorText = (& $builtAO doctor 2>&1) -join "`n"
|
|
if ($LASTEXITCODE -gt 1) {
|
|
throw "ao doctor failed (exit $LASTEXITCODE)"
|
|
}
|
|
if ($doctorText -notmatch 'ao skills link') {
|
|
throw "doctor output did not include the ao skills link install hint"
|
|
}
|
|
}
|
|
finally {
|
|
Remove-IfExists -Path $builtAO
|
|
Remove-IfExists -Path $linkHome
|
|
}
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 4. Focused Windows-sensitive Go tests
|
|
# ---------------------------------------------------------------------------
|
|
|
|
Write-Step "Running focused Windows-sensitive Go tests"
|
|
Invoke-GoTest -TestArgs @("-timeout", "3m", "./internal/quality")
|
|
Invoke-GoTest -TestArgs @("-timeout", "3m", "./cmd/ao", "-run", "^(TestBatchForge_appendForgedRecord|TestAppendForgedRecord|TestBatchForgeSkipsAlreadyForged|TestLoadAndFilterTranscripts_RespectsForgedIndex|TestCanonicalArtifactPath|TestCobraDemoConceptsCommand|TestCobraDemoQuickCommand|TestCobraShowConcepts)$")
|
|
Invoke-GoTest -TestArgs @("-timeout", "3m", "./internal/storage", "-run", "^TestWithLockedFile_")
|
|
Invoke-GoTest -TestArgs @("-timeout", "3m", "./internal/provenanceapp", "-run", "^Test(MineSession|WriteMineState)", "-count", "1")
|
|
Invoke-GoTest -TestArgs @("-timeout", "3m", "./internal/config", "-run", "^TestSave_ConcurrentPatchesPreserveBothUpdates$")
|
|
|
|
Write-Host "Windows smoke tests passed"
|