Skip to content

Add file-size cap to rewriter steps (50 MB default, env override)

Placeholder ppxd requested to merge feat/calamari-g1-file-size-cap into main

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 build solution-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.

Merge request reports

Loading