Phase 12.J.E.8 — WaitForStatus timeout env var (audit Risk A) + _clean field cleanup
Summary
Closes audit Risk A identified in PR #201's deep review: heavyweight agents (heavy plugin enumeration / .NET tiered JIT cold start / AV scanning a 50MB binary) can take >30s to reach SCM RUNNING state. The Phase B Start-Service WaitForStatus is inside J.E.6's try/catch that triggers Invoke-Rollback → false rollback on a slow-but-successful upgrade.
Fix: operator-tunable env var following the established Rule 8 pattern.
# heavyweight agent (3min plugin enumeration on first start)
export SQUID_TARGET_WINDOWS_TENTACLE_SERVICE_TIMEOUT_SECONDS=240
Single var covers all 4 WaitForStatus call sites; default 30s preserves pre-PR behaviour exactly.
Production changes
WindowsTentacleUpgradeStrategy
- New
public const ServiceTimeoutSecondsEnvVar - New
internal const DefaultServiceTimeoutSeconds = 30 -
ResolveServiceTimeoutSeconds()— matches siblingResolve*shape (positive-integer parse + invalid-fallback-with-warning) -
{{SERVICE_TIMEOUT_SECONDS}}placeholder wired intoRenderInnerScript
upgrade-windows-tentacle.ps1
-
$SERVICE_TIMEOUT_SECONDS = {{SERVICE_TIMEOUT_SECONDS}}numeric literal -
$SERVICE_TIMEOUT_SPAN = [TimeSpan]::FromSeconds($SERVICE_TIMEOUT_SECONDS)constructed once at top (avoids 4× construction per upgrade) - All 4 hardcoded
'00:00:30'replaced with$SERVICE_TIMEOUT_SPAN:- Phase B Stop-Service
- Phase B Start-Service (the audit-Risk-A target)
- Invoke-Rollback Stop new
- Invoke-Rollback Restart old
Unit tests (6 new)
| Test | What it pins |
|---|---|
ServiceTimeoutSecondsEnvVar_ConstantNamePinned |
Rule 8 — silent rename breaks operators |
ResolveServiceTimeoutSeconds_NoEnvVar_ReturnsDefault30 |
Default preserves pre-PR behaviour |
ResolveServiceTimeoutSeconds_ValidValue_RoundTripsAsInteger (Theory) |
60 / 180 / 3600 / whitespace-padded |
ResolveServiceTimeoutSeconds_InvalidValue_FallsBackToDefault30 (Theory) |
0 / -30 / "30s" / non-numeric / empty — including the common typo where operators write a unit suffix |
RenderInnerScript_ServiceTimeoutSecondsPlaceholder_SubstitutedFromEnv |
Substitution + reverse-pin ("'00:00:30'" stale literal must NOT remain) |
RenderInnerScript_ServiceTimeoutSpanIsTimeSpanInstance |
Pins the TimeSpan construction shape — a "simplification" that passes int to WaitForStatus (which expects TimeSpan) would crash at runtime |
Drift detector updates
- Unit:
RenderInnerScript_PlaceholderTokens_AppearExactlyOnceInTemplateextended - E2E:
UpgradeScript_PlaceholderSet_PinnedToProductionContractextended
Bonus: _clean field finally has a purpose
Pre-this-PR _clean was assigned but never read (CS0414 warning since J.E.3). Now used in Dispose() to emit a diagnostic Console.WriteLine when cleanup runs without MarkClean having fired (= test failed before happy-path completion). Useful when reading CI logs to disambiguate "test passed but cleanup failed" from "test failed → MarkClean never reached".
Local verification
- 262/262 unit tests pass
- 87/87 cross-platform E2E pass
Deferred
The 5th env var triggers consideration of an IUpgradeOverride[] refactor (Rule 7). Currently:
DownloadBaseUrlEnvVarHealthcheckUrlEnvVarHealthcheckRetriesEnvVarHealthcheckFatalEnvVar-
ServiceTimeoutSecondsEnvVar← THIS PR
Deferred until 6th — refactoring at exactly the threshold is over-eager.