Skip to content

ObjectRegistry: add qualified-name iteration API to fix cross-package collision in getClassNames-driven loops #1276

@willgriffin

Description

@willgriffin

Problem

ObjectRegistry.getClassNames() returns class names deduplicated by simple name. When two packages register classes with the same simple name (e.g. both ship an Event), getClassNames() returns the name once and downstream callers — including schema-bootstrap loops in test helpers and tooling — resolve it via getClass(simpleName)findClass's multi-strategy lookup, which picks one arbitrary registration. The other colliding class never gets iterated, never gets its schema prepared, never participates in the loop.

This is a pre-existing architectural seam that R5-canon's qualified-name canonicalization made more visible. R5-canon (#1274) made getInheritanceChain / getSTIBase / getDescendants return qualified names internally so call-site comparisons are unambiguous, but iteration over getClassNames() itself stays simple-name-keyed.

Concrete failure mode (from #1274 Copilot review)

packages/core/src/testing/database.ts:216getTestDatabase iterates ObjectRegistry.getClassNames() to bootstrap schema. The R5-canon fixup at that line computes the qualifiedClassName for the comparison, but the loop input is already deduplicated. In a collision, only one of two same-simple-name classes is iterated; the other's table is never created.

Other likely affected sites worth auditing:

  • packages/cli/src/commands/utilities.tsdb:setup / dependency-order printer also iterate getClassNames() / getInitializationOrder() and apply per-class schema work.
  • packages/core/src/registry/schema-builder.tsgetAllSchemas iterates getClasses() directly (the underlying Map keyed by registrationKey, which IS qualified). So this path is collision-safe already; just confirming.
  • Any other consumer that calls getClassNames() and treats each entry as a unique identity.

Proposed direction

  1. Add a new registry API for qualified-name iteration:

    /**
     * Iterate every registered class by qualified name (or simple name
     * when the registration has no package context). Unlike
     * `getClassNames()`, this preserves cross-package collisions —
     * two packages shipping the same simple name appear as two
     * separate entries.
     */
    static getQualifiedClassNames(): string[];

    Underlying iteration is over getClasses().keys() (or getClasses().values() mapping to qualifiedName ?? name).

  2. Migrate collision-sensitive callers to the new API. At minimum:

    • testing/database.ts:getTestDatabase schema bootstrap loop.
    • cli/commands/utilities.ts schema-related iterations.
    • Audit the rest for ones that should switch.
  3. Keep getClassNames() for backward compatibility (the dedup-by-simple-name behavior is fine for display / discovery contexts where collisions are reported separately). Document the trade-off.

  4. Add a regression test that registers two same-simple-name classes under different packages, iterates via the new API, and confirms both get their tables prepared.

Why this wasn't fixed in #1274

R5-canon's PR was already a ~20-file refactor of the registry's core lookup invariants. Adding a new public API + migrating multiple call sites is a separate concern with its own scope and review surface. Filed here so it doesn't get lost.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions