Phase 12.K.1: install-tentacle.ps1 E2E + production em-dash fix
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✅ onwindows-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_ExtractsBinaryCustom-InstallDir+-DownloadBaseoverride +-NoServiceInstall. Drives realpowershell.exerunninginstall-tentacle.ps1against aLocalReleaseMirrorHttpListener 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_ScriptExitsNonZeroMirror returns 404 for the requested version → script exits non-zero AND no binary extracted (script bails before extract). -
A8.h
InstallScript_ReRun_OverExistingInstall_SucceedsTwo installs over same dir with different content. Second install's content observable post-extract — provesExpand-Archive -Forceactually 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}.zipGET /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:
-
Phase 12.G round 0:
WindowsServiceHost.BuildScCreateArgspacked sc.exe argv tokens (every Windows operator runningservice installhit[SC] CreateService FAILED 1639) -
Phase 12.K round 7:
install-tentacle.ps1em-dash parse error (every Windows operator runningirm | iexon 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:
- Spawns pwsh.exe (~300-500ms)
- Sleeps 1 second (stdout reader fully attached during this)
- 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:
-
Halibut 8.1.1943 cache-key bug:
ParameterCacheKeys.GenerateCacheKeyonly acceptsnull/string/Guid/DateTime/DateTimeOffset/IEnumerable. EmptyCapabilitiesRequest(the production wire shape) breaks every[CacheResponse(60)]-decorated capabilities probe. May explain real prod observability gaps where capabilities reads silently fail. -
Squid.E2ETestsnot 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.