MotirBuilding in public
MOTIR · moooon
onMotir
You’re viewing a public project. Anyone can view it — no account needed. Sign in to submit, upvote, or comment on requests.View-only — you can’t edit work items
MOTIR-3458

(motir-core) Make the subscriber set reachable from the emit path, with a guard that fails when it is not

Done
Description

Implement whatever the decision card settled, so that dispatchEventToEngine resolves the real subscriber set when called from an ordinary Next.js request path — and add the guard that fails if it ever stops doing so.

Reproduce first

Do not start from the fix. The claim this card is built on is that engineSubscribers(name) returns [] on a request path; reproduce that before changing anything, so the guard you write is known to fail against origin/main rather than assumed to.

The cheapest reproduction is the guard itself (below) written first: it fails on the unfixed tree and passes after. If it passes before the fix, the premise is wrong — stop and report rather than proceeding, because everything downstream in this story assumes the defect is real.

The change

Per the ADR amendment. The recommendation there is a data-only manifest: the emit path reads { id, trigger, cron, maxAttempts } and never the handler, so the module it imports pulls in no services and closes no import cycle. dispatchEventToEngine reads only sub.id and sub.maxAttempts today, and hasInngestSubscribers only id and trigger — verified on origin/main@b944dab5 — so no emit-path caller loses anything.

Whatever the mechanism, two properties must hold and both are worth stating as invariants rather than as steps:

  • Completeness stays BY CONSTRUCTION, not by anyone remembering. defineJob is the single choke point every job passes through, and registerEngineJob / registerSchedule both already register from inside it for exactly that reason. A manifest assembled by hand is the second list lib/jobs/engine/registry.ts's own header refuses; do not introduce one.
  • The worker keeps its full registry. scripts/worker.ts executes handlers and must go on importing @/lib/jobs/registry for its side effect. Its comment explains why that import looks unused — keep it, and update it if this card changes what it guarantees.

Scope boundary

ENDS at: the emit path resolving subscribers correctly, with the guard, in this repo, on this PR.

Does NOT change: any file under lib/jobs/definitions/ (no job's behaviour moves), the routing set's semantics in lib/jobs/engine/cutover.ts, or lib/jobs/schedules.ts — the schedule registry has the identical defect and belongs to MOTIR-3416; the ADR amendment states the answer for it so that story inherits it rather than re-deriving it. If the chosen mechanism happens to fix both at no extra cost, take it and say so in the PR body.

Does NOT move any job onto the engine. MOTIR_POSTGRES_JOB_IDS is untouched by this PR; this card makes the switch WORK, it does not throw it.

Acceptance criteria

  • A NEW spec — tests/jobs/engine-subscriber-reachability.test.ts — asserts the emit path's subscriber set equals the full registry's, by building the two sets from DIFFERENT module graphs: inside vi.resetModules(), import ONLY @/lib/jobs/sendEvent and read engineSubscribers from the same fresh graph; then reset again, import @/lib/jobs/registry, and read it again. The two sets must be equal. Deriving both from one import is the failure mode this test exists to catch, so it cannot be written that way.
  • That spec contains no top-level import '@/lib/jobs/registry', and a comment says why — the poisoning import at tests/jobs/engine-dispatcher.test.ts:24 is precisely what has kept this defect invisible.
  • The spec asserts the concrete case by name: work-item/transitioned resolves four subscribers from the emit-path graph.
  • The guard FAILS on the tree as it stands before this card's change, and the PR body quotes that failure.
  • No file under lib/jobs/definitions/ is modified, and no job's observable behaviour changes.
  • pnpm lint and the typecheck pass, with no new ESLint disable and no widening of the JOB_ENGINE_RESTRICTION import boundary.

Context refs

  • lib/jobs/engine/registry.tsregisterEngineJob, engineSubscribers, and the header on import-completeness
  • lib/jobs/engine/dispatcher.ts — the two emit-path readers and the fields they actually use
  • lib/jobs/sendEvent.ts — the emit path
  • lib/jobs/defineJob.ts — the choke point, and registerSchedule beside it
  • scripts/worker.ts — the load-bearing side-effect import
  • tests/jobs/engine-dispatcher.test.ts — the import that makes the existing suite green
  • eslint.config.mjsJOB_ENGINE_RESTRICTION, the boundary a new module must stay inside