Skip to content

di's arity gate is the last mute diagnostic, and NeedsGate is the proven replacement #93

Description

@btravers

From the 2026-08-23 DX audit. Generalizes #83, which is the same finding scoped
to tapped — this covers the mechanism everywhere it survives.

The problem

Module.build and Module.scoped gate unmet dependencies with a conditional
rest tuple, and an arity error carries no type:

error TS2554: Expected 5 arguments, but got 2.

Neither the UNSATISFIED DEPENDENCIES label nor the missing ports reach the
message. This is the worst diagnostic in the framework, and the prose knows it:
the root CLAUDE.md, packages/di/CLAUDE.md, docs/explanation/compile-time-wiring.md
and docs/reference/di/entry-points.md all carry paragraphs explaining what the
bare arity line actually means and how to hand-spell the phantom arguments to
make the port print. Four documents apologizing for one gate is the measure of
the cost. GHC treats custom type errors as first-class; the marker technique is
this repo's equivalent, and every OTHER gate already uses it.

The replacement is proven in-repo

#50's NeedsGate rides an intersection whose failure branch is an object with
one required property, and the diagnostic ends on the port (measured):

Property '"UNDECLARED NEEDS — name it in `needs`"' is missing in type
  '{ provides: [...]; }' but required in type
  '{ readonly "UNDECLARED NEEDS — name it in `needs`": Logger; }'.

The same shape now guards routerFor's scope gate (#90) and start's
StartGate. The arity tuple predates all of them.

The work

  • Module.build(module) / Module.scoped(module, use): replace the
    ..._missing rest tuple with a marker intersected onto the module
    parameter — unknown when Needs (minus Scope, for scoped) is never,
    the named-property object otherwise.
  • tapped (tapped's gate is an arity error: the last rest tuple #83): same move, which closes that issue.
  • Every @ts-expect-error — UNSATISFIED DEPENDENCIES arm in the type tests
    re-measured; examples/order-application/src/needs-gate.test-d.ts pins the
    new sentence.
  • The four apology paragraphs shrink to one line each, stating what now prints.

Risks worth measuring first

The rest tuple has one property the intersection must not lose: it fires on the
CALL, not on the argument, so --pretty's related-information span differs.
And Module<X, E, N>'s covariant _needs must still refuse the laundered
assignment — the marker sits beside that check, not instead of it. Both are a
scratch-file measurement before the sweep, per house rules.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Wanted, not urgentenhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions