Skip to content

fix(di): flip injector hardener S2 S3 S7 - #3421

Merged
bpamiri merged 1 commit into
developfrom
cursor/injector-hardener-s2-s3-s7-2a0f
Aug 25, 2026
Merged

fix(di): flip injector hardener S2 S3 S7#3421
bpamiri merged 1 commit into
developfrom
cursor/injector-hardener-s2-s3-s7-2a0f

Conversation

@bpamiri

@bpamiri bpamiri commented Aug 25, 2026

Copy link
Copy Markdown
Collaborator

Why

Peter flipped injector HOLDs S2, S3, and S7. Rebind kept stale singleton/request flags and request.$wheelsDICache. asSingleton / asRequestScoped were silent no-ops when lastMappedName was empty, and map(b).asSingleton().to(...) flagged the previous key. getMappings() returned the live internal struct.

This PR flips those three contracts in Injector.cfc and updates the HOLD-pin specs so they assert the new behavior.

Desk IDs stay S1–S10. S1/S4/S5/S6/S8/S9/S10 stay as already proven on develop. S4 stays dotted-path fallback.

Scope

  • vendor/wheels/Injector.cfc — production flips
  • vendor/wheels/tests/specs/injector/InjectorHardenerSpec.cfc — HOLD pins become the new contract
  • changelog.d/injector-hardener-s2-s3-s7.changed.md

Out: CLI Application.cfc injector, SQL-injection specs, docs/blog, inject() in controller/services.cfc, $snapshotBindings/$restoreBindings, application.wheelsdi self-register.

Exception types

Reuse Wheels.Injector (same type as to() without map(), and as S9 empty map("") then to()).

  • asSingleton() with no in-progress map() and no completed to() throws Wheels.Injector
  • asRequestScoped() under the same empty state throws Wheels.Injector

No new type.

Tradeoffs

Rebind reset lives in map(), not to(). That lets map(b).asSingleton().to(...) flag b after the reset. Resetting in to() would wipe the flag that asSingleton just set.

getMappings() now returns Duplicate(variables.mappings). Callers that mutated the old live struct to register aliases will no longer change the container.

Dropping request.$wheelsDICache on rebind is the whole request cache, not one key. That is what the old HOLD pin asserted we never did.

Desk S1–S10

ID Status Note
S1 PROVEN Already on develop. Two transient getInstance calls of the same mapping get distinct instances. Unchanged.
S2 PROVEN flipped Rebind resets singleton and request-scoped flags and drops request.$wheelsDICache. First bind of a new name does not drop the cache. Unrelated flags stay.
S3 PROVEN flipped asSingleton / asRequestScoped throw Wheels.Injector when lastMappedName and currentMapping are empty. map(b).asSingleton().to(...) flags b, not the previous key.
S4 PROVEN Already on develop. Unmapped getInstance is dotted-path fallback. Unchanged.
S5 PROVEN Already on develop. Circular-recovery catch asserts Wheels.DI.CircularDependency. Unchanged.
S6 PROVEN Already on develop. Resolve-all skips missing keys. Unchanged.
S7 PROVEN flipped getMappings() returns a Duplicate copy. Mutating it, or mapping later, does not change later getMappings() or containsInstance.
S8 PROVEN Already on develop. onDIcomplete once per constructed instance. Unchanged.
S9 PROVEN Already on develop. Empty map("") then to() throws Wheels.Injector. Unchanged.
S10 PROVEN Already on develop. Both flags set, singleton wins. Unchanged.

Verification

Command actually run:

wheels test --core --ci --filter=injector
Running core tests (sqlite)...
Scope: wheels.tests.specs.injector
30 passed (0.15s)

JSON from the same filter (directory=wheels.tests.specs.injector):

totalPass=30
totalFail=0
totalError=0
totalSkipped=0
totalSpecs=30
bundlesDiscovered=1
directoryRejected=false
directoryResolved=wheels.tests.specs.injector

Also ran, not the driver:

wheels test --core --ci --filter=di
39 passed (0.13s)
totalPass=39
totalFail=0
totalError=0
totalSkipped=0
totalSpecs=39
bundlesDiscovered=2
directoryRejected=false
directoryResolved=wheels.tests.specs.di

Wheels CLI 4.0.6. Lucee 7.0.0.395. sqlite.

Adobe and BoxLang were not run here.

HEAD a28a2814e982a3997eba0bf0b9343a69fcfb14e0

Base 32aa26747e29b884a9970f77c100cd06b0055905 (develop after injector hardener prove).

Do not merge. Hive off merge.

Open in Web Open in Cursor 

Rebind resets singleton/request flags and drops request.$wheelsDICache.
asSingleton/asRequestScoped throw Wheels.Injector when no mapping is in
progress, and map(b).asSingleton().to(...) flags b. getMappings()
returns a Duplicate copy.

Signed-off-by: Cursor Agent <cursoragent@cursor.com>

Co-authored-by: Peter Amiri <peter@alurium.com>
@bpamiri
bpamiri marked this pull request as ready for review August 25, 2026 15:36
@bpamiri
bpamiri merged commit 24cd442 into develop Aug 25, 2026
13 checks passed
@bpamiri
bpamiri deleted the cursor/injector-hardener-s2-s3-s7-2a0f branch August 25, 2026 15:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants