feat(platform-service): Phase 2 scheduler/router core (§7) + wire into atomic claim
Add a pure, fixed-weight scoring engine that decides WHICH queued job a claiming
factory gets, and wire it into coordinator.claimNextJob (the atomic rev-CAS claim
in tryClaimJob is unchanged).
scheduler.ts (pure, synchronous, no I/O):
- scoreCandidate(job, factory, ctx, weights?) -> { score, breakdown }
score = w1*capabilityFit + w2*affinity + w3*(1/(1+load)) + w4*costFit(budget)
+ w5*health - w6*starvationPenalty(age); breakdown is per-weighted-term
and sums to score (explainability / Phase-3 readiness).
- selectJob(candidates, factory, ctx, weights?) -> FleetJobDoc | null
filters to stage-eligible + deps-satisfied (injected pure predicate) +
capability-subset (+ down-health floor), ranks by score, deterministic
tie-break: higher priority -> older createdAt -> lower cost class.
- Fixed default weights + bucketed anti-starvation aging (Phase 3 = tunable
weights + preemption; intentionally NOT built here).
coordinator.ts (candidate-ranking section only):
- claimNextJob now resolves deps (store-backed) into a pure predicate, builds the
factory view + authoritative now, and selects via selectJob; tryClaimJob CAS /
lease / fence logic untouched. ClaimContext gains additive optional scheduler
inputs (health/load/seatLimit/factoryEngines/warmScopes/costCeilingUsd). The
pure capability-subset predicate moved into scheduler.ts and is re-exported.
Tests: scheduler.test.ts (16) covers capability hard-filter, priority/age
tie-breaks, load, health (+ down floor), starvation, cost fit, affinity, breakdown
sum, determinism, empty/no-eligible. coordinator.test.ts adds score-driven
selection, health floor, and ordered drain; all prior fleet tests stay green.
Generated with [Devin](https://cli.devin.ai/docs)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>