alissa-tools-github-devloop 0.8.42__tar.gz → 0.8.43__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.
- {alissa_tools_github_devloop-0.8.42/src/main/alissa_tools_github_devloop.egg-info → alissa_tools_github_devloop-0.8.43}/PKG-INFO +27 -1
- {alissa_tools_github_devloop-0.8.42 → alissa_tools_github_devloop-0.8.43}/README.md +26 -0
- {alissa_tools_github_devloop-0.8.42 → alissa_tools_github_devloop-0.8.43}/src/main/alissa/tools/github/devloop/fleet_vitals.py +13 -3
- {alissa_tools_github_devloop-0.8.42 → alissa_tools_github_devloop-0.8.43}/src/main/alissa/tools/github/devloop/parking.py +112 -15
- alissa_tools_github_devloop-0.8.43/src/main/alissa/tools/github/devloop/version +1 -0
- {alissa_tools_github_devloop-0.8.42 → alissa_tools_github_devloop-0.8.43}/src/main/alissa/tools/github/devloop/webui/sources.py +12 -4
- {alissa_tools_github_devloop-0.8.42 → alissa_tools_github_devloop-0.8.43/src/main/alissa_tools_github_devloop.egg-info}/PKG-INFO +27 -1
- alissa_tools_github_devloop-0.8.42/src/main/alissa/tools/github/devloop/version +0 -1
- {alissa_tools_github_devloop-0.8.42 → alissa_tools_github_devloop-0.8.43}/LICENSE +0 -0
- {alissa_tools_github_devloop-0.8.42 → alissa_tools_github_devloop-0.8.43}/MANIFEST.in +0 -0
- {alissa_tools_github_devloop-0.8.42 → alissa_tools_github_devloop-0.8.43}/NOTICE +0 -0
- {alissa_tools_github_devloop-0.8.42 → alissa_tools_github_devloop-0.8.43}/requirements.txt +0 -0
- {alissa_tools_github_devloop-0.8.42 → alissa_tools_github_devloop-0.8.43}/setup.cfg +0 -0
- {alissa_tools_github_devloop-0.8.42 → alissa_tools_github_devloop-0.8.43}/setup.py +0 -0
- {alissa_tools_github_devloop-0.8.42 → alissa_tools_github_devloop-0.8.43}/src/main/alissa/tools/github/devloop/__init__.py +0 -0
- {alissa_tools_github_devloop-0.8.42 → alissa_tools_github_devloop-0.8.43}/src/main/alissa/tools/github/devloop/__main__.py +0 -0
- {alissa_tools_github_devloop-0.8.42 → alissa_tools_github_devloop-0.8.43}/src/main/alissa/tools/github/devloop/alissa.py +0 -0
- {alissa_tools_github_devloop-0.8.42 → alissa_tools_github_devloop-0.8.43}/src/main/alissa/tools/github/devloop/alissa_client.py +0 -0
- {alissa_tools_github_devloop-0.8.42 → alissa_tools_github_devloop-0.8.43}/src/main/alissa/tools/github/devloop/bows.py +0 -0
- {alissa_tools_github_devloop-0.8.42 → alissa_tools_github_devloop-0.8.43}/src/main/alissa/tools/github/devloop/config.py +0 -0
- {alissa_tools_github_devloop-0.8.42 → alissa_tools_github_devloop-0.8.43}/src/main/alissa/tools/github/devloop/ghclient.py +0 -0
- {alissa_tools_github_devloop-0.8.42 → alissa_tools_github_devloop-0.8.43}/src/main/alissa/tools/github/devloop/hubs.py +0 -0
- {alissa_tools_github_devloop-0.8.42 → alissa_tools_github_devloop-0.8.43}/src/main/alissa/tools/github/devloop/janitor.py +0 -0
- {alissa_tools_github_devloop-0.8.42 → alissa_tools_github_devloop-0.8.43}/src/main/alissa/tools/github/devloop/loop.py +0 -0
- {alissa_tools_github_devloop-0.8.42 → alissa_tools_github_devloop-0.8.43}/src/main/alissa/tools/github/devloop/loop_events.py +0 -0
- {alissa_tools_github_devloop-0.8.42 → alissa_tools_github_devloop-0.8.43}/src/main/alissa/tools/github/devloop/proc.py +0 -0
- {alissa_tools_github_devloop-0.8.42 → alissa_tools_github_devloop-0.8.43}/src/main/alissa/tools/github/devloop/prompts.py +0 -0
- {alissa_tools_github_devloop-0.8.42 → alissa_tools_github_devloop-0.8.43}/src/main/alissa/tools/github/devloop/state.py +0 -0
- {alissa_tools_github_devloop-0.8.42 → alissa_tools_github_devloop-0.8.43}/src/main/alissa/tools/github/devloop/version.py +0 -0
- {alissa_tools_github_devloop-0.8.42 → alissa_tools_github_devloop-0.8.43}/src/main/alissa/tools/github/devloop/webui/__init__.py +0 -0
- {alissa_tools_github_devloop-0.8.42 → alissa_tools_github_devloop-0.8.43}/src/main/alissa/tools/github/devloop/webui/__main__.py +0 -0
- {alissa_tools_github_devloop-0.8.42 → alissa_tools_github_devloop-0.8.43}/src/main/alissa/tools/github/devloop/webui/auth.py +0 -0
- {alissa_tools_github_devloop-0.8.42 → alissa_tools_github_devloop-0.8.43}/src/main/alissa/tools/github/devloop/webui/page.py +0 -0
- {alissa_tools_github_devloop-0.8.42 → alissa_tools_github_devloop-0.8.43}/src/main/alissa/tools/github/devloop/webui/server.py +0 -0
- {alissa_tools_github_devloop-0.8.42 → alissa_tools_github_devloop-0.8.43}/src/main/alissa/tools/github/devloop/webui/sysinfo.py +0 -0
- {alissa_tools_github_devloop-0.8.42 → alissa_tools_github_devloop-0.8.43}/src/main/alissa_tools_github_devloop.egg-info/SOURCES.txt +0 -0
- {alissa_tools_github_devloop-0.8.42 → alissa_tools_github_devloop-0.8.43}/src/main/alissa_tools_github_devloop.egg-info/dependency_links.txt +0 -0
- {alissa_tools_github_devloop-0.8.42 → alissa_tools_github_devloop-0.8.43}/src/main/alissa_tools_github_devloop.egg-info/entry_points.txt +0 -0
- {alissa_tools_github_devloop-0.8.42 → alissa_tools_github_devloop-0.8.43}/src/main/alissa_tools_github_devloop.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: alissa-tools-github-devloop
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.43
|
|
4
4
|
Summary: ALISSA-TOOLS-GITHUB-DEVLOOP
|
|
5
5
|
Home-page: https://alissa.app
|
|
6
6
|
Author: Fahera
|
|
@@ -194,6 +194,32 @@ resolved from the remote name or URL in the directory the push runs in
|
|
|
194
194
|
no marker are untouched. `ALISSA_SHELL_GUARD=off` disables this guard with
|
|
195
195
|
the rest.
|
|
196
196
|
|
|
197
|
+
## Seat parking: what counts as managed (issue #166)
|
|
198
|
+
|
|
199
|
+
The idle block on `GET /api/state` (`parking.idle_block`, issue #153) is what
|
|
200
|
+
orcloop's D2 invariant reads before it drains and parks the seat: idle only
|
|
201
|
+
when `live == managed == queued == owed == 0` with no timer and a fresh pass.
|
|
202
|
+
`live` and `managed` come from `alissa tmux ls --json`:
|
|
203
|
+
|
|
204
|
+
- a row is **live** unless it says otherwise (its `live` flag, else
|
|
205
|
+
`status != "gone"`);
|
|
206
|
+
- a row is **managed** when it is live, **or** when it is dead but its
|
|
207
|
+
session still backs a lane the seat holds — the session named by a stage
|
|
208
|
+
of the latest poll snapshot that is owed or in flight (`spawned` /
|
|
209
|
+
`in-flight`), any session the spawn ledgers record on such a stage's lane
|
|
210
|
+
(an earlier attempt the lane may still resume), or a session the alissa
|
|
211
|
+
queue holds an undispatched item for;
|
|
212
|
+
- a dead row backing none of these is an **orphan**: it is not managed, and
|
|
213
|
+
is reported in the block's `gone` field as `{count, sessions}` so the
|
|
214
|
+
console and orcloop's logs still show it.
|
|
215
|
+
|
|
216
|
+
Unknown is never idle: a failed listing leaves `live`, `managed` and `gone`
|
|
217
|
+
null, and before the first completed pass (no snapshot to read lanes from)
|
|
218
|
+
every dead row stays managed. The console only reports an orphan — it never
|
|
219
|
+
kills or reaps one. The fleet-vitals push applies the same rule to
|
|
220
|
+
`sessions.managed` and `idle.managed` (Studio's park guard reads both) and
|
|
221
|
+
leaves `gone` out, since Studio's idle schema is strict.
|
|
222
|
+
|
|
197
223
|
## License
|
|
198
224
|
|
|
199
225
|
Licensed under the
|
|
@@ -172,6 +172,32 @@ resolved from the remote name or URL in the directory the push runs in
|
|
|
172
172
|
no marker are untouched. `ALISSA_SHELL_GUARD=off` disables this guard with
|
|
173
173
|
the rest.
|
|
174
174
|
|
|
175
|
+
## Seat parking: what counts as managed (issue #166)
|
|
176
|
+
|
|
177
|
+
The idle block on `GET /api/state` (`parking.idle_block`, issue #153) is what
|
|
178
|
+
orcloop's D2 invariant reads before it drains and parks the seat: idle only
|
|
179
|
+
when `live == managed == queued == owed == 0` with no timer and a fresh pass.
|
|
180
|
+
`live` and `managed` come from `alissa tmux ls --json`:
|
|
181
|
+
|
|
182
|
+
- a row is **live** unless it says otherwise (its `live` flag, else
|
|
183
|
+
`status != "gone"`);
|
|
184
|
+
- a row is **managed** when it is live, **or** when it is dead but its
|
|
185
|
+
session still backs a lane the seat holds — the session named by a stage
|
|
186
|
+
of the latest poll snapshot that is owed or in flight (`spawned` /
|
|
187
|
+
`in-flight`), any session the spawn ledgers record on such a stage's lane
|
|
188
|
+
(an earlier attempt the lane may still resume), or a session the alissa
|
|
189
|
+
queue holds an undispatched item for;
|
|
190
|
+
- a dead row backing none of these is an **orphan**: it is not managed, and
|
|
191
|
+
is reported in the block's `gone` field as `{count, sessions}` so the
|
|
192
|
+
console and orcloop's logs still show it.
|
|
193
|
+
|
|
194
|
+
Unknown is never idle: a failed listing leaves `live`, `managed` and `gone`
|
|
195
|
+
null, and before the first completed pass (no snapshot to read lanes from)
|
|
196
|
+
every dead row stays managed. The console only reports an orphan — it never
|
|
197
|
+
kills or reaps one. The fleet-vitals push applies the same rule to
|
|
198
|
+
`sessions.managed` and `idle.managed` (Studio's park guard reads both) and
|
|
199
|
+
leaves `gone` out, since Studio's idle schema is strict.
|
|
200
|
+
|
|
175
201
|
## License
|
|
176
202
|
|
|
177
203
|
Licensed under the
|
|
@@ -257,7 +257,8 @@ def build_snapshot(
|
|
|
257
257
|
`queued` (the undispatched alissa queue items, None when unread) and
|
|
258
258
|
`drain` (the holding drain row, or None) feed the `idle` block, whose
|
|
259
259
|
owed / timers come from the latest poll snapshot and whose live /
|
|
260
|
-
managed come from `roster` -- the same reading as `sessions
|
|
260
|
+
managed come from `roster` -- the same reading as `sessions`, with a dead
|
|
261
|
+
row backing no held lane left out of `managed` (issue #166).
|
|
261
262
|
|
|
262
263
|
`config` is the seat's `config_block`, or None to leave the key out
|
|
263
264
|
(the console's push, which has no derived answer to give)."""
|
|
@@ -275,13 +276,20 @@ def build_snapshot(
|
|
|
275
276
|
if session and session not in repo_by_session:
|
|
276
277
|
repo_by_session[str(session)] = str(row["repo_slug"])
|
|
277
278
|
|
|
279
|
+
# A dead row counts as managed only while it backs a lane the seat still
|
|
280
|
+
# holds (issue #166): Studio's park guard reads `sessions.managed` as well
|
|
281
|
+
# as the idle block's, so both follow the console's rule.
|
|
282
|
+
roster_live, roster_managed, _gone = parking.roster_counts(
|
|
283
|
+
roster, parking.lane_sessions(latest, ledgers=ledgers),
|
|
284
|
+
)
|
|
285
|
+
|
|
278
286
|
sessions_block: "dict | None" = None
|
|
279
287
|
session_list: "list[dict] | None" = None
|
|
280
288
|
if roster is not None:
|
|
281
289
|
records = sources.sessions(listing=[dict(r) for r in roster])
|
|
282
290
|
sessions_block = {
|
|
283
291
|
"live": sum(1 for r in records if r.get("live")),
|
|
284
|
-
"managed":
|
|
292
|
+
"managed": roster_managed,
|
|
285
293
|
}
|
|
286
294
|
session_list = [
|
|
287
295
|
_session_row(r, repo_by_session=repo_by_session, parse_session=parse)
|
|
@@ -307,11 +315,13 @@ def build_snapshot(
|
|
|
307
315
|
+ int(latest.get("revert_candidates") or 0)
|
|
308
316
|
)
|
|
309
317
|
|
|
310
|
-
roster_live, roster_managed = parking.roster_counts(roster)
|
|
311
318
|
idle = parking.idle_block(
|
|
312
319
|
latest, live=roster_live, managed=roster_managed, queued=queued,
|
|
313
320
|
drain=drain, now=now_ts,
|
|
314
321
|
)
|
|
322
|
+
# `gone` is the console's observation; Studio's idle schema is strict and
|
|
323
|
+
# does not carry it.
|
|
324
|
+
idle.pop("gone", None)
|
|
315
325
|
|
|
316
326
|
drift = sources.drift()
|
|
317
327
|
snapshot = {
|
|
@@ -8,8 +8,8 @@ the devloop half of that contract, shared by the daemon (which computes the
|
|
|
8
8
|
verdict per pass and pushes it with the fleet vitals) and the console (which
|
|
9
9
|
serves it on ``GET /api/state`` and runs the drain handshake):
|
|
10
10
|
|
|
11
|
-
* **The idle block.** ``{asOf, passAt, owed, live, managed, queued,
|
|
12
|
-
drainedAt}``. `owed` counts the candidates whose LAST evaluation was an
|
|
11
|
+
* **The idle block.** ``{asOf, passAt, owed, live, managed, queued, gone,
|
|
12
|
+
timers, drainedAt}``. `owed` counts the candidates whose LAST evaluation was an
|
|
13
13
|
action or a timer the seat owns (a spawn, a fresh spawn row in its stale
|
|
14
14
|
window, an owed respawn, a deferral, a pending stall ping, a grace the seat
|
|
15
15
|
is waiting out, an evaluation that failed); a candidate the seat has
|
|
@@ -17,7 +17,9 @@ serves it on ``GET /api/state`` and runs the drain handshake):
|
|
|
17
17
|
owed. Each pass records the verdict per candidate on its poll snapshot's
|
|
18
18
|
stage (`owed`, `timer`), so the block is read back from the snapshot and
|
|
19
19
|
costs no GitHub call. `live` / `managed` / `queued` are read from tmux and
|
|
20
|
-
the alissa queue by whoever builds the block.
|
|
20
|
+
the alissa queue by whoever builds the block. `managed` is the live rows
|
|
21
|
+
plus the dead rows whose session backs a lane the seat still holds; a dead
|
|
22
|
+
row backing nothing is reported under `gone` instead (`roster_counts`).
|
|
21
23
|
* **Unknown is never idle.** No snapshot yet → `owed` is null; a listing that
|
|
22
24
|
failed → `live` / `managed` / `queued` are null. `idle_refusals` reads every
|
|
23
25
|
null as a refusal.
|
|
@@ -29,7 +31,7 @@ serves it on ``GET /api/state`` and runs the drain handshake):
|
|
|
29
31
|
from __future__ import annotations
|
|
30
32
|
|
|
31
33
|
from datetime import datetime, timezone
|
|
32
|
-
from typing import Iterable, Mapping, Sequence
|
|
34
|
+
from typing import Collection, Iterable, Mapping, Sequence
|
|
33
35
|
|
|
34
36
|
# The timer kinds of the design's idle block (§2.1). devloop owns the first
|
|
35
37
|
# three; the last two are revloop's and are listed so the union is stated once.
|
|
@@ -111,19 +113,107 @@ def queued_items(queues: "object") -> "int | None":
|
|
|
111
113
|
return count
|
|
112
114
|
|
|
113
115
|
|
|
116
|
+
# The tmux prefix `alissa tmux` puts on a managed session's real name
|
|
117
|
+
# (`alissa.MANAGED_PREFIX`, restated so this module imports nothing of the
|
|
118
|
+
# daemon's). A listing row carries both spellings (`name` bare, `session`
|
|
119
|
+
# prefixed), a snapshot stage the bare one, a queue the prefixed one.
|
|
120
|
+
MANAGED_PREFIX = "ali-"
|
|
121
|
+
|
|
122
|
+
# The spawn ledgers (`Sources.ledgers` keys) with the snapshot edge each one
|
|
123
|
+
# feeds, the column holding its issue / PR number, and the slug separator the
|
|
124
|
+
# poll snapshot uses for it (`owner/repo#7`, `owner/repo#pr16`).
|
|
125
|
+
LEDGER_LANES = (
|
|
126
|
+
("spawns", "issue", "issue", "#"),
|
|
127
|
+
("fix_spawns", "fix", "number", "#pr"),
|
|
128
|
+
("maintain_spawns", "maintain", "number", "#pr"),
|
|
129
|
+
("revert_spawns", "revert", "number", "#pr"),
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
# A stage holding one of these actions has a worker in flight on it whatever
|
|
133
|
+
# its owed verdict says: its session is never an orphan.
|
|
134
|
+
IN_FLIGHT_ACTIONS = frozenset({"spawned", "in-flight"})
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def bare_session(name: "object") -> str:
|
|
138
|
+
"""A session name without its `ali-` tmux prefix ("" for a non-name)."""
|
|
139
|
+
text = str(name or "")
|
|
140
|
+
return text[len(MANAGED_PREFIX):] if text.startswith(MANAGED_PREFIX) else text
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def lane_sessions(
|
|
144
|
+
snapshot: "Mapping | None",
|
|
145
|
+
*,
|
|
146
|
+
ledgers: "Mapping | None" = None,
|
|
147
|
+
queues: "object" = None,
|
|
148
|
+
) -> "frozenset[str] | None":
|
|
149
|
+
"""The bare names of the sessions that back a lane the seat still holds
|
|
150
|
+
(issue #166): the session a stage that is owed or in flight names; every
|
|
151
|
+
session the spawn ledgers record on such a stage's lane (an earlier
|
|
152
|
+
attempt the lane may still resume); and every session the alissa queue
|
|
153
|
+
holds an undispatched item for. None before the first pass -- with no
|
|
154
|
+
snapshot nothing can be called an orphan, so every row stays managed."""
|
|
155
|
+
if snapshot is None:
|
|
156
|
+
return None
|
|
157
|
+
names: "set[str]" = set()
|
|
158
|
+
held: "set[tuple[str, str]]" = set()
|
|
159
|
+
for stage in snapshot.get("stages") or ():
|
|
160
|
+
if not isinstance(stage, Mapping):
|
|
161
|
+
continue
|
|
162
|
+
if not (stage_owed(stage) or stage.get("stage") in IN_FLIGHT_ACTIONS):
|
|
163
|
+
continue
|
|
164
|
+
held.add((str(stage.get("edge") or ""), str(stage.get("slug") or "")))
|
|
165
|
+
if stage.get("session"):
|
|
166
|
+
names.add(bare_session(stage["session"]))
|
|
167
|
+
for table, edge, column, sep in LEDGER_LANES:
|
|
168
|
+
for row in (ledgers or {}).get(table) or ():
|
|
169
|
+
lane = (edge, f"{row.get('repo_slug')}{sep}{row.get(column)}")
|
|
170
|
+
if lane in held and row.get("session"):
|
|
171
|
+
names.add(bare_session(row["session"]))
|
|
172
|
+
if isinstance(queues, list):
|
|
173
|
+
for queue in queues:
|
|
174
|
+
if not isinstance(queue, Mapping):
|
|
175
|
+
continue
|
|
176
|
+
if any(
|
|
177
|
+
isinstance(item, Mapping) and not item.get("lastDispatchedAt")
|
|
178
|
+
for item in queue.get("items") or ()
|
|
179
|
+
):
|
|
180
|
+
names.add(bare_session(queue.get("session")))
|
|
181
|
+
names.discard("")
|
|
182
|
+
return frozenset(names)
|
|
183
|
+
|
|
184
|
+
|
|
114
185
|
def roster_counts(
|
|
115
186
|
roster: "Sequence[Mapping] | None",
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
187
|
+
lanes: "Collection[str] | None" = None,
|
|
188
|
+
) -> "tuple[int | None, int | None, list[str] | None]":
|
|
189
|
+
"""`(live, managed, gone)` from an ``alissa tmux ls --json`` listing.
|
|
190
|
+
|
|
191
|
+
A row is live unless it says otherwise (the fleet-vitals `sessions`
|
|
192
|
+
rule: its `live` flag, else `status != "gone"`). A row is MANAGED when it
|
|
193
|
+
is live or when its session backs a lane the seat still holds -- `lanes`,
|
|
194
|
+
the bare names `lane_sessions` returns. A dead row matching no lane is an
|
|
195
|
+
orphan: it is not managed, and its name is reported in `gone` instead
|
|
196
|
+
(issue #166 -- one such row kept orcloop's `managed == 0` from ever
|
|
197
|
+
holding). `lanes=None` (no completed pass to read lanes from) keeps every
|
|
198
|
+
row managed: unknown is never idle. `(None, None, None)` when the listing
|
|
199
|
+
failed."""
|
|
120
200
|
if roster is None:
|
|
121
|
-
return None, None
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
201
|
+
return None, None, None
|
|
202
|
+
live = managed = 0
|
|
203
|
+
gone: "list[str]" = []
|
|
204
|
+
for row in roster:
|
|
205
|
+
if not isinstance(row, Mapping):
|
|
206
|
+
continue
|
|
207
|
+
if bool(row.get("live", row.get("status") != "gone")):
|
|
208
|
+
live += 1
|
|
209
|
+
managed += 1
|
|
210
|
+
continue
|
|
211
|
+
name = bare_session(row.get("name") or row.get("session"))
|
|
212
|
+
if lanes is None or name in lanes or bare_session(row.get("session")) in lanes:
|
|
213
|
+
managed += 1
|
|
214
|
+
else:
|
|
215
|
+
gone.append(name)
|
|
216
|
+
return live, managed, sorted(gone)
|
|
127
217
|
|
|
128
218
|
|
|
129
219
|
def idle_block(
|
|
@@ -134,10 +224,13 @@ def idle_block(
|
|
|
134
224
|
queued: "int | None",
|
|
135
225
|
drain: "Mapping | None",
|
|
136
226
|
now: "float | int",
|
|
227
|
+
gone: "Sequence[str] | None" = None,
|
|
137
228
|
) -> dict:
|
|
138
229
|
"""The seat's idle block from its latest poll snapshot (None before the
|
|
139
230
|
first pass) and the caller's live readings. `drain` is the holding drain
|
|
140
|
-
row (`State.drain`) or None.
|
|
231
|
+
row (`State.drain`) or None. `gone` is `roster_counts`' orphaned rows,
|
|
232
|
+
reported as `{count, sessions}` (null when the listing was not read) --
|
|
233
|
+
an observation for the operator, never a refusal."""
|
|
141
234
|
stages: "Iterable[Mapping]" = (snapshot or {}).get("stages") or ()
|
|
142
235
|
owed: "int | None" = None
|
|
143
236
|
timers: "list[dict]" = []
|
|
@@ -157,6 +250,10 @@ def idle_block(
|
|
|
157
250
|
"live": live,
|
|
158
251
|
"managed": managed,
|
|
159
252
|
"queued": queued,
|
|
253
|
+
"gone": (
|
|
254
|
+
{"count": len(gone), "sessions": list(gone)}
|
|
255
|
+
if gone is not None else None
|
|
256
|
+
),
|
|
160
257
|
"timers": timers,
|
|
161
258
|
"drainedAt": iso(drain.get("drained_at")) if drain else None,
|
|
162
259
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.8.43
|
|
@@ -438,17 +438,25 @@ class Sources:
|
|
|
438
438
|
a drain holds. A caller that already holds the listing (None when it
|
|
439
439
|
failed) or the latest snapshot (None before the first pass) hands it
|
|
440
440
|
in; anything not handed in is read here. A failed listing is null
|
|
441
|
-
counts, never zeros.
|
|
441
|
+
counts, never zeros. A dead row backing no lane the seat holds is
|
|
442
|
+
reported under `gone`, not counted as managed (issue #166)."""
|
|
442
443
|
if latest is _UNREAD:
|
|
443
444
|
snaps = self.snapshots(1)
|
|
444
445
|
latest = snaps[0] if snaps else None
|
|
445
446
|
if listing is _UNREAD:
|
|
446
447
|
listing = self._safe_json(["alissa", "tmux", "ls", "--json"])
|
|
447
448
|
roster = listing if isinstance(listing, list) else None
|
|
448
|
-
|
|
449
|
+
snapshot = latest if isinstance(latest, dict) else None
|
|
450
|
+
queues = self._safe_json(["alissa", "tmux", "queue", "ls", "--json"])
|
|
451
|
+
# A dead row counts as managed only while it backs a lane the seat
|
|
452
|
+
# still holds (issue #166).
|
|
453
|
+
lanes = parking.lane_sessions(
|
|
454
|
+
snapshot, ledgers=self.ledgers(), queues=queues,
|
|
455
|
+
)
|
|
456
|
+
live, managed, gone = parking.roster_counts(roster, lanes)
|
|
449
457
|
return parking.idle_block(
|
|
450
|
-
|
|
451
|
-
|
|
458
|
+
snapshot, live=live, managed=managed,
|
|
459
|
+
queued=parking.queued_items(queues), gone=gone,
|
|
452
460
|
drain=self.drain(), now=self._wall(),
|
|
453
461
|
)
|
|
454
462
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: alissa-tools-github-devloop
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.43
|
|
4
4
|
Summary: ALISSA-TOOLS-GITHUB-DEVLOOP
|
|
5
5
|
Home-page: https://alissa.app
|
|
6
6
|
Author: Fahera
|
|
@@ -194,6 +194,32 @@ resolved from the remote name or URL in the directory the push runs in
|
|
|
194
194
|
no marker are untouched. `ALISSA_SHELL_GUARD=off` disables this guard with
|
|
195
195
|
the rest.
|
|
196
196
|
|
|
197
|
+
## Seat parking: what counts as managed (issue #166)
|
|
198
|
+
|
|
199
|
+
The idle block on `GET /api/state` (`parking.idle_block`, issue #153) is what
|
|
200
|
+
orcloop's D2 invariant reads before it drains and parks the seat: idle only
|
|
201
|
+
when `live == managed == queued == owed == 0` with no timer and a fresh pass.
|
|
202
|
+
`live` and `managed` come from `alissa tmux ls --json`:
|
|
203
|
+
|
|
204
|
+
- a row is **live** unless it says otherwise (its `live` flag, else
|
|
205
|
+
`status != "gone"`);
|
|
206
|
+
- a row is **managed** when it is live, **or** when it is dead but its
|
|
207
|
+
session still backs a lane the seat holds — the session named by a stage
|
|
208
|
+
of the latest poll snapshot that is owed or in flight (`spawned` /
|
|
209
|
+
`in-flight`), any session the spawn ledgers record on such a stage's lane
|
|
210
|
+
(an earlier attempt the lane may still resume), or a session the alissa
|
|
211
|
+
queue holds an undispatched item for;
|
|
212
|
+
- a dead row backing none of these is an **orphan**: it is not managed, and
|
|
213
|
+
is reported in the block's `gone` field as `{count, sessions}` so the
|
|
214
|
+
console and orcloop's logs still show it.
|
|
215
|
+
|
|
216
|
+
Unknown is never idle: a failed listing leaves `live`, `managed` and `gone`
|
|
217
|
+
null, and before the first completed pass (no snapshot to read lanes from)
|
|
218
|
+
every dead row stays managed. The console only reports an orphan — it never
|
|
219
|
+
kills or reaps one. The fleet-vitals push applies the same rule to
|
|
220
|
+
`sessions.managed` and `idle.managed` (Studio's park guard reads both) and
|
|
221
|
+
leaves `gone` out, since Studio's idle schema is strict.
|
|
222
|
+
|
|
197
223
|
## License
|
|
198
224
|
|
|
199
225
|
Licensed under the
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
0.8.42
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|