Skip to content

Phase 12.K.1: install-tentacle.ps1 E2E + production em-dash fix

Placeholder ppxd requested to merge phase12.K-install-scripts into main

Summary

Phase 12.K adds 3 high-fidelity E2E tests for the production deploy/scripts/install-tentacle.ps1 install script, plus catches and fixes a real production bug that's been live since the script was authored.

The production bug caught

install-tentacle.ps1 line 97 had throw "...win-arm64 only — 32-bit Windows...". The em-dash (—, U+2014) inside the throw string broke PowerShell 5.1 parsing because PS 5.1 reads BOM-less UTF-8 as ANSI codepage, mangling the bytes into â?" which the parser then treats as an unexpected token boundary.

Effect: every Windows operator running irm <install-tentacle.ps1> | iex on a stock host (Windows 10 / Server 2019 / Server 2022 — pwsh 7 not installed by default) hit a cascading parse error before any install logic ran. The script simply did not work on default Windows. Now fixed — both install scripts replaced 19 + 39 = 58 non-ASCII typography chars with ASCII equivalents (em-dash → -, box-drawing → -, smart quotes → ' / ").

Test plan

  • dotnet test tests/Squid.WindowsUpgradeE2ETests — 69/69 ✅ on macOS
  • dotnet test tests/Squid.UnitTests — 5003/5003 ✅
  • tentacle-windows-e2e.yml — 69/69 ✅ on windows-latest (run 25424547140)
  • Round 7 + 8 fixes verified green

Tests added (3, all Windows-only with skip-guard)

  • A1.h + A4.h + A7.h InstallScript_HappyPath_WithCustomDirAndMirror_ExtractsBinary Custom -InstallDir + -DownloadBase override + -NoServiceInstall. Drives real powershell.exe running install-tentacle.ps1 against a LocalReleaseMirror HttpListener serving fake zip downloads. Asserts exit 0, binary extracted, mirror was hit (proves DOWNLOAD_BASE took effect, not silently fell back to github.com).

  • A1.u1 + A2.u1 InstallScript_BogusVersion_ScriptExitsNonZero Mirror returns 404 for the requested version → script exits non-zero AND no binary extracted (script bails before extract).

  • A8.h InstallScript_ReRun_OverExistingInstall_Succeeds Two installs over same dir with different content. Second install's content observable post-extract — proves Expand-Archive -Force actually overwrites.

New shared infrastructure

LocalReleaseMirror (tests/Squid.WindowsUpgradeE2ETests/Infrastructure/LocalReleaseMirror.cs) — HttpListener-based fake GitHub Releases / private-mirror. Wire shapes mirrored from production scripts:

  • GET /latest/download/squid-tentacle-{rid}.zip
  • GET /download/{version}/squid-tentacle-{version}-{rid}.zip
  • GET /download/v{version}/squid-tentacle-{version}-{rid}.zip (fallback)
  • GET /download/{version}/squid-tentacle-{version}-{rid}.tar.gz (Linux)

Configurable: StageBinary, ConfigureNotFoundForVersion, ReceivedRequests for assertion. Includes a hand-rolled ustar+gzip writer (System.IO.Compression has zip but not tar). Already supports tar.gz for future Phase 12.K.3 install-tentacle.sh tests on Linux.

Round 7 — production fix (install-tentacle.{ps1,sh} ASCII)

This is the 2nd production bug caught by Phase 12.G+ E2E:

  1. Phase 12.G round 0: WindowsServiceHost.BuildScCreateArgs packed sc.exe argv tokens (every Windows operator running service install hit [SC] CreateService FAILED 1639)
  2. Phase 12.K round 7: install-tentacle.ps1 em-dash parse error (every Windows operator running irm | iex on a default host hit cascading PowerShell parser errors)

Both bugs were silent without a Windows runner exercising the real scripts. Without Phase 12.G's CI investment, these would only surface as customer support tickets — and the script-em-dash one is harder to diagnose remotely (parser errors don't blame the encoding directly).

Round 8 — test-stability fix (J.D.2 concurrent dispatch)

The pre-existing Listening_ConcurrentDispatches_OutputsIsolatedByTicket test (Phase 12.J.D.2 from PR #191) re-flaked under Round 7's verification load. Round 5's 50ms stagger + bare echo finished too quickly — pwsh.exe spawn race could leave the script's stdout reader unattached when the echo completed.

Fix: SleepThenEcho(1, "marker") so each script:

  1. Spawns pwsh.exe (~300-500ms)
  2. Sleeps 1 second (stdout reader fully attached during this)
  3. Emits marker (captured cleanly post-attach)

100ms stagger + 1s sleep = real concurrent overlap (~700ms) for actual isolation testing. Test cost: ~3s per run (was ~1s). Reliable on stressed Windows runners.

Discoveries flagged for follow-up

While investigating Phase 12.J.E.2 (capabilities probe), discovered:

  1. Halibut 8.1.1943 cache-key bug: ParameterCacheKeys.GenerateCacheKey only accepts null/string/Guid/DateTime/DateTimeOffset/IEnumerable. Empty CapabilitiesRequest (the production wire shape) breaks every [CacheResponse(60)]-decorated capabilities probe. May explain real prod observability gaps where capabilities reads silently fail.

  2. Squid.E2ETests not running in any CI workflow: The K8s capabilities E2E (Agent_GetCapabilities_ReturnsExpectedServices) has been failing silently because the project isn't in .github/workflows/tests.yml.

Both flagged as a separate worktree task for follow-up investigation.

Test count

Project total: 69 tests (66 from main + 3 new K.1). On macOS: 69/69 pass (3 K skip cleanly via OperatingSystem.IsWindows()). On Windows: 69/69 pass — full coverage of install + register + deploy + upgrade dispatch.

🤖 Generated with Claude Code

Merge request reports

Loading