( table: TableDefinition, row: TableRow, excludeGroupId?: string )
| 136 | * to claim past a dispatcher pre-stamp (`pending` with `executionId: null`) |
| 137 | * — that's a placeholder, not a real worker claim. */ |
| 138 | export function pickNextEligibleGroupForRow( |
| 139 | table: TableDefinition, |
| 140 | row: TableRow, |
| 141 | excludeGroupId?: string |
| 142 | ): WorkflowGroup | null { |
| 143 | const groups = table.schema.workflowGroups ?? [] |
| 144 | for (const group of groups) { |
| 145 | if (group.id === excludeGroupId) continue |
| 146 | const exec = row.executions?.[group.id] |
| 147 | // Dispatcher pre-stamp (pending + executionId: null) is a queued marker: an |
| 148 | // explicit run request whose cell-task bailed on lock contention. It's the |
| 149 | // handoff — the cascade owner runs it next. Treat it as `isManualRun` so an |
| 150 | // explicitly-requested `autoRun: false` group is honored (the dispatcher |
| 151 | // already applied manual eligibility before stamping it); groups with no |
| 152 | // marker stay `isManualRun: false` so pure dep-fill auto-cascade still |
| 153 | // respects `autoRun`. Either way the placeholder is cleared from the |
| 154 | // eligibility view so the group is claimable. |
| 155 | const isRequested = exec?.status === 'pending' && exec.executionId == null |
| 156 | const effectiveRow = isRequested |
| 157 | ? { ...row, executions: { ...row.executions, [group.id]: undefined } as RowExecutions } |
| 158 | : row |
| 159 | if (isGroupEligible(group, effectiveRow, { isManualRun: isRequested, mode: 'incomplete' })) { |
| 160 | return group |
| 161 | } |
| 162 | } |
| 163 | return null |
| 164 | } |
| 165 | |
| 166 | /** |
| 167 | * Shared options for the three `scheduleRuns*` entry points. `isManualRun` |
no test coverage detected