manta-node 0.6b5.dev705__py3-none-any.whl → 0.6b5.dev707__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.
@@ -4,7 +4,7 @@ This file is auto-generated at build time by scripts/generate_version.py.
4
4
  Do not edit manually.
5
5
  """
6
6
 
7
- __version__ = "2026.09.24.d60eab69"
8
- __build_date__ = "2026.09.24"
9
- __commit_id__ = "d60eab69"
7
+ __version__ = "2026.09.25.d14aac9a"
8
+ __build_date__ = "2026.09.25"
9
+ __commit_id__ = "d14aac9a"
10
10
  __package_version__ = "0.6b5"
@@ -20,6 +20,7 @@ from __future__ import annotations
20
20
  import time
21
21
  from collections.abc import Callable
22
22
  from dataclasses import dataclass
23
+ from pathlib import Path
23
24
  from typing import Protocol
24
25
 
25
26
  from ..seam import SeamState, SwitchOutcome, UpdateRequest
@@ -28,6 +29,11 @@ from .bounds import Bounds
28
29
  from .inflight import InFlight, clear_inflight, deadline_from_now, write_inflight
29
30
  from .switch import VersionTreeError
30
31
 
32
+ # The free-space floor to stage a version dir. A version dir holds a full
33
+ # dependency closure, so the check refuses well before the disk is full rather
34
+ # than half-staging (NF-12). Operator-overridable via `run_switch`'s argument.
35
+ MIN_FREE_BYTES = 500 * 1024 * 1024
36
+
31
37
 
32
38
  class LifecycleError(Exception):
33
39
  """A lifecycle step failed; the node stays on its current version (NF-3)."""
@@ -88,6 +94,7 @@ def run_switch(
88
94
  is_back: ReturnProbe,
89
95
  bounds: Bounds | None = None,
90
96
  now: Callable[[], float] = time.time,
97
+ min_free_bytes: int = MIN_FREE_BYTES,
91
98
  ) -> SwitchOutcome:
92
99
  """Run the lifecycle for one request and return its terminal outcome.
93
100
 
@@ -121,6 +128,16 @@ def run_switch(
121
128
  if not _within(bounds.verify, lambda: verify(candidate_dir, candidate_python)):
122
129
  return _outcome(request, SeamState.FAILED, "verification failed (no switch)")
123
130
 
131
+ # FREE SPACE — refuse before staging when there is no room for a full version
132
+ # dir (NF-12, §6.2). Refusing here, before any write, is what stops a
133
+ # half-staged tree: the node stays on its current version.
134
+ if not version_tree.has_room_for(min_free_bytes):
135
+ return _outcome(
136
+ request,
137
+ SeamState.REFUSED,
138
+ "insufficient free space to stage a version dir",
139
+ )
140
+
124
141
  # STAGE — materialise the candidate's version dir (idempotent), bounded.
125
142
  staged: dict = {}
126
143
 
@@ -145,11 +162,13 @@ def run_switch(
145
162
  try:
146
163
  if not _within(bounds.switch, lambda: version_tree.commit(target)):
147
164
  clear_inflight()
165
+ _reclaim_candidate(bounds, target, last_good_dir)
148
166
  return _outcome(
149
167
  request, SeamState.FAILED, "switch exceeded its bound (repoint failed)"
150
168
  )
151
169
  except VersionTreeError as exc:
152
170
  clear_inflight()
171
+ _reclaim_candidate(bounds, target, last_good_dir)
153
172
  return _outcome(request, SeamState.FAILED, f"switch failed: {exc}")
154
173
 
155
174
  # RESTART onto the committed version, then wait for the return window. A
@@ -157,10 +176,18 @@ def run_switch(
157
176
  # the record rather than letting the exception escape.
158
177
  if not _within(bounds.switch, restart):
159
178
  clear_inflight()
179
+ _reclaim_candidate(bounds, target, last_good_dir)
160
180
  return _outcome(request, SeamState.DOWN, "restart failed after the switch")
161
181
 
162
182
  if is_back(record.deadline_epoch):
163
183
  clear_inflight()
184
+ # Reclaim on a SUCCESSFUL switch (NF-12/B-34): the tree stays bounded, and
185
+ # the current and last-good versions are never removed. No separate
186
+ # sweeper: reclamation rides the switch that made a version superseded.
187
+ protect = {target}
188
+ if last_good_dir is not None:
189
+ protect.add(last_good_dir)
190
+ version_tree.reclaim(bounds.retention_dirs, protect)
164
191
  return _outcome(
165
192
  request,
166
193
  SeamState.REPORTING,
@@ -172,6 +199,7 @@ def run_switch(
172
199
  if last_good is None:
173
200
  # Nothing to revert to: surface DOWN rather than claim success.
174
201
  clear_inflight()
202
+ _reclaim_candidate(bounds, target, last_good_dir)
175
203
  return _outcome(
176
204
  request, SeamState.DOWN, "no return and no last-good version to revert to"
177
205
  )
@@ -181,8 +209,13 @@ def run_switch(
181
209
  raise LifecycleError("restart after revert failed")
182
210
  except (VersionTreeError, LifecycleError) as exc:
183
211
  clear_inflight()
212
+ _reclaim_candidate(bounds, target, last_good_dir)
184
213
  return _outcome(request, SeamState.DOWN, f"revert failed: {exc}")
185
214
  clear_inflight()
215
+ # The candidate was superseded the moment the switch did not land: it is
216
+ # reclaimed even though the switch failed, so repeated failing switches do
217
+ # not grow the tree past the bound (NF-12). current/last-good are protected.
218
+ _reclaim_candidate(bounds, target, last_good_dir)
186
219
  return _outcome(
187
220
  request,
188
221
  SeamState.REVERTED,
@@ -191,6 +224,29 @@ def run_switch(
191
224
  )
192
225
 
193
226
 
227
+ def _reclaim_candidate(
228
+ bounds: Bounds, candidate: Path, last_good_dir: Path | None
229
+ ) -> None:
230
+ """Reclaim after a switch that did NOT land: the candidate is superseded.
231
+
232
+ Called on every FAILED/REVERTED/DOWN path after staging, so repeated failing
233
+ switches do not grow the tree past ``retention_dirs`` (NF-12). The candidate is
234
+ removed first, then ``reclaim`` bounds the rest, protecting ``current`` and the
235
+ last good.
236
+
237
+ Whether the candidate is still current depends on the branch: a failed commit
238
+ leaves it non-current, but a switch whose commit succeeded and whose RESTART
239
+ then failed (or found no last good to revert to) has ``target`` AS ``current``.
240
+ ``remove_version`` therefore refuses to delete whatever ``current`` names, and
241
+ that check — not any assumption here — is what keeps the node runnable on those
242
+ DOWN paths. Keep it if the candidate could ever be current.
243
+ """
244
+ version_tree.remove_version(candidate)
245
+ current = version_tree.selected()
246
+ protect = {p for p in (current, last_good_dir) if p is not None}
247
+ version_tree.reclaim(bounds.retention_dirs, protect)
248
+
249
+
194
250
  def _within(bound: float, step: Callable[[], None]) -> bool:
195
251
  """Run ``step``; True if it completed, False if it raised or exceeded ``bound``.
196
252
 
@@ -40,9 +40,11 @@ from ..seam import (
40
40
  UpdateRequest,
41
41
  seam_socket_path,
42
42
  )
43
+ from . import switch as version_tree
43
44
  from .bounds import load_bounds
44
45
  from .executor import ChildControl, make_executor
45
46
  from .inflight import read_inflight
47
+ from .lifecycle import MIN_FREE_BYTES
46
48
  from .records import (
47
49
  supervisor_record,
48
50
  supervisor_record_path,
@@ -358,10 +360,47 @@ def apply_command(argv: list[str]) -> int:
358
360
  return 0 if outcome.state not in (SeamState.FAILED, SeamState.DOWN) else 1
359
361
 
360
362
 
363
+ def preflight_command(argv: list[str]) -> int:
364
+ """`manta-supervisor preflight <pair>` — may this node take another version?
365
+
366
+ **Local, not a socket verb.** It reads the node's own version tree and applies
367
+ the supervisor's room policy *without writing anything*; it needs no running
368
+ supervisor, so the fleet path can ask before it creates the version dir and the
369
+ refusal is early (NF-12 has teeth). The deploy only asks; the supervisor owns
370
+ the policy.
371
+
372
+ Output mirrors ``apply`` but echoes the pair: ``<state>: <pair>: <reason>``
373
+ (``ok`` / ``refused``), exiting **0** either way — the state is the verdict,
374
+ not the exit code.
375
+ """
376
+ parser = argparse.ArgumentParser(
377
+ prog="manta-supervisor preflight",
378
+ description="Check room for one more version dir before installing it.",
379
+ )
380
+ parser.add_argument("pair", help="the pair to be staged (for the message)")
381
+ parser.add_argument(
382
+ "--min-free-bytes",
383
+ type=int,
384
+ default=None,
385
+ help="override the free-space floor (defaults to the configured floor)",
386
+ )
387
+ args = parser.parse_args(argv)
388
+
389
+ floor = args.min_free_bytes if args.min_free_bytes is not None else MIN_FREE_BYTES
390
+ result = version_tree.preflight(floor)
391
+ state = "ok" if result.ok else "refused"
392
+ # The pair is echoed so an operator running preflight across several pairs
393
+ # can tell which line is which.
394
+ print(f"{state}: {args.pair}: {result.reason}")
395
+ return 0
396
+
397
+
361
398
  def main(argv: list[str] | None = None) -> int:
362
399
  argv = list(sys.argv[1:] if argv is None else argv)
363
400
  if argv and argv[0] == "apply":
364
401
  return apply_command(argv[1:])
402
+ if argv and argv[0] == "preflight":
403
+ return preflight_command(argv[1:])
365
404
 
366
405
  parser = argparse.ArgumentParser(
367
406
  prog="manta-supervisor",
@@ -22,7 +22,9 @@ the token, config or datasets.
22
22
  from __future__ import annotations
23
23
 
24
24
  import os
25
- from dataclasses import dataclass
25
+ import shutil
26
+ import sys
27
+ from dataclasses import dataclass, replace
26
28
  from datetime import datetime, timezone
27
29
  from importlib.metadata import PackageNotFoundError, version
28
30
  from pathlib import Path
@@ -70,19 +72,31 @@ def current_link() -> Path:
70
72
 
71
73
  @dataclass(frozen=True)
72
74
  class Version:
73
- """A version dir's identity: the pair and the interpreter that runs it."""
75
+ """A version dir's identity: the pair, the interpreter, and its size.
76
+
77
+ ``size_bytes`` is recorded once (see ``version_dir_bytes``) so the room check
78
+ is a manifest read, not a recursive walk of the dependency closure on every
79
+ switch. A version dir is immutable (NF-1), so a recorded size stays valid.
80
+ """
74
81
 
75
82
  pair: str
76
83
  python: str
77
84
  created: str
85
+ size_bytes: int | None = None
78
86
 
79
87
  def to_dict(self) -> dict:
80
- return {"pair": self.pair, "python": self.python, "created": self.created}
88
+ data = {"pair": self.pair, "python": self.python, "created": self.created}
89
+ if self.size_bytes is not None:
90
+ data["size_bytes"] = self.size_bytes
91
+ return data
81
92
 
82
93
  @classmethod
83
94
  def from_dict(cls, data: dict) -> Version:
84
95
  return cls(
85
- pair=data["pair"], python=data["python"], created=data.get("created", "")
96
+ pair=data["pair"],
97
+ python=data["python"],
98
+ created=data.get("created", ""),
99
+ size_bytes=data.get("size_bytes"),
86
100
  )
87
101
 
88
102
 
@@ -199,3 +213,225 @@ def is_pointer_target(target: Path) -> bool:
199
213
  """True when ``current`` resolves to ``target``."""
200
214
  link = current_link()
201
215
  return link.is_symlink() and Path(os.path.realpath(link)) == target
216
+
217
+
218
+ def list_versions() -> list[Path]:
219
+ """The version dirs, oldest first.
220
+
221
+ Ordered by the manifest's ``created`` (a dir with no readable manifest sorts
222
+ first, so a corrupt one is reclaimed before a good one). Used by retention to
223
+ pick what to reclaim.
224
+ """
225
+ root = versions_dir()
226
+ if not root.is_dir():
227
+ return []
228
+
229
+ def created(p: Path) -> str:
230
+ manifest = read_manifest(p)
231
+ return manifest.created if manifest else ""
232
+
233
+ dirs = [p for p in root.iterdir() if p.is_dir()]
234
+ return sorted(dirs, key=created)
235
+
236
+
237
+ def remove_version(version_dir: Path) -> bool:
238
+ """Remove one version dir and its contents; True if it is gone.
239
+
240
+ Best-effort, like ``reclaim``: a dir that cannot be removed is reported, not
241
+ raised — a stale dir must not fail the switch that is trying to discard it.
242
+ """
243
+ # Never remove the version `current` names, even if a caller asks: the boot
244
+ # path must always resolve to something runnable.
245
+ if os.path.realpath(version_dir) == os.path.realpath(current_link()):
246
+ return False
247
+ try:
248
+ for child in version_dir.iterdir():
249
+ if child.is_dir():
250
+ shutil.rmtree(child)
251
+ else:
252
+ child.unlink()
253
+ version_dir.rmdir()
254
+ return True
255
+ except OSError as exc:
256
+ print(f"retention: could not remove {version_dir}: {exc}", file=sys.stderr)
257
+ return False
258
+
259
+
260
+ def reclaim(retention_dirs: int, protect: set[Path]) -> list[Path]:
261
+ """Remove the oldest versions beyond ``retention_dirs``, never a protected one.
262
+
263
+ NF-12/B-34: the version tree stays bounded, but the **current** and last-good
264
+ versions are never removed — ``protect`` names them. Returns the removed dirs.
265
+ Called on a successful switch, so there is no separate sweeper to start.
266
+ """
267
+ versions = list_versions()
268
+ protected = {os.path.realpath(p) for p in protect}
269
+ # Oldest first; drop the front until the count is within the bound, skipping
270
+ # a protected dir (do not count it as removable, but it still occupies a slot).
271
+ removed: list[Path] = []
272
+ keep = max(retention_dirs, 2) # current + last good is the floor
273
+ for candidate in versions:
274
+ if len(versions) - len(removed) <= keep:
275
+ break
276
+ if os.path.realpath(candidate) in protected:
277
+ continue
278
+ if remove_version(candidate):
279
+ removed.append(candidate)
280
+ return removed
281
+
282
+
283
+ def _free_bytes() -> int:
284
+ """Free bytes on the version tree's filesystem.
285
+
286
+ Checks the nearest existing ancestor: the tree may not exist yet on a fresh
287
+ node, and the capacity is the filesystem's either way.
288
+ """
289
+ probe = versions_dir()
290
+ while not probe.exists() and probe != probe.parent:
291
+ probe = probe.parent
292
+ return shutil.disk_usage(probe).free
293
+
294
+
295
+ def version_dir_bytes(version_dir: Path) -> int:
296
+ """The on-disk size of a version dir.
297
+
298
+ Cached in the manifest once measured: a version dir is immutable (NF-1), so
299
+ the size does not change, and a switch on an ARM edge node must not re-walk a
300
+ full dependency closure. The first read measures and writes it back; every
301
+ later read is a manifest lookup. A dir with no readable manifest is measured
302
+ but not cached (there is nowhere to write it).
303
+ """
304
+ manifest = read_manifest(version_dir)
305
+ if manifest is not None and manifest.size_bytes is not None:
306
+ return manifest.size_bytes
307
+
308
+ total = 0
309
+ for path in version_dir.rglob("*"):
310
+ try:
311
+ if path.is_file():
312
+ total += path.stat().st_size
313
+ except OSError:
314
+ # A file that vanished mid-walk is not counted; the size is an
315
+ # estimate for the room check, not a ledger.
316
+ continue
317
+
318
+ if manifest is not None:
319
+ write_json_atomic(
320
+ version_dir / MANIFEST_NAME,
321
+ replace(manifest, size_bytes=total).to_dict(),
322
+ )
323
+ return total
324
+
325
+
326
+ def largest_version_bytes() -> int:
327
+ """The size of the largest existing version dir, or 0 when the tree is empty.
328
+
329
+ This is the best available estimate of "what one more version dir costs":
330
+ the versions on this host are the same package and dependency closure, so the
331
+ largest already-present dir is a floor for the candidate's footprint. Sizes
332
+ come from each dir's manifest cache (see ``version_dir_bytes``), so this is
333
+ manifest reads, not recursive walks.
334
+ """
335
+ sizes = [version_dir_bytes(v) for v in list_versions()]
336
+ return max(sizes) if sizes else 0
337
+
338
+
339
+ def candidate_bytes(min_bytes: int) -> int:
340
+ """The bytes the guard should require free for one more version dir.
341
+
342
+ The design (NF-12, §6.2) is "insufficient free space to stage a FULL version
343
+ dir", so the requirement is the largest existing version dir — known from the
344
+ tree — floored by ``min_bytes`` as a backstop for a fresh or tiny tree.
345
+ """
346
+ return max(largest_version_bytes(), min_bytes)
347
+
348
+
349
+ def has_room_for(min_bytes: int) -> bool:
350
+ """True when the tree's filesystem has room for one more full version dir.
351
+
352
+ The free-space refusal (NF-12, mechanism §6.2): a node with no room to stage
353
+ a full version dir refuses the update rather than half-staging. The
354
+ requirement is ``candidate_bytes(min_bytes)`` — the largest existing version
355
+ dir, floored by ``min_bytes`` — not merely a fixed floor.
356
+ """
357
+ return _free_bytes() >= candidate_bytes(min_bytes)
358
+
359
+
360
+ @dataclass(frozen=True)
361
+ class Preflight:
362
+ """The supervisor's answer to "may this node take one more version dir?"."""
363
+
364
+ ok: bool
365
+ reason: str
366
+ free_bytes: int
367
+ required_bytes: int
368
+ versions: int
369
+ reclaimable: int
370
+
371
+ def to_dict(self) -> dict:
372
+ return {
373
+ "ok": self.ok,
374
+ "reason": self.reason,
375
+ "free_bytes": self.free_bytes,
376
+ "required_bytes": self.required_bytes,
377
+ "versions": self.versions,
378
+ "reclaimable": self.reclaimable,
379
+ }
380
+
381
+
382
+ def preflight(min_free_bytes: int) -> Preflight:
383
+ """Whether the node can stage ONE more version dir, read-only.
384
+
385
+ The fleet path calls this **before** it creates anything, so a refusal is
386
+ early and the supervisor owns the policy — the same room requirement the STAGE
387
+ check uses (one policy, one place). Measures and decides; creates nothing.
388
+
389
+ The one refusal is **room**: the filesystem must have ``candidate_bytes`` free
390
+ for the candidate's contents (NF-12). Retention is **not** a refusal — a real
391
+ switch always brings the tree within its bound by reclaiming, so a small tree
392
+ has *more* headroom, not less. ``reclaimable`` is reported for observability;
393
+ it does not affect the verdict.
394
+
395
+ ``reclaimable`` is the count of existing dirs that could be removed while
396
+ protecting the two versions a switch holds: the candidate (not yet created)
397
+ and whatever is current right now.
398
+ """
399
+ free = _free_bytes()
400
+ required = candidate_bytes(min_free_bytes)
401
+ versions = list_versions()
402
+ current = selected()
403
+ # A real switch reclaims with `protect = {candidate, current-now}` — the
404
+ # candidate does not exist yet, so among EXISTING dirs only current is
405
+ # protected. (After the switch, today's current becomes the last good, which
406
+ # is why the two-versions-retained rule holds; but at preflight time the
407
+ # second protected slot is the not-yet-staged candidate, not another existing
408
+ # dir.) So every existing dir but current is reclaimable.
409
+ protected = {os.path.realpath(current)} if current is not None else set()
410
+ reclaimable = sum(1 for v in versions if os.path.realpath(v) not in protected)
411
+ # Retention is NOT a refusal here. A real switch always satisfies its bound:
412
+ # after the candidate lands, `reclaim` removes reclaimable dirs down to the
413
+ # configured keep, and since every existing dir but current is reclaimable the
414
+ # tree always reaches `min(N+1, keep) <= keep`. So retention cannot make an
415
+ # update fail; refusing on it would reject exactly the trees with the most
416
+ # headroom (a small tree under a larger bound). `reclaimable` is reported for
417
+ # observability, not as a gate.
418
+ if free < required:
419
+ return Preflight(
420
+ ok=False,
421
+ reason=(
422
+ f"insufficient free space: {free} < {required} bytes"
423
+ " (one version dir, floored at the minimum)"
424
+ ),
425
+ free_bytes=free,
426
+ required_bytes=required,
427
+ versions=len(versions),
428
+ reclaimable=reclaimable,
429
+ )
430
+ return Preflight(
431
+ ok=True,
432
+ reason="room for one more version dir is available",
433
+ free_bytes=free,
434
+ required_bytes=required,
435
+ versions=len(versions),
436
+ reclaimable=reclaimable,
437
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: manta_node
3
- Version: 0.6b5.dev705
3
+ Version: 0.6b5.dev707
4
4
  Summary: Node side software of the Manta platform. It is responsible for the communication with the Manta Core and the execution of the tasks.
5
5
  Author-email: Hugo Miralles <hugo.miralles@manta-tech.io>, Matthew Thompson <matthew.thompson@manta-tech.io>
6
6
  License-Expression: AGPL-3.0-or-later
@@ -6,7 +6,7 @@ manta_node/task_manager.py,sha256=xRE-yKdUW0FFR8vtX1jDwl1AxTbqVD_JS7QVR62GA00,38
6
6
  manta_node/tasks.py,sha256=o837Hi-KFVJg9LF5iOtE8oD6UuTOfrEPWR-JOdhRGeY,6983
7
7
  manta_node/utils.py,sha256=Hw4Iw2jYQYsfVLVJuBMt-_rMFBeml3nCDE2jiiXc8YM,1155
8
8
  manta_node/cli/__init__.py,sha256=arzO3mXDJ-E2yeeytbZfN7mCswwxdQ43_QjyeL2E5yE,134
9
- manta_node/cli/_version_generated.py,sha256=Avtgxf4CTYxrwjUmb8TM92DAZRNRqrzV-eNId81M60o,275
9
+ manta_node/cli/_version_generated.py,sha256=oq-vrYUSYqZEeiOos3Fqzj9GAI7ShLAmLb-7xD8MTio,275
10
10
  manta_node/cli/config_manager.py,sha256=BCju9zqzraviL3KrzRnB-vZQUY__Q5kuEqb7EdB8YdI,4409
11
11
  manta_node/cli/main.py,sha256=dZa6zrypWBvpN8hd4qpxevvlyNFhxKINCg1537NJ36w,4318
12
12
  manta_node/cli/version.py,sha256=3lrnIWpOosszBN-VsCBVLxDgpvCC7IB0aDIKx_3e7hI,3115
@@ -56,14 +56,14 @@ manta_node/supervisor/__init__.py,sha256=kgdoNce1tM4e7jvdxkdmnNJV1L92-NI2cRfYSdm
56
56
  manta_node/supervisor/bounds.py,sha256=MlUfj0tYY-US348JsxYA-Ph5pSpgoOPlRdVTwmrPtZA,3181
57
57
  manta_node/supervisor/executor.py,sha256=a0JafVoTmtbIu87nwUtj08KAC-Q-u0Ajdn_ptyu9yoc,6833
58
58
  manta_node/supervisor/inflight.py,sha256=JOqwXYM928fpcQtYdoK7596eAg28KM70bh85cyQgX3Q,2663
59
- manta_node/supervisor/lifecycle.py,sha256=JE74H0ODt5_y997kliiVDiQAf1laYptNjkrnnfGdgK0,7411
60
- manta_node/supervisor/process.py,sha256=mWd2C4-S8NpupcLp0YbBEYmqQUQbrfMAZi5TF9UQ_cM,17504
59
+ manta_node/supervisor/lifecycle.py,sha256=SA-xJqlxwbLa37fvp0W_YbUZbrRz9vQJDpg6vvuokpI,10303
60
+ manta_node/supervisor/process.py,sha256=N5WvNCJnJR3c6I4uqBPPFqgEdCI9rFulHyGem9Shc-I,19155
61
61
  manta_node/supervisor/records.py,sha256=fUlL1SfmWpGnCbJ9956cuy0H3a7-aEF-B-naqbJiYoU,6405
62
62
  manta_node/supervisor/seam.py,sha256=HomMFTDbc0dZQ9VrfGVhQxjiU7E23-zwyjZnLsnq6dc,8031
63
- manta_node/supervisor/switch.py,sha256=BUSvMdWyUfZmeEVSBmQ7wLNRD7troPxZ3pdg8tOGXo4,7433
64
- manta_node-0.6b5.dev705.dist-info/licenses/LICENSE,sha256=9mBgLhiB9goQPQjg9XQJ6FjgVxYw9Q8lyOZYkPGwF_c,34484
65
- manta_node-0.6b5.dev705.dist-info/METADATA,sha256=ajleN4PRTTgRVihXNc2WTZEatE_cgnmro32xQBBd1JY,4231
66
- manta_node-0.6b5.dev705.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
67
- manta_node-0.6b5.dev705.dist-info/entry_points.txt,sha256=uCwggRApalLEsYs1YxwTBN0-e9oMJAkMoBp01HaSdoY,110
68
- manta_node-0.6b5.dev705.dist-info/top_level.txt,sha256=nbT5J5WETuXM9UPoQckl29uUOq8wMYdRb9QDx5kAFeo,11
69
- manta_node-0.6b5.dev705.dist-info/RECORD,,
63
+ manta_node/supervisor/switch.py,sha256=iWLukvuxynK3NU26iIk0qhkwrRPUyJuutV67QA3aLO8,16732
64
+ manta_node-0.6b5.dev707.dist-info/licenses/LICENSE,sha256=9mBgLhiB9goQPQjg9XQJ6FjgVxYw9Q8lyOZYkPGwF_c,34484
65
+ manta_node-0.6b5.dev707.dist-info/METADATA,sha256=uOmOaIVFbJllDFQMjCOr4zfNlYc-Qq9V4VaBk6uHYDg,4231
66
+ manta_node-0.6b5.dev707.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
67
+ manta_node-0.6b5.dev707.dist-info/entry_points.txt,sha256=uCwggRApalLEsYs1YxwTBN0-e9oMJAkMoBp01HaSdoY,110
68
+ manta_node-0.6b5.dev707.dist-info/top_level.txt,sha256=nbT5J5WETuXM9UPoQckl29uUOq8wMYdRb9QDx5kAFeo,11
69
+ manta_node-0.6b5.dev707.dist-info/RECORD,,