claude-multiacc 2.0.20 → 2.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -52,9 +52,10 @@ class SelectorTests(unittest.TestCase):
52
52
  self.assertEqual(
53
53
  response["candidate_snapshot_digest"],
54
54
  "e8ef0b4bc7b95e76231ee6da79616d92bd9251c1c991845c8c0524fcbfb6593a")
55
+ # Recomputed 2026-09-03: the proof input grew by session_gate.
55
56
  self.assertEqual(
56
57
  response["selection_digest"],
57
- "8b68d69b31b2d2252a16f7b83ac46860987647aa360402caa815b2a1bd468def")
58
+ "1239440c286d579f4326b4dd633072f51435709a0459cba4f9d671b8cefeb646")
58
59
 
59
60
  def test_unicode_and_active_reservation_proof(self):
60
61
  busy = {"active_expires_at": "2026-08-25T09:00:00.000001Z", "last_selected_at": None}
@@ -66,9 +67,10 @@ class SelectorTests(unittest.TestCase):
66
67
  self.assertEqual(
67
68
  response["candidate_snapshot_digest"],
68
69
  "29e8e7d1a611f38ee0be43622102b33875e7ff75ff9024aa43d16151575071d4")
70
+ # Recomputed 2026-09-03: the proof input grew by session_gate.
69
71
  self.assertEqual(
70
72
  response["selection_digest"],
71
- "72e7b31a00496fff0f6281f4dacd656d3a2a1fc438038e2c20c49274e1f61068")
73
+ "646f38c209df65af94a4ee25e47579d4794902161f902e94030da23ca49d07ff")
72
74
 
73
75
  def test_both_chooses_each_provider_and_survives_provider_loss(self):
74
76
  codex = select(_request([
@@ -308,5 +310,105 @@ class HeadroomBandTests(unittest.TestCase):
308
310
  self.assertEqual(select(_request(pool))["headroom_band"], "30")
309
311
 
310
312
 
313
+ class SessionGateTests(unittest.TestCase):
314
+ """The session (5h) bucket is the FIRST cut; the weekly band ranks the survivors.
315
+
316
+ Operator, 2026-09-03: "among accounts where high session limits it must choose
317
+ randomly from ones where highest weekly limits." A nearly-spent 5h bucket is
318
+ about to reject the launch whatever the weekly headroom says, so it disqualifies
319
+ the account outright — it is no longer a tie-break, and it no longer shrinks the
320
+ band through min(weekly, session).
321
+ """
322
+
323
+ def _pool(self, *specs) -> list[dict]:
324
+ # spec: (account_id, weekly_pct, session_pct) — distinct runner ids keep the
325
+ # identities unique without touching the account names the assertions read.
326
+ return [_candidate(runner_id=3 + index, account_id=name,
327
+ weekly_pct=weekly, session_pct=session)
328
+ for index, (name, weekly, session) in enumerate(specs)]
329
+
330
+ def _winners(self, pool: list[dict], **patch) -> set[str]:
331
+ return {select(_request(pool, reservation_key=f"launch-{index}", **patch))["account_id"]
332
+ for index in range(40)}
333
+
334
+ def test_spent_session_loses_to_a_worse_weekly_account(self):
335
+ # 10w/85s vs 70w/20s -> ALWAYS the 70w account. This reverses the pre-gate
336
+ # behaviour, where the session-heavy account stayed in the band on its
337
+ # weekly headroom alone.
338
+ pool = self._pool(("session-spent", 10, 85), ("session-free", 70, 20))
339
+ response = select(_request(pool))
340
+ self.assertEqual(response["account_id"], "session-free")
341
+ self.assertEqual((response["session_ok_count"], response["band_count"]), (1, 1))
342
+ self.assertEqual(self._winners(pool), {"session-free"})
343
+
344
+ def test_weekly_decides_when_both_clear_the_gate(self):
345
+ # 10w/45s vs 70w/20s -> always the 10w account: session only acts through the
346
+ # gate, never as a tie-break inside the band.
347
+ pool = self._pool(("best-weekly", 10, 45), ("session-free", 70, 20))
348
+ response = select(_request(pool))
349
+ self.assertEqual(response["account_id"], "best-weekly")
350
+ self.assertEqual((response["session_ok_count"], response["band_count"]), (2, 1))
351
+ self.assertEqual(self._winners(pool), {"best-weekly"})
352
+
353
+ def test_gate_steps_aside_when_nobody_clears_it(self):
354
+ # 10w/85s vs 70w/60s -> the gate compares, it never empties the pool, so the
355
+ # weekly band ranks everyone exactly as it did before the gate existed.
356
+ pool = self._pool(("best-weekly", 10, 85), ("other", 70, 60))
357
+ response = select(_request(pool))
358
+ self.assertEqual(response["account_id"], "best-weekly")
359
+ self.assertEqual(response["session_ok_count"], 0)
360
+ self.assertEqual((response["band_floor"], response["band_count"]), ("60", 1))
361
+
362
+ def test_gate_of_one_hundred_disables_it(self):
363
+ pool = self._pool(("best-weekly", 10, 85), ("session-free", 70, 20))
364
+ response = select(_request(pool, session_gate=100))
365
+ self.assertEqual(response["account_id"], "best-weekly")
366
+ self.assertEqual((response["session_gate"], response["session_ok_count"]), ("100", 2))
367
+ self.assertEqual(self._winners(pool, session_gate=100), {"best-weekly"})
368
+
369
+ def test_unknown_quota_never_clears_the_gate(self):
370
+ # A row with no telemetry has no session reading, so it cannot be evidence of
371
+ # room: it stays out of the gated set and out of the weekly band, and the
372
+ # floor is computed from the known row alone.
373
+ pool = [_candidate(account_id="blind", weekly_pct=None, session_pct=None),
374
+ _candidate(runner_id=4, account_id="heavy", weekly_pct=0, session_pct=90)]
375
+ response = select(_request(pool))
376
+ self.assertEqual(response["account_id"], "heavy")
377
+ self.assertEqual(response["session_ok_count"], 0)
378
+ self.assertEqual((response["band_floor"], response["band_count"]), ("70", 1))
379
+
380
+ def test_gate_is_validated_exactly_like_the_band(self):
381
+ pool = self._pool(("a", 0, 0), ("b", 40, 0))
382
+ narrow = select(_request(pool, session_gate="0"))
383
+ self.assertTrue(narrow["ok"])
384
+ self.assertEqual(narrow["session_gate"], "0")
385
+ for bad in ("abc", True, [], "1e5", None):
386
+ refusal = select(_request(pool, session_gate=bad))
387
+ self.assertFalse(refusal["ok"], bad)
388
+ self.assertEqual(refusal["error_detail"], {"field": "session_gate"}, bad)
389
+
390
+ def test_gate_participates_in_the_selection_proof(self):
391
+ # Same pool, same winner, different gate -> a different proof: a policy input
392
+ # that can change the winner has to be inside the digest.
393
+ pool = self._pool(("a", 0, 40), ("b", 20, 40))
394
+ tight = select(_request(pool, session_gate="10"))
395
+ loose = select(_request(pool, session_gate="90"))
396
+ self.assertNotEqual(tight["selection_digest"], loose["selection_digest"])
397
+ # An omitted gate is the documented default, and says so in the response.
398
+ self.assertEqual(select(_request(pool))["session_gate"], "50")
399
+
400
+ def test_band_measures_weekly_not_the_minimum_of_weekly_and_session(self):
401
+ # 0w/45s vs 40w/0s with band 30: on min(weekly, session) the 0w account's
402
+ # effective headroom (55) is below the other's (60) and both used to band
403
+ # together. On weekly remaining the 0w account IS the leader and the 40w
404
+ # account (60) falls outside the 70-point floor.
405
+ pool = self._pool(("empty-weekly", 0, 45), ("empty-session", 40, 0))
406
+ response = select(_request(pool))
407
+ self.assertEqual(response["account_id"], "empty-weekly")
408
+ self.assertEqual((response["band_floor"], response["band_count"]), ("70", 1))
409
+ self.assertEqual(response["session_ok_count"], 2)
410
+ self.assertEqual(self._winners(pool), {"empty-weekly"})
411
+
412
+
311
413
  if __name__ == "__main__":
312
414
  unittest.main()