Skip to content

Guard the DI container's orderings and shape - #2119

Draft
NickJosevski wants to merge 1 commit into
mainfrom
nj/di-container-ordering-guard
Draft

NickJosevski wants to merge 1 commit into
mainfrom
nj/di-container-ordering-guard

Conversation

@NickJosevski

@NickJosevski NickJosevski commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Adds two DI guards to Calamari.Tests. No production code changes.

Why

Script wrappers form an execution chain; file-format replacers are tried in sequence. A reordering changes deployment behaviour, but every wrapper still constructs and every command still resolves — so the existing suite stays green. Nothing currently detects it.

What

ContainerOrderingFixture — asserts the orderings directly. The load-bearing test is ScriptWrapperPrioritiesAreUnique: ScriptEngine sorts with OrderByDescending, which is stable, so two wrappers sharing a priority silently hand the tie-break to the container's collection ordering.

Priorities are distinct today (1003/1002/1001/1000/100) — but AwsScriptWrapper, AzureContextScriptWrapper, GoogleCloudContextScriptWrapper and AzureServiceFabricPowerShellContext all sit on CloudAuthenticationPriority in their separate flavours. A second cloud wrapper in any one flavour would make that chain container-dependent. Asserting the invariant is a better guard than pinning a sequence.

The rest pin the wrapper chain for a Kubernetes/AWS/PowerShell step, the file-format replacer order, and discoverer key uniqueness.

ContainerSnapshotFixture — compares a rendering of the whole container against ContainerSnapshot.expected.txt: registrations, lifetimes, collection order, and the concrete types injected into every command. Targeted assertions only catch what someone thought of; this catches anything observable that moves. Re-approve with CALAMARI_APPROVE_CONTAINER_SNAPSHOT=1.

Both are PlatformAgnostic. The snapshot is normalised so one file serves every platform — OS-chosen filesystem and certificate-store implementations collapse to placeholders, Autofac's __RegistrationOrder tick counter is dropped, collection fields render distinct element types rather than counts.

The expected file is deliberately not named *.approved.*.gitattributes marks that pattern binary -text (protecting ~207 existing approved files from line-ending normalisation), which would hide the diff this test exists to surface.

Rebased onto main — what that showed

Originally baselined at Autofac 4.8.0 so the upgrade would land as a reviewed diff. That upgrade has since merged via #2131, so this is now baselined at 9.3.1 to match main.

Rebasing across it reproduced exactly the predicted diff and nothing else — the recorded version line plus five RegisterInstance registrations gaining AutoActivate, with all five ordering tests still passing:

2c2
< 4.8.0.0
---
> 9.3.1.0
27,28c27,28 · 34c34 · 99c99 · 140c140
- CommonOptions ×2, ConsoleLog, <PhysicalFileSystem>, SystemSemaphoreManager
+ ... :: AutoActivate+... :: ... :: [__AutoActivated=True]

Those five are inert: none of the instances is IDisposable, and the codebase has no OnActivating/OnActivated handlers. Autofac 9 simply attaches already-constructed instances at container-build time rather than at first resolve.

The rebase crossed eight commits of main — Octostache 3.9.3 and the new date filters, the unconditional docker credential helper, a new feature toggle, removal of the net462 reference assemblies. Nothing outside those six lines moved. For a golden file that's the evidence that matters: it is stable enough not to cry wolf.

Verification

14 tests green locally (these 6, plus CommandResolutionTests and PrioritisedRegistrationFixture).

The golden was captured on macOS. The normalisation should make it OS-invariant, but CI running it on Windows is the actual proof — if the platform mapping missed something it surfaces there as a clear diff rather than a mystery.

🤖 Generated with Claude Code

@NickJosevski
NickJosevski force-pushed the nj/di-container-ordering-guard branch from 28bd8ba to 85142c0 Compare August 10, 2026 04:24
Script wrappers form an execution chain and file-format replacers are tried in
sequence, so a reordering changes deployment behaviour. Nothing currently
detects that: every wrapper still constructs and every command still resolves,
so the existing suite stays green.

Two guards, both PlatformAgnostic:

ContainerOrderingFixture asserts the orderings directly. The load-bearing one is
ScriptWrapperPrioritiesAreUnique - ScriptEngine sorts with OrderByDescending,
which is stable, so a shared priority silently hands the tie-break to the
container's collection ordering. Priorities are distinct today (1003/1002/1001/
1000/100), but AwsScriptWrapper, AzureContextScriptWrapper,
GoogleCloudContextScriptWrapper and AzureServiceFabricPowerShellContext all sit
on CloudAuthenticationPriority in their separate flavours, so a second cloud
wrapper in one flavour would make the chain container-dependent. The rest pin the
wrapper chain for a Kubernetes/AWS/PowerShell step, the file-format replacer
order, and discoverer key uniqueness.

ContainerSnapshotFixture compares a rendering of the whole container against an
approved file: registrations, lifetimes, collection order, and the concrete types
injected into every command. Targeted assertions only catch what someone thought
of; this catches anything observable that moves. Re-approve with
CALAMARI_APPROVE_CONTAINER_SNAPSHOT=1.

The snapshot is normalised so one file serves every platform: the OS-chosen
filesystem and certificate-store implementations collapse to placeholders,
Autofac's __RegistrationOrder tick counter is dropped, and collection fields
render distinct element types rather than counts.

Baselined at Autofac 9.3.1, matching main. The instrument was built to catch the
4.8.0 -> 9.3.1 bump, which has since landed via #2131; rebasing across it
reproduced exactly the predicted diff - the recorded version line plus five
RegisterInstance registrations gaining AutoActivate - with the five ordering
tests still passing. Those five are inert: none of the instances is IDisposable
and the codebase has no OnActivating/OnActivated handlers.

That rebase crossed eight commits of main, including Octostache 3.9.3, the
unconditional docker credential helper and a new feature toggle. Nothing outside
those six lines moved, which is the evidence that matters for a golden file: it
is stable enough not to cry wolf.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@NickJosevski
NickJosevski force-pushed the nj/di-container-ordering-guard branch from 85142c0 to b7fd680 Compare August 14, 2026 06:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant