Files
triggerdotdev--trigger.dev/internal-packages
Daniel Sutton aa9b888988 refactor(run-store): hold RoutingRunStore's stores in a keyed shard map (#4752)
## What

`RoutingRunStore` held two named store fields, `#new` and `#legacy`, and
took its routing policy from the order the statements happened to run
in. It now holds a `Map<ShardKey, RunStore>`, and the three policies
that were implicit are readable data:

- **`#probeOrder`** (`new` → `legacy`) — the sequential probe for a
lookup with no routable id. The first non-null result wins, and the
*last* entry owns the canonical not-found throw.
- **`#precedence`** (`legacy` → `new`) — ascending authority for a
merge, so the highest-authority shard wins a duplicate id.
- **`#idlessRouteShard`** (`new`) and **`#idlessWaitpointShard`**
(`legacy`) — the two id-less defaults, which differ by role and were
previously two unrelated literals in unrelated methods.

The two orders are the **reverse of each other**, which is why they are
separate fields rather than one ordering. Nine sites observe the
result-array order and must iterate `#probeOrder`; five decide a value
by which shard wins a duplicate and must iterate `#precedence`. Five
more sum counts and are order-independent, because addition commutes.

Four helpers absorb the twenty-six hand-written fan-outs —
`#probeFirst`, `#fanOut(order, fn)`, `#fanOutPartitioned`,
`#shardsExcept` — and `#shardKeyOf` replaces the inline
residency-to-store ternaries. `#fanOut` takes its order as an argument
so every call site states which policy it uses.

The constructor keeps its exact options type. No union arm, no `shards`
member: that would loosen the excess-property check and silently retire
the `@ts-expect-error onLegacyRead` lock in the test corpus. N-way
construction is a later change.

## One behaviour change

`findManyTaskRunWaitpoints` merged its edge rows NEW-first into a
last-wins dedupe, so a duplicate edge id resolved to the **legacy** row
— the opposite of the rule the other four merges follow, and the
opposite of what `dedupeEdgesById`'s own comment claimed. No test pinned
it in either direction.

It now resolves NEW-wins, consistent with every sibling merge, and a new
test pins the winner so it cannot drift back silently.

Reaching this case needs one edge id present on both stores at the same
time, with no routable `taskRunId`. That only arises from drain
mirroring. The drain seam is removed (`runOpsStore.test.ts`, "fan-out
spans NEW+LEGACY with no drain seam"), so **no new duplicates can be
created** — but removing the code does not delete rows it previously
wrote, and this class still carries comments treating mirrored rows as a
live data condition. Whether any historical duplicate edge rows persist
is an empirical question about production data, not something this diff
settles.

If such a row is hit, the two copies either agree — in which case the
winner is immaterial — or they have diverged, in which case NEW is the
authoritative copy by the router's own precedence rule. So the corrected
behaviour is at least as correct as the old one in every reachable case.

Everything else is behaviour-preserving.

## How it was verified

- **`internal-packages/run-store`: 69 files, 379 tests pass.** The
corpus is the regression gate for this refactor. 67 of the 68
pre-existing test files are byte-identical; the one that differs
(`runOpsStore.mixedResidency.test.ts`) changes only `//` comments.
- **`internal-packages/run-engine`: 12 files, 69 tests pass** — every
file that constructs the router, exercised at runtime.
- **The `@ts-expect-error onLegacyRead` lock still fires.**
`tsconfig.build.json` excludes `*.test.ts`, so a green typecheck does
not cover it. A scratch probe confirmed `tsc` still reports `TS2353` for
`onLegacyRead` and no error for the three real options.
- **All 48 construction sites outside the package compile unchanged.**
`tsconfig.check.json` also excludes `*.test.ts`, so the 25 webapp test
files were checked with the test exclusion dropped and compared against
the same check on the base commit: 614 errors before, 614 after, zero
present in one and not the other. Those 614 are pre-existing in
never-typechecked test files.
- `typecheck` passes for `run-store`, `run-engine` and `webapp`. `knip`
reports nothing in `run-store`.

## Also

Refreshes the sixteen stale `runOpsStore.ts` line references in
`runOpsStore.mixedResidency.test.ts`, each verified against the symbol
it names.

## Notes for the reviewer

- The riskiest possible mistake in this diff is a fan-out passing the
wrong order — the compiler cannot catch it, because both orders are
`readonly ShardKey[]`. The five `#precedence` sites are `#findRunsOpen`,
`findRunsByIdempotencyKeys`, `#collectManyWaitpoints`,
`findManyTaskRunWaitpoints` and `findManyWaitpointTags`. Those are the
lines worth the closest read.
- Four sites previously derived "the other store" by object identity
(`home === this.#new ? ...`). They now compare keys. The two are
equivalent: in single-database mode both keys map to the same store
object, and when the stores are distinct, identity and key comparison
agree.
- No changeset and no `.server-changes` note: the package is internal
and the one behaviour change is unreachable in production, so a release
note would tell a user nothing.
- Two CI checks fail for reasons that predate this branch and reproduce
on the base commit: `lint` (~16 unknown `react/*` rules make
`.oxlintrc.json` fail to parse, which disables oxlint entirely —
including the two `trigger-runops` fences) and `knip` (`unrun`, an
unused devDependency on the default branch). Both want their own fix.
2026-08-21 17:05:51 +01:00
..