alissa-tools-github-revloop 0.31.17__tar.gz → 0.31.18__tar.gz

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.
Files changed (40) hide show
  1. {alissa_tools_github_revloop-0.31.17/src/main/alissa_tools_github_revloop.egg-info → alissa_tools_github_revloop-0.31.18}/PKG-INFO +1 -1
  2. {alissa_tools_github_revloop-0.31.17 → alissa_tools_github_revloop-0.31.18}/src/main/alissa/tools/github/revloop/loop.py +67 -34
  3. {alissa_tools_github_revloop-0.31.17 → alissa_tools_github_revloop-0.31.18}/src/main/alissa/tools/github/revloop/parking.py +3 -1
  4. {alissa_tools_github_revloop-0.31.17 → alissa_tools_github_revloop-0.31.18}/src/main/alissa/tools/github/revloop/state.py +6 -3
  5. alissa_tools_github_revloop-0.31.18/src/main/alissa/tools/github/revloop/version +1 -0
  6. {alissa_tools_github_revloop-0.31.17 → alissa_tools_github_revloop-0.31.18}/src/main/alissa/tools/github/revloop/webui/server.py +3 -1
  7. {alissa_tools_github_revloop-0.31.17 → alissa_tools_github_revloop-0.31.18/src/main/alissa_tools_github_revloop.egg-info}/PKG-INFO +1 -1
  8. alissa_tools_github_revloop-0.31.17/src/main/alissa/tools/github/revloop/version +0 -1
  9. {alissa_tools_github_revloop-0.31.17 → alissa_tools_github_revloop-0.31.18}/LICENSE +0 -0
  10. {alissa_tools_github_revloop-0.31.17 → alissa_tools_github_revloop-0.31.18}/MANIFEST.in +0 -0
  11. {alissa_tools_github_revloop-0.31.17 → alissa_tools_github_revloop-0.31.18}/NOTICE +0 -0
  12. {alissa_tools_github_revloop-0.31.17 → alissa_tools_github_revloop-0.31.18}/README.md +0 -0
  13. {alissa_tools_github_revloop-0.31.17 → alissa_tools_github_revloop-0.31.18}/requirements.txt +0 -0
  14. {alissa_tools_github_revloop-0.31.17 → alissa_tools_github_revloop-0.31.18}/setup.cfg +0 -0
  15. {alissa_tools_github_revloop-0.31.17 → alissa_tools_github_revloop-0.31.18}/setup.py +0 -0
  16. {alissa_tools_github_revloop-0.31.17 → alissa_tools_github_revloop-0.31.18}/src/main/alissa/tools/github/revloop/__init__.py +0 -0
  17. {alissa_tools_github_revloop-0.31.17 → alissa_tools_github_revloop-0.31.18}/src/main/alissa/tools/github/revloop/__main__.py +0 -0
  18. {alissa_tools_github_revloop-0.31.17 → alissa_tools_github_revloop-0.31.18}/src/main/alissa/tools/github/revloop/alissa.py +0 -0
  19. {alissa_tools_github_revloop-0.31.17 → alissa_tools_github_revloop-0.31.18}/src/main/alissa/tools/github/revloop/alissa_client.py +0 -0
  20. {alissa_tools_github_revloop-0.31.17 → alissa_tools_github_revloop-0.31.18}/src/main/alissa/tools/github/revloop/bows.py +0 -0
  21. {alissa_tools_github_revloop-0.31.17 → alissa_tools_github_revloop-0.31.18}/src/main/alissa/tools/github/revloop/config.py +0 -0
  22. {alissa_tools_github_revloop-0.31.17 → alissa_tools_github_revloop-0.31.18}/src/main/alissa/tools/github/revloop/fleet_vitals.py +0 -0
  23. {alissa_tools_github_revloop-0.31.17 → alissa_tools_github_revloop-0.31.18}/src/main/alissa/tools/github/revloop/ghclient.py +0 -0
  24. {alissa_tools_github_revloop-0.31.17 → alissa_tools_github_revloop-0.31.18}/src/main/alissa/tools/github/revloop/janitor.py +0 -0
  25. {alissa_tools_github_revloop-0.31.17 → alissa_tools_github_revloop-0.31.18}/src/main/alissa/tools/github/revloop/loop_events.py +0 -0
  26. {alissa_tools_github_revloop-0.31.17 → alissa_tools_github_revloop-0.31.18}/src/main/alissa/tools/github/revloop/proc.py +0 -0
  27. {alissa_tools_github_revloop-0.31.17 → alissa_tools_github_revloop-0.31.18}/src/main/alissa/tools/github/revloop/prompts.py +0 -0
  28. {alissa_tools_github_revloop-0.31.17 → alissa_tools_github_revloop-0.31.18}/src/main/alissa/tools/github/revloop/prreview.py +0 -0
  29. {alissa_tools_github_revloop-0.31.17 → alissa_tools_github_revloop-0.31.18}/src/main/alissa/tools/github/revloop/trust.py +0 -0
  30. {alissa_tools_github_revloop-0.31.17 → alissa_tools_github_revloop-0.31.18}/src/main/alissa/tools/github/revloop/version.py +0 -0
  31. {alissa_tools_github_revloop-0.31.17 → alissa_tools_github_revloop-0.31.18}/src/main/alissa/tools/github/revloop/webui/__init__.py +0 -0
  32. {alissa_tools_github_revloop-0.31.17 → alissa_tools_github_revloop-0.31.18}/src/main/alissa/tools/github/revloop/webui/__main__.py +0 -0
  33. {alissa_tools_github_revloop-0.31.17 → alissa_tools_github_revloop-0.31.18}/src/main/alissa/tools/github/revloop/webui/auth.py +0 -0
  34. {alissa_tools_github_revloop-0.31.17 → alissa_tools_github_revloop-0.31.18}/src/main/alissa/tools/github/revloop/webui/page.py +0 -0
  35. {alissa_tools_github_revloop-0.31.17 → alissa_tools_github_revloop-0.31.18}/src/main/alissa/tools/github/revloop/webui/sources.py +0 -0
  36. {alissa_tools_github_revloop-0.31.17 → alissa_tools_github_revloop-0.31.18}/src/main/alissa/tools/github/revloop/webui/sysinfo.py +0 -0
  37. {alissa_tools_github_revloop-0.31.17 → alissa_tools_github_revloop-0.31.18}/src/main/alissa_tools_github_revloop.egg-info/SOURCES.txt +0 -0
  38. {alissa_tools_github_revloop-0.31.17 → alissa_tools_github_revloop-0.31.18}/src/main/alissa_tools_github_revloop.egg-info/dependency_links.txt +0 -0
  39. {alissa_tools_github_revloop-0.31.17 → alissa_tools_github_revloop-0.31.18}/src/main/alissa_tools_github_revloop.egg-info/entry_points.txt +0 -0
  40. {alissa_tools_github_revloop-0.31.17 → alissa_tools_github_revloop-0.31.18}/src/main/alissa_tools_github_revloop.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: alissa-tools-github-revloop
3
- Version: 0.31.17
3
+ Version: 0.31.18
4
4
  Summary: ALISSA-TOOLS-GITHUB-REVLOOP
5
5
  Home-page: https://alissa.app
6
6
  Author: Fahera
@@ -6302,20 +6302,49 @@ class ReviewWatcher:
6302
6302
  #157): claim the spawn of this round's session against the drain
6303
6303
  flag, run `spawn` under the claim, and drop the claim however it ends.
6304
6304
 
6305
- The gate sits ABOVE every side effect of the spawn -- the hub add,
6306
- the `alissa tmux queue add`, the census bump, the ledger row, the
6307
- activity line -- so a drained seat touches nothing, and the round
6308
- stays owed: a QUEUED `drained` decision writes no spawn row, burns no
6309
- round number and spends no attempt, exactly like the slot gate. The
6310
- claim and the flag are read and written in one immediate transaction
6311
- on each side (`State.claim_spawn` / `State.set_drain`): a drain that
6305
+ The gate sits ABOVE every side effect of the spawn but one -- the
6306
+ `alissa tmux queue add`, the census bump, the ledger row and the
6307
+ activity line all come after it -- and a round it stands down stays
6308
+ owed: a QUEUED `drained` decision writes no spawn row, burns no round
6309
+ number and spends no attempt, exactly like the slot gate. The claim
6310
+ and the flag are read and written in one immediate transaction on
6311
+ each side (`State.claim_spawn` / `State.set_drain`): a drain that
6312
6312
  lands while this spawn is between its gate and its ledger write sees
6313
- the claim and refuses itself, so a park can never race a spawn. A
6314
- ledger that cannot answer stands the spawn down too -- a spawn that
6315
- cannot prove the seat is not drained does not happen.
6313
+ the claim and refuses itself. A ledger that cannot answer stands the
6314
+ spawn down too -- a spawn that cannot prove the seat is not drained
6315
+ does not happen.
6316
+
6317
+ The claim only counts for `parking.SPAWN_CLAIM_TTL`, and the hub add
6318
+ (`_ensure_hub`, a clone on a PR's first round) can outlast it. So
6319
+ `_spawn_claimed` re-takes the claim (`_claim_spawn` again, issue
6320
+ #173) between the hub add and the enqueue, which refreshes it and
6321
+ re-reads the flag: a drain that held while the hub was provisioned
6322
+ stands the round down there, the same QUEUED `drained` way. That late
6323
+ re-check is the one stand-down that happens AFTER a side effect --
6324
+ the hub is provisioned and stays -- which is harmless, because the
6325
+ hub is a reusable cache the round's next spawn finds in place.
6326
+ Together the two claims mean a park can never race a spawn, whatever
6327
+ the drain's freshness rule allows.
6316
6328
 
6317
6329
  Dry-run READS the flag and claims nothing: it shares the production
6318
6330
  state path, and a dry pass must write no durable row."""
6331
+ refused = self._claim_spawn(pr, round_, name)
6332
+ if refused is not None:
6333
+ return refused
6334
+ try:
6335
+ return spawn()
6336
+ finally:
6337
+ if not self.config.dry_run:
6338
+ self.state.release_spawn(name)
6339
+
6340
+ def _claim_spawn(
6341
+ self, pr: PullRequest, round_: int, name: str, *, late: bool = False,
6342
+ ) -> "Decision | None":
6343
+ """Claim (or, `late`, re-claim) the spawn of `name` against the drain
6344
+ flag: None when the spawn may proceed, else the QUEUED `drained`
6345
+ decision that stands the round down. Dry-run reads the flag and
6346
+ claims nothing. See `_drain_gated`."""
6347
+ where = " after its hub was provisioned" if late else ""
6319
6348
  try:
6320
6349
  if self.config.dry_run:
6321
6350
  drain = self.state.drain()
@@ -6323,36 +6352,32 @@ class ReviewWatcher:
6323
6352
  drain = self.state.claim_spawn(name)
6324
6353
  except sqlite3.Error as exc:
6325
6354
  log.warning(
6326
- "%s round %d: could not claim the spawn against the drain flag "
6327
- "(%s) — standing it down this pass", pr.slug, round_, exc,
6328
- )
6329
- return Decision(
6330
- Action.QUEUED,
6331
- f"round {round_} stood down — the state ledger could not prove "
6332
- f"the seat is not drained ({exc}); retrying next poll",
6333
- round_,
6334
- drained=True,
6335
- )
6336
- if drain is not None:
6337
- since = parking_mod.iso(drain["drained_at"])
6338
- until = parking_mod.iso(drain["expires_at"])
6339
- log.info(
6340
- "%s round %d: not spawning — the seat is drained for parking "
6341
- "since %s (until %s)", pr.slug, round_, since, until,
6355
+ "%s round %d: could not claim the spawn against the drain flag%s "
6356
+ "(%s) — standing it down this pass", pr.slug, round_, where, exc,
6342
6357
  )
6343
6358
  return Decision(
6344
6359
  Action.QUEUED,
6345
- f"round {round_} held — the seat is drained for parking since "
6346
- f"{since} (until {until}: {drain['reason'] or 'no reason given'}); "
6347
- f"retrying once it is undrained",
6360
+ f"round {round_} stood down{where} — the state ledger could not "
6361
+ f"prove the seat is not drained ({exc}); retrying next poll",
6348
6362
  round_,
6349
6363
  drained=True,
6350
6364
  )
6351
- try:
6352
- return spawn()
6353
- finally:
6354
- if not self.config.dry_run:
6355
- self.state.release_spawn(name)
6365
+ if drain is None:
6366
+ return None
6367
+ since = parking_mod.iso(drain["drained_at"])
6368
+ until = parking_mod.iso(drain["expires_at"])
6369
+ log.info(
6370
+ "%s round %d: not spawning%s — the seat is drained for parking "
6371
+ "since %s (until %s)", pr.slug, round_, where, since, until,
6372
+ )
6373
+ return Decision(
6374
+ Action.QUEUED,
6375
+ f"round {round_} held{where} — the seat is drained for parking since "
6376
+ f"{since} (until {until}: {drain['reason'] or 'no reason given'}); "
6377
+ f"retrying once it is undrained",
6378
+ round_,
6379
+ drained=True,
6380
+ )
6356
6381
 
6357
6382
  def _spawn_claimed(
6358
6383
  self,
@@ -6427,6 +6452,14 @@ class ReviewWatcher:
6427
6452
  # provisioned -- unknown, never settled.
6428
6453
  return Decision(Action.SKIPPED, problem, round_, owed=True)
6429
6454
 
6455
+ # The hub add can outlast the gate's claim (a clone on a PR's first
6456
+ # round): re-take it, refreshing it and re-reading the drain flag, so
6457
+ # a drain that landed meanwhile stands the round down here (issue
6458
+ # #173). The claim is still released in `_drain_gated`'s finally.
6459
+ refused = self._claim_spawn(pr, round_, name, late=True)
6460
+ if refused is not None:
6461
+ return refused
6462
+
6430
6463
  self.alissa.enqueue_reviewer(
6431
6464
  session=name,
6432
6465
  directive=directive,
@@ -26,7 +26,9 @@ handshake):
26
26
  reads every null as a refusal.
27
27
  * **The drain.** A flag with a TTL in `state.db` (`State.set_drain`), cleared
28
28
  when the daemon boots. While it holds, the round spawn stands down before
29
- its first side effect (`ReviewWatcher._drain_gated`).
29
+ its first side effect (`ReviewWatcher._drain_gated`), or, when the drain
30
+ lands while the hub is provisioned, after the hub add and before the
31
+ enqueue (the post-hub re-claim).
30
32
  """
31
33
 
32
34
  from __future__ import annotations
@@ -39,6 +39,8 @@ from collections.abc import Callable, Iterable
39
39
  from dataclasses import dataclass
40
40
  from pathlib import Path
41
41
 
42
+ from .parking import SPAWN_CLAIM_TTL
43
+
42
44
  log = logging.getLogger(__name__)
43
45
 
44
46
  # Poll-snapshot retention: the newest N rows are kept, older ones pruned on
@@ -2067,11 +2069,12 @@ class State:
2067
2069
 
2068
2070
  def set_drain(
2069
2071
  self, ttl: int, reason: str, now: "float | None" = None,
2070
- claim_ttl: int = 600,
2072
+ claim_ttl: int = SPAWN_CLAIM_TTL,
2071
2073
  ) -> "tuple[dict, list[dict]]":
2072
2074
  """Raise the drain flag for `ttl` seconds and return it with the spawn
2073
- claims standing at that instant (younger than `claim_ttl` -- an older
2074
- one is a spawn whose process died mid-way, cleared at the next boot).
2075
+ claims standing at that instant (younger than `claim_ttl`, by default
2076
+ `parking.SPAWN_CLAIM_TTL` -- an older one is a spawn whose process
2077
+ died mid-way, cleared at the next boot).
2075
2078
  ONE immediate transaction, so a spawn either claimed before this (and
2076
2079
  is in the list) or claims after it (and sees the flag). A drain that
2077
2080
  is already up is replaced: a re-drain restarts the TTL. Raises
@@ -332,7 +332,9 @@ class App:
332
332
  cleared = self.sources.clear_drain()
333
333
  except sqlite3.Error as exc:
334
334
  self._audit("undrain", {"ok": False, "error": str(exc)})
335
- return 503, {"drained": True, "error": "state_unavailable"}
335
+ # The flag's state is unknown here, and unknown is never a value
336
+ # (D2): the 503 already says the undrain did not land.
337
+ return 503, {"drained": None, "error": "state_unavailable"}
336
338
  self._audit("undrain", {"ok": True, "cleared": cleared})
337
339
  return 200, {"drained": False, "cleared": cleared}
338
340
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: alissa-tools-github-revloop
3
- Version: 0.31.17
3
+ Version: 0.31.18
4
4
  Summary: ALISSA-TOOLS-GITHUB-REVLOOP
5
5
  Home-page: https://alissa.app
6
6
  Author: Fahera