Decouple G1.x wire literals from the IIS handler
Summary
Architectural fix from the G1.x review. The rewriter steps had generic implementations but were silently coupled to the IIS handler via IIS-prefixed wire literals. A future RunScript / Docker / nginx handler wanting these features would have had to emit IIS-prefixed variable names — wrong on its face, and a breaking change to fix later. Fix it now.
Stacks on #366 (the H1 hardening PR). After both #365 and #366 merge, this PR's base can be retargeted at master.
SquidWeb zero-impact: the IIS editor's wire-literal arrays all reference the IIS-prefixed names; the IIS handler emits them; the Calamari step's legacy fallback reads them. The new canonical names are purely additive — for future generic handlers.
The two-halves wire contract
Each of the 3 features (SubstituteInFiles / ConfigurationTransforms / JsonConfigVariables) now has:
| Half | Where | What new code should use | Back-compat |
|---|---|---|---|
Canonical (top-level on *VariableNames) |
Squid.Action.<Feature>.<Property> |
|
Step reads FIRST |
Legacy (nested .Legacy class) |
Squid.Action.IISWebSite.<Feature>.<Property> |
|
Step falls back to it when canonical is absent |
// IsEnabled / Execute pattern in each step
var raw = context.Variables.Get(<Feature>VariableNames.Enabled) // canonical first
?? context.Variables.Get(<Feature>VariableNames.Legacy.Enabled); // legacy fallback
What's renamed at the wire-literal level
| Feature | Legacy (kept, IIS emits this) | Canonical (new, generic) |
|---|---|---|
| SubstituteInFiles | Squid.Action.IISWebSite.SubstituteInFiles.Enabled |
Squid.Action.SubstituteInFiles.Enabled |
| ConfigurationTransforms | Squid.Action.IISWebSite.ConfigurationTransforms.Enabled |
Squid.Action.ConfigurationTransforms.Enabled |
| StructuredConfigurationVariables → JsonConfigVariables | Squid.Action.IISWebSite.StructuredConfigurationVariables.Enabled |
Squid.Action.JsonConfigVariables.Enabled |
The JSON-leaf feature's canonical name drops the StructuredConfigurationVariables label — aligns with the frontend's existing feature ID Squid.Features.JsonConfigurationVariables, and accurately describes the JSON-only scope.
The C# class StructuredConfigVariableNames keeps its name (no ripple-rename); new alias JsonConfigVariableNames exposes the same canonical literals for new handlers.
What's in the box
| Layer | File |
|---|---|
| Canonical + Legacy constants + dual-read | src/Squid.Calamari/Commands/Substitution/SubstituteInFilesStep.cs |
| Same | src/Squid.Calamari/Commands/Configuration/ConfigurationTransformsStep.cs |
Same + new JsonConfigVariableNames alias |
src/Squid.Calamari/Commands/StructuredConfig/StructuredConfigVariablesStep.cs |
| Drift detector — Sub |
tests/Squid.UnitTests/.../IISSubstituteInFilesWireContractTests.cs (8 tests; was 4) |
| Drift detector — Conf |
tests/Squid.UnitTests/.../IISConfigurationTransformsWireContractTests.cs (6 tests; was 4) |
| Drift detector — Json |
tests/Squid.UnitTests/.../IISStructuredConfigVariablesWireContractTests.cs (8 tests; was 4) |
| Back-compat + precedence per-step | step test files |
| 3-step composition E2E (review gap T1) |
tests/Squid.Calamari.Tests/Calamari/Commands/RewriterPipelineCompositionTests.cs (new, 3 tests) |
Test plan
-
Squid.Calamari.Tests: 258/258 (was 246; +12) -
Squid.UnitTests cross-project wire-contract: 5625/5625 (was 5615; +10) -
Build solution-wide: 0 errors -
3-step composition test drives all 3 steps in order using ONLY canonical literals — proves a future generic handler can use the pipeline without any IIS-specific knowledge -
Back-compat tests drive the steps with ONLY legacy literals + real file rewriting — confirms existing IIS deploys keep working -
Precedence test confirms canonical wins when both are set (dual-emit scenario) -
Idempotency byte-identity test confirms running the chain twice produces no diff (review gap T2) -
Staging: real IIS deploy with existing project definitions (IIS-prefixed variables) — backward-compat smoke -
Staging: synthetic test with canonical-only variables driving a RunScript handler (no such handler exists yet, but the wire surface is ready)
Non-breaking guarantee
| Surface | Status |
|---|---|
Existing wire literals (Squid.Action.IISWebSite.*) |
Preserved as .Legacy.*; same string values |
| IIS PS1 script | Unchanged — still reads IIS-prefixed names |
IISDeployProperties constants |
Unchanged |
Frontend (DeployToIisEditor.tsx) |
Unchanged — arrays still reference IIS-prefixed names |
| Operator's existing deployment definitions in DB | Unchanged behavior |
SubstituteInFilesStep.EnabledVariableName etc. (internal) |
Value changes from IIS-prefixed to canonical — only referenced by the test file that's updated in the same PR |