chore: child to parent singleton bleed - #14
Draft
brunozoric wants to merge 29 commits into
Draft
brunozoric wants to merge 29 commits into
brunozoric wants to merge 29 commits into
Conversation
brunozoric
force-pushed
the
bruno/refactor/child-parent-singleton-bleed
branch
2 times, most recently
from
June 25, 2026 08:17
de91da1 to
93b9f3f
Compare
Describes the singleton bleed-through bug where child container registrations pollute a parent's cached singleton, and the fix: cache singletons per-resolving-container instead of per-owning-container. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds singleton variants of all childContainer.test.ts scenarios to ensure cross-resolution behavior is bulletproof under singleton scoping. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add child decorator chain application to the singleton scoping fix - Add ProductRegistry example with parent/child/grandchild showing before/after behavior with 6 products and 2 decorators - Add registry.test.ts to impacted test list (found by code review) - Add 6 additional test scenarios: decorator chain, resolution ordering, singleton dependency chains - Document as breaking semantic change requiring semver bump Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
6 tasks covering: failing tests, Container.ts fix, existing test updates, singleton cross-resolution tests, decorator chain tests, and documentation updates. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cold review findings: falsy singleton cache bug, reflect-metadata
side-effect in types.ts, circular { multiple: true } stack overflow,
dead prettier scripts, undocumented composite/resolveAll behavior.
6 tasks covering: cache fix, reflect-metadata cleanup, prettier
removal, circular depth guard, builder fluency, AGENTS.md docs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds LifetimeScope.Global design (owner-context resolution, walk-up cache lookup, downward sharing) and fixes 20 issues found across 4 review passes including incorrect assertions, missing breaking tests, and table inaccuracies. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a child container registers an additional plugin and resolves a
parent-registered singleton registry (with { multiple: true } deps),
the child's plugin leaks into the parent's cached singleton instance.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Validates that a parent-registered singleton's cached instance is not affected by later parent registrations when resolved from a child whose own singleton is already cached. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- AGENTS.md: add registry test directory to test structure, add singleton bleed-through to known issues - Design spec: mark regression tests 1-2 as implemented/failing, renumber remaining planned tests - Implementation plan: mark Task 1 complete with actual commit hashes and file paths - PR description: split tests section into committed (failing) and planned, add current status section Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Proves that registerInstance in a child container does not pollute the parent's resolution — instance registrations are per-container and isolated, unlike the singleton cache bleed bug. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add the ContainerToken inheritance issue to AGENTS.md known issues, test structure, and the singleton scoping design doc. Instance registrations are isolated (no bleed), but child containers inherit the parent's self-registration by default. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add the 5 new ContainerToken tests to committed tests section, current status, and test plan checklist. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The rebase left both sides of the merge in pnpm-workspace.yaml: the new settings block was added but the old one was never removed, leaving orphaned indented lines dangling under frozenLockfile and duplicate keys. The result was invalid YAML - pnpm could not load its configuration at all, so every command failed on a fresh clone. Rewrite the file as a single deduplicated block, validating every key against pnpm 12.3.4 (which ships as a native binary and warns on unrecognized keys). Tighten the settings while here: - allowBuilds: esbuild true -> false. esbuild is not installed (it is only an optional peer of rslib; this repo builds via rspack), so this was granting lifecycle-script rights to a package absent from the tree. core-js and node-gyp are likewise pinned false so they cannot gain build rights if they ever appear transitively. - autoInstallPeers: false. pnpm defaults this to true and silently installs missing peers as real packages; with strictPeerDependencies an unmet peer is now a hard error instead of an unreviewed dependency. This is recorded in the lockfile, hence the lockfile change - the package set is unchanged (493 entries before and after), only peer-resolution bookkeeping differs. - minimumReleaseAgeIgnoreMissingTime: false. Keeps a registry that cannot date its releases from bypassing both the age gate and trustPolicy. - strictStorePkgContentCheck / trustLockfile / verifyDepsBeforeRun: pin store content verification, force lockfile re-verification against the supply-chain policies on every install, and refuse to run scripts against a node_modules that does not match the lockfile. - saveExact: pin exact versions for anything added later. No age-gate or trust exceptions are configured; this package has no first-party dependencies that would need them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
brunozoric
force-pushed
the
bruno/refactor/child-parent-singleton-bleed
branch
from
September 11, 2026 08:24
8346446 to
b95ae9b
Compare
Revision 2 of the design. Records five review findings against the first revision and the decision for each: the decorator chain walk double-applied ancestor decorators (use resolveFrom.applyDecorators once), transient decorator behavior contradicted the table (decorators follow the requesting container on every non-global path), global scope was order-dependent (cache in the owning container), the falsy cache check (defensive only), and stale line/commit references. Adds a branch-hygiene section for the engines/CI mismatch and stale cache-key-collision material. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Adds 28 tests across three files that encode the revision 2 spec: singletonBleed.test.ts, childContainer/singletonCrossResolution.test.ts and singletonDecoratorChain.test.ts. 20 fail today because the fix is not implemented; the two existing registry regression tests keep failing as before. No passing test changed and no source changed. Global scope tests are deferred until the inGlobalScope() API exists. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Copies .claude/skills/handoff from webiny-js and rewrites it for this repo: pnpm lint/build/test as the check gate, the docs table points at AGENTS.md, docs/*-design.md, docs/superpowers/plans, pr/<branch>.md, bugs/ and .changeset, handoff files go to docs/handoff/, and tests may fail only when they pin a designed-but-unimplemented behavior. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ems in spec Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Add docs/2026-09-14-scoping-prior-art.md covering tsyringe, InversifyJS, NestJS, Microsoft DI, Autofac, Spring, Guice, Symfony, Laravel and shaku, framed around whose view builds a shared instance and where it is cached. Record two new open items in the design spec: scope naming (every surveyed container keeps Singleton meaning the shared instance and names the per-container behavior separately) and the unguarded captive-dependency case. Mirror both in the PR description, link the survey from AGENTS.md, and mark the implementation plan as stale pending the naming decision. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Issue
A singleton registered in a parent container is cached in the parent but built from the view of whichever container resolves it first. If a child resolves it first, the child's registrations (
{ multiple: true }entries, overridden dependencies) end up in the parent's cached instance and every other container gets them.Root cause in
resolveRegistration: cache inthis.instances, dependencies fromresolveFrom. Two related gaps: decorators registered in a child never apply to a parent-owned service the child resolves, and there is no scope for "one instance shared by the whole hierarchy" once singletons stop leaking.Pinned by 22 failing tests. Implementation not started.
Possible solutions
Pick one context container per resolution and use it for cache, dependencies and decorators. Two ways to name the result:
A. Redefine
Singleton, addGlobal(current spec).inSingletonScope()becomes one instance per resolving container;inGlobalScope()is one instance per registration, built and cached in the owner. Isolation by default. Breakschild.resolve(X) === parent.resolve(X); major release.B. Keep
Singleton, addContainerScoped.inSingletonScope()keeps shared identity but is built from the owner's view, which alone removes the bleed. NewinContainerScope()gives the per-container instance. Matches tsyringe, Autofac and Microsoft DI naming. Patch for the fix, minor for the new scope.Same
resolveRegistrationchange either way; only the meaning of the existing keyword differs. Decision pending.Docs
docs/2026-05-26-per-container-singleton-scoping-design.md(revision 2, open items at the end)docs/2026-09-14-scoping-prior-art.md🤖 Generated with Claude Code