Add file-size cap to rewriter steps (50 MB default, env override)
Summary
Defensive OOM bound for the G1.x rewriter pipeline. Every rewriter previously loaded the entire matched file into memory — a glob accidentally matching a 200 MB log would OOM the agent process. Typical configs are < 100 KB, so the failure mode is rare but realistic when operators reuse generic globs (*.config, **/*.json) across heterogeneous packages.
Stacks on #367 (A1 wire-literal generalization). Merges after the upstream stack lands.
What's added
| Surface | Behavior |
|---|---|
EncodingPreservingFileIO.MaxFileSizeMBEnvVar |
Public const = "SQUID_CALAMARI_REWRITER_MAX_FILE_SIZE_MB"
|
EncodingPreservingFileIO.DefaultMaxFileSizeMB |
Public const = 50
|
EncodingPreservingFileIO.ResolveMaxFileSizeMB() |
Reads env var live (no caching). Invalid → default. |
EncodingPreservingFileIO.IsWithinSizeLimit(path, out sizeBytes, out limitBytes) |
Pre-flight gate. Fail-closed on FileInfo errors. |
| Step skip behavior | Log structured warning (size + limit + env var name to flip), continue to next file. Sibling files still process. |
What's in the box
| Layer | File |
|---|---|
| File-size helper | src/Squid.Calamari/Commands/Common/EncodingPreservingFileIO.cs |
| G1.1 skip integration | src/Squid.Calamari/Commands/Substitution/SubstituteInFilesStep.cs |
| G1.2 skip integration | src/Squid.Calamari/Commands/Configuration/XdtTransformer.cs |
| G1.3 skip integration | src/Squid.Calamari/Commands/StructuredConfig/StructuredConfigVariablesStep.cs |
| Helper tests (9 new) | tests/Squid.Calamari.Tests/Calamari/Commands/Common/EncodingPreservingFileIOTests.cs |
| xUnit serial collection marker (new) | tests/Squid.Calamari.Tests/Calamari/Commands/Common/RewriterEnvVarSerialCollection.cs |
| Per-step oversized-skip tests (3 new) | step test files |
Why a serial test collection
The 4 test classes that mutate MaxFileSizeMBEnvVar (env-var-process-wide state) MUST run sequentially — xUnit parallelizes across classes by default, and Environment.SetEnvironmentVariable calls race. Without the [Collection] marker, tests pass in isolation but flake under parallelism. Pinned by the new shared collection definition.
Test plan
-
Squid.Calamari.Tests: 277/277 (was 258; +19) -
Squid.UnitTests: 5625/5625 (unchanged — no cross-project changes) -
dotnet buildsolution-wide: 0 errors -
Env var constant name pinned (Rule 8): SQUID_CALAMARI_REWRITER_MAX_FILE_SIZE_MB -
Default 50 MB pinned literal — change requires deliberate test edit -
Invalid env var values (empty, whitespace, non-numeric, 0, negative, decimal) → default fallback (6-case theory) -
Valid env var values (1, 100, 1024) → override applied (3-case theory) -
Per-step skip: oversized file untouched, sibling normal file processed (3 tests — one per step) -
Exact-size boundary: file at the cap is allowed (inclusive) -
Staging: deploy a package containing a legit oversized config + an env-var override raising the cap
Non-breaking guarantee
| Surface | Status |
|---|---|
| Wire literals | Unchanged |
| Step output for files within limit | Unchanged |
| Step output for files over limit | NEW — was OOM (process crashed). Now logged + skipped. Strictly better |
| Default 50 MB | Generous for typical operator configs (50 MB ≫ any realistic web.config / appsettings.json) |
| Frontend | Untouched |
The behavior change for oversized files is a correction — was undefined (OOM, process crash, partial state), is now defined (skip + warn, continue with siblings). No correct deploy ever depended on the OOM path.