alissa-tools-github-devloop 0.8.45__py3-none-any.whl → 0.8.46__py3-none-any.whl

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.
@@ -25,7 +25,7 @@ serves it on ``GET /api/state`` and runs the drain handshake):
25
25
  null as a refusal.
26
26
  * **The drain.** A flag with a TTL in `state.db` (`State.set_drain`), cleared
27
27
  when the daemon boots. While it holds, every spawn path stands down before
28
- its first side effect (`DevWatcher._drain_gate`).
28
+ its first side effect (`DevWatcher._drain_gated`).
29
29
  """
30
30
 
31
31
  from __future__ import annotations
@@ -1 +1 @@
1
- 0.8.45
1
+ 0.8.46
@@ -234,7 +234,16 @@ class App:
234
234
  down and the answer is `drained: false` with the reasons. When the
235
235
  drain holds, a vitals snapshot carrying `idle.drainedAt` is pushed
236
236
  BEFORE the answer, because that snapshot is what Studio re-checks
237
- before it removes anything; the answer says how the push went."""
237
+ before it removes anything; the answer says how the push went.
238
+
239
+ A refused drain lowers the flag, not just its own raise: the clear is
240
+ unscoped (`State.clear_drain`). A refused RE-drain therefore also
241
+ lowers an earlier drain that held and had answered `drained: true`,
242
+ and so does a second, overlapping `/action/drain` that is refused.
243
+ That is intended (seat-parking §2.4: when the block breaks D2 "the
244
+ seat clears the flag itself and answers `drained: false`"): a seat
245
+ that is no longer idle must not stay drained, whoever raised the
246
+ flag."""
238
247
  if ttl_s is None:
239
248
  ttl = parking.DRAIN_TTL_DEFAULT
240
249
  elif (
@@ -265,7 +274,7 @@ class App:
265
274
  except sqlite3.Error as exc:
266
275
  self._audit("drain", {"ok": False, "error": str(exc)})
267
276
  return 503, {"drained": False, "error": "state_unavailable"}
268
- listing = self.sources._safe_json(["alissa", "tmux", "ls", "--json"])
277
+ listing = self.sources.listing()
269
278
  idle = self.sources.idle(listing=listing)
270
279
  refusals = parking.idle_refusals(
271
280
  idle, poll_interval=self.sources.config.poll_interval, now=now,
@@ -305,13 +314,18 @@ class App:
305
314
  def undrain(self) -> "tuple[int, dict]":
306
315
  """`POST /action/undrain`: lower the drain flag -- orcloop's step 4
307
316
  on any failure of a park. Idempotent: undraining a seat that is not
308
- drained answers `cleared: false`. (status, payload), audited."""
317
+ drained answers `cleared: false`. (status, payload), audited.
318
+
319
+ A ledger that cannot be written answers 503 with `drained: null`:
320
+ nobody knows then whether the flag is up (there may be no drain at
321
+ all), and unknown is never a value (design D2). The 503 already says
322
+ the undrain did not land; the TTL still bounds any flag left up."""
309
323
  try:
310
324
  with State(self.sources.config.state_db) as st:
311
325
  cleared = st.clear_drain()
312
326
  except sqlite3.Error as exc:
313
327
  self._audit("undrain", {"ok": False, "error": str(exc)})
314
- return 503, {"drained": True, "error": "state_unavailable"}
328
+ return 503, {"drained": None, "error": "state_unavailable"}
315
329
  self._audit("undrain", {"ok": True, "cleared": cleared})
316
330
  return 200, {"drained": False, "cleared": cleared}
317
331
 
@@ -384,6 +384,15 @@ class Sources:
384
384
  except ValueError:
385
385
  return None
386
386
 
387
+ def listing(self) -> "object":
388
+ """The parsed ``alissa tmux ls --json`` listing, or None when it could
389
+ not be read (the command failed, printed nothing, or printed something
390
+ that is not JSON). The console's one reader of the tmux roster: the
391
+ sessions panel, the idle block, the dashboard and the drain handshake
392
+ all read it here. The value is whatever the CLI printed -- callers
393
+ check it is a list before trusting its shape."""
394
+ return self._safe_json(["alissa", "tmux", "ls", "--json"])
395
+
387
396
  def sessions(self, listing: "list[dict] | None" = None) -> "list[dict]":
388
397
  """The managed sessions with their edge, age and `/proc` footprint.
389
398
 
@@ -393,9 +402,7 @@ class Sources:
393
402
  console (no listing) lists for itself. Either way the rows go
394
403
  through the same shaping, so the two never disagree about what a
395
404
  session record looks like."""
396
- raw = listing if listing is not None else (
397
- self._safe_json(["alissa", "tmux", "ls", "--json"]) or []
398
- )
405
+ raw = listing if listing is not None else (self.listing() or [])
399
406
  if not isinstance(raw, list):
400
407
  return []
401
408
  now = int(self._wall())
@@ -463,7 +470,7 @@ class Sources:
463
470
  snaps = self.snapshots(1)
464
471
  latest = snaps[0] if snaps else None
465
472
  if listing is _UNREAD:
466
- listing = self._safe_json(["alissa", "tmux", "ls", "--json"])
473
+ listing = self.listing()
467
474
  roster = listing if isinstance(listing, list) else None
468
475
  snapshot = latest if isinstance(latest, dict) else None
469
476
  queues = self._safe_json(["alissa", "tmux", "queue", "ls", "--json"])
@@ -678,7 +685,7 @@ class Sources:
678
685
  latest = snaps[0] if snaps else None
679
686
  ledgers = self.ledgers()
680
687
  # One listing feeds both the sessions panel and the idle block.
681
- listing = self._safe_json(["alissa", "tmux", "ls", "--json"])
688
+ listing = self.listing()
682
689
  sessions = self.sessions(
683
690
  listing=listing if isinstance(listing, list) else []
684
691
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: alissa-tools-github-devloop
3
- Version: 0.8.45
3
+ Version: 0.8.46
4
4
  Summary: ALISSA-TOOLS-GITHUB-DEVLOOP
5
5
  Home-page: https://alissa.app
6
6
  Author: Fahera
@@ -10,23 +10,23 @@ alissa/tools/github/devloop/hubs.py,sha256=yjnsnSHLG7GNNP8X2hILeAMV6P5n4JGc_IaAx
10
10
  alissa/tools/github/devloop/janitor.py,sha256=R8REoHBeY2cfW7fYJVhKr-F5z9Mjlc-vmv-z4KtvBZc,27360
11
11
  alissa/tools/github/devloop/loop.py,sha256=I5rZy082JqplmFm1t2XgYkstc7IdkJJdX-FXAfwqcKk,624114
12
12
  alissa/tools/github/devloop/loop_events.py,sha256=pKtBl1HK8gYPB2gKiI29xUkx-JuW2A48KwRlbdcf_lo,37942
13
- alissa/tools/github/devloop/parking.py,sha256=vEf-qprN14-I4vu4M53tIFaA3827FiHLpKetBAgN4LQ,12409
13
+ alissa/tools/github/devloop/parking.py,sha256=7h3Gg2XQ0eqlvn0M6K6NeEg9xbqnlUPrZNs9gRofzxc,12410
14
14
  alissa/tools/github/devloop/proc.py,sha256=nzfsxJSHFWRQcrXmfv588KazFysv8dVQrhMPH1q5eRk,3134
15
15
  alissa/tools/github/devloop/prompts.py,sha256=VzN6ec5eF2wwg7QSCRg52WLcxo-tGbGRgQbc4HSahUc,55169
16
16
  alissa/tools/github/devloop/state.py,sha256=uVwhbkogUTjWXXFq-COKzUeYrpuGbHzi05CjfEzAtPA,88668
17
- alissa/tools/github/devloop/version,sha256=SvuxweSl5ewBHlA8sLXW9Cnb1QXZFR16TSaeQVx76Nw,7
17
+ alissa/tools/github/devloop/version,sha256=YpzpT7756Lld10FKXeESQnDWJA9HvJScK9ngOSfSk8s,7
18
18
  alissa/tools/github/devloop/version.py,sha256=znuuOaitXGrBpbskNy78_rlVpFdtmKmk_3k1ACxZrl0,1418
19
19
  alissa/tools/github/devloop/webui/__init__.py,sha256=vdXYoyGYzD7BrJ5lV6XdH8Bpw_pej8-RK0hjfxop7-8,1513
20
20
  alissa/tools/github/devloop/webui/__main__.py,sha256=wORyW5AkgsniK0TZcrOrvm_P7Q4Mr-fH-4S6qj9XcgA,6137
21
21
  alissa/tools/github/devloop/webui/auth.py,sha256=g9YEDao9nKt-cBEENp7vFqwJ-T8olN6uLiCzVIN8yhA,7890
22
22
  alissa/tools/github/devloop/webui/page.py,sha256=4OD3e01ByvVM4Wo04AZyFuyOYTFEMT2lagktZAvyCKk,30862
23
- alissa/tools/github/devloop/webui/server.py,sha256=YuUmcng5qdWqSurcuyxH3FKKkBBfrpDtvtZLidJOGiU,26226
24
- alissa/tools/github/devloop/webui/sources.py,sha256=JJXJEkLKh2qRbpTaIyQwCTu90DuKq_Z3nz6DwLhzlr4,38900
23
+ alissa/tools/github/devloop/webui/server.py,sha256=3aGYGesjUSrPrgcpfy-oYDefBNKnAj3lGfRzwsRkv_I,27031
24
+ alissa/tools/github/devloop/webui/sources.py,sha256=Uw_2B5QGSmW6f66WfVbQb6yLLYmSv6uuF7HNJ5OSr_I,39314
25
25
  alissa/tools/github/devloop/webui/sysinfo.py,sha256=ptlogH9alFv0vZ-pQfyUaA3w-K7LQS3tQHGv0d8Tdlk,9649
26
- alissa_tools_github_devloop-0.8.45.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
27
- alissa_tools_github_devloop-0.8.45.dist-info/licenses/NOTICE,sha256=FKstNs3uw_tQod4_1XcFLD1FUTJQXf4FC0oXJAOA_HE,260
28
- alissa_tools_github_devloop-0.8.45.dist-info/METADATA,sha256=UUG6d1BW6ztPi_whOMe8pL-eLbLK7LpT8BEFUECIp0s,13628
29
- alissa_tools_github_devloop-0.8.45.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
30
- alissa_tools_github_devloop-0.8.45.dist-info/entry_points.txt,sha256=8aC7Ajzx4bu9ZRR37a40IDfuStacvZP5KsAxY1dbFKU,145
31
- alissa_tools_github_devloop-0.8.45.dist-info/top_level.txt,sha256=DodjDg-l-TWQnlxG-Vuc3G5sB4O7cWGQwe6XrVx3u-A,7
32
- alissa_tools_github_devloop-0.8.45.dist-info/RECORD,,
26
+ alissa_tools_github_devloop-0.8.46.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
27
+ alissa_tools_github_devloop-0.8.46.dist-info/licenses/NOTICE,sha256=FKstNs3uw_tQod4_1XcFLD1FUTJQXf4FC0oXJAOA_HE,260
28
+ alissa_tools_github_devloop-0.8.46.dist-info/METADATA,sha256=0V0tseoJcEXAfIvuBYJmDowXYvWG0FaMgnUNbASbxVE,13628
29
+ alissa_tools_github_devloop-0.8.46.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
30
+ alissa_tools_github_devloop-0.8.46.dist-info/entry_points.txt,sha256=8aC7Ajzx4bu9ZRR37a40IDfuStacvZP5KsAxY1dbFKU,145
31
+ alissa_tools_github_devloop-0.8.46.dist-info/top_level.txt,sha256=DodjDg-l-TWQnlxG-Vuc3G5sB4O7cWGQwe6XrVx3u-A,7
32
+ alissa_tools_github_devloop-0.8.46.dist-info/RECORD,,