Fix autoscaler pruning active instances on token rotation
What does this MR do?
Related to #39380 (closed)
The docker-autoscaler provider keyed its taskscalers by the runner authentication token. When the runner rotates the token on the server-configured expiry (Admin > CI/CD > Runners > Runners expiration time), the rotated token missed the existing scaler in provider.scalers, so a second scaler was created for the same instance group.
That fresh scaler had no record of the running instances, discovered them as cause=preexisted, and pruned them mid-job (no data on pre-existing instance so removing for safety / instance exceeded max idle time). Jobs then failed with SSH tunnel EOF and system failures.
How
Key the scalers map by a stable identifier that survives token rotation. A token reset only mutates the token and its timestamps — the GitLab-assigned runner ID is preserved — so the key is now URL|ID. When the ID is unset (0) we retain the previous token-based key.
Deployment / upgrade impact
None special. provider.scalers is in-memory and rebuilt from empty on every manager start, so the key change has no cross-restart effect — it only matters within a running process when a live token rotation occurs. The on-disk taskscaler state path (.taskscaler/<shortToken>) is intentionally left unchanged, so state recovery across the upgrade is identical to today.
Tests
TestInit/TestInitReusesScalerOnTokenRotation— unit coverage: the scaler lookup is keyed by URL+ID, and rotating the token in place reuses the existing scaler rather than creating a second one.TestProviderTokenRotationDoesNotPruneInstance— black-box test: drives the real taskscaler through the provider against fleeting's in-memorydummyInstanceGroup (no cloud). A runner provisions an instance, its token is rotated in place, and the test asserts the instance is never pruned (noDecrease). On the old token-keyed code this fails exactly as reported — a second taskscaler is created and the instance is pruned as pre-existing.
Out of scope (follow-up)
The on-disk state-storage path is still token-derived, so a rotation-then-process-restart could still read an empty state dir. The live-rotation bug reported here does not hit that path (the running scaler keeps its storage handle). Re-keying the on-disk path would orphan existing state once on upgrade, so it's left for a separate change.
Related to #39380 (closed)