@@ -80,13 +80,18 @@ export class PostgresRunStore implements RunStore {
8080
8181 /**
8282 * Read a single row matching a non-id predicate from BOTH physical tables.
83- * A run lives in exactly one table (chosen by its id format), so a key-based
84- * predicate (idempotency key, "has this env any runs") can match a row in
85- * either. Query both in parallel and return the first match — at most one
86- * side is non-null, and legacy is preferred for a stable result if a
87- * predicate ever matches both. `task_run_v2` is an identical clone of
88- * `TaskRun`, so the SAME args (select/include and the security-scoping
89- * `where`) run unchanged against either delegate.
83+ * A key-based predicate (idempotency key, "has this env any runs") can match
84+ * a row in either table. Query both in parallel and return the match,
85+ * preferring `task_run_v2` when both are non-null.
86+ *
87+ * Today a run lives in exactly one table (createRun routes by id format), so
88+ * at most one side is non-null and the preference never bites. The later
89+ * slow legacy->v2 migration copies a run into task_run_v2 before operating on
90+ * it, so it transiently lives in BOTH tables with the v2 copy as the
91+ * canonical/operated-on one; preferring v2 returns the current row, not the
92+ * stale legacy source. `task_run_v2` is an identical clone of `TaskRun`, so
93+ * the SAME args (select/include and the security-scoping `where`) run
94+ * unchanged against either delegate.
9095 */
9196 async #findFirstAcrossTables(
9297 prisma : ReadClient ,
@@ -100,7 +105,7 @@ export class PostgresRunStore implements RunStore {
100105 v2Model . findFirst ( { where, ...args } ) ,
101106 ] ) ;
102107
103- return legacyRun ?? v2Run ;
108+ return v2Run ?? legacyRun ;
104109 }
105110
106111 async createRun (
@@ -871,9 +876,22 @@ export class PostgresRunStore implements RunStore {
871876 return model . findMany ( args as Prisma . TaskRunFindManyArgs ) ;
872877 }
873878
874- // BOTH tables in play. Offset pagination can't be expressed across two
875- // tables (applying `skip` to each skips N rows from its own result, not N
876- // from the merged result), so reject it rather than silently double-skip.
879+ // BOTH tables in play.
880+ //
881+ // FORWARD-LOOKING (slow legacy->v2 migration, a later stage): that migration
882+ // copies a run into task_run_v2 before operating on it, so a run can briefly
883+ // live in BOTH tables. When that lands, the cross-table reads below (both the
884+ // ordered #mergeOrdered path AND the unordered concat) must DEDUP BY id,
885+ // keeping the canonical v2 copy, or a doubly-present run is returned twice.
886+ // Dedup needs `id` forced into the projection (and stripped when the caller
887+ // didn't select it), and the "v2 wins" policy is part of the copy protocol,
888+ // so it belongs with the migration PR that introduces the overlap. Today
889+ // createRun routes by id format, so no run is in both tables and concatenation
890+ // is already duplicate-free.
891+ //
892+ // Offset pagination can't be expressed across two tables (applying `skip` to
893+ // each skips N rows from its own result, not N from the merged result), so
894+ // reject it rather than silently double-skip.
877895 if ( args . skip !== undefined ) {
878896 throw new Error (
879897 "RunStore.findRuns: `skip` (offset pagination) is not supported across the legacy TaskRun " +
0 commit comments