mirror of
https://github.com/CyberMind-FR/secubox-deb.git
synced 2026-07-29 09:14:33 +00:00
fix(profiles): default lifecycle is always-on, not eager (ref #896)
On a 184-module fleet, DEFAULT_LIFECYCLE="eager" made every module with no manifest opinion idle-sleep-eligible, including core services (admin, gitea, nextcloud) that never opted into scale-to-zero. Flip the default to always-on: sleep is now a strict opt-in via lifecycle="eager" or "on-demand" declared explicitly in the manifest. scan()-derived manifests already relied on this same default (no code change needed there), so they inherit the safer behavior too. Co-Authored-By: Gerald KERMA <devel@cybermind.fr>
This commit is contained in:
parent
ed45608e0c
commit
356f8293a8
|
|
@ -96,8 +96,9 @@ packages/secubox-<module>/
|
|||
Every module known to `secubox-profiles` (its manifest under
|
||||
`/etc/secubox/modules.d/<id>.toml`) declares a `lifecycle`: `always-on`
|
||||
(never stopped — forced for any `protected` module regardless of what it
|
||||
declares), `eager` (starts at boot, may be idled), `on-demand` (off by
|
||||
default, woken on first access), or `manual` (operator-only, via the
|
||||
declares, and the **default** when a manifest omits the field, fleet-safe on
|
||||
a 184-module install), `eager` (starts at boot, may be idled), `on-demand`
|
||||
(off by default, woken on first access), or `manual` (operator-only, via the
|
||||
`/profiles/` panel). An optional `wake_class` (`normal`/`urgent`) tunes the
|
||||
idle threshold and the wake-splash budget. This is a **module-wide**
|
||||
guideline, not a `secubox-profiles`-internal detail: a module choosing
|
||||
|
|
|
|||
|
|
@ -62,9 +62,15 @@ parsed by the existing `secubox-profiles` manifest loader:
|
|||
|
||||
| Field | Values | Default | Meaning |
|
||||
|-------|--------|---------|---------|
|
||||
| `lifecycle` | `always-on` \| `eager` \| `on-demand` \| `manual` | `eager` | see below |
|
||||
| `lifecycle` | `always-on` \| `eager` \| `on-demand` \| `manual` | `always-on` | see below |
|
||||
| `wake_class` | `normal` \| `urgent` | `normal` | wake priority / idle grace |
|
||||
|
||||
> **Default changed post-pilot (#896 follow-up):** the original design below
|
||||
> defaulted `lifecycle` to `eager`, which on the real 184-module fleet made
|
||||
> every existing module — including ones with no manifest opinion at all —
|
||||
> idle-sleep eligible. Default `always-on` is fleet-safe; `eager`/`on-demand`
|
||||
> are explicit opt-ins.
|
||||
|
||||
- **`always-on`** — never sleeps; excluded from the sleeper. Core/gateway/auth/
|
||||
aggregator/WAF, and anything `protected`. (A `protected` module is treated as
|
||||
`always-on` regardless of its declared `lifecycle`.)
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ Panel webui : `/profiles/` (hybrid-dark, cf. `.claude/WEBUI-PANEL-GUIDELINES.md`
|
|||
priority = 40 # 0-100
|
||||
protected = false # true = jamais éteignable
|
||||
needs = ["auth"]
|
||||
lifecycle = "on-demand" # always-on|eager|on-demand|manual (défaut: eager)
|
||||
lifecycle = "on-demand" # always-on|eager|on-demand|manual (défaut: always-on)
|
||||
wake_class = "normal" # normal|urgent (défaut: normal)
|
||||
|
||||
Un manifeste corrigé à la main fait autorité : `scan` ne l'écrase pas sans `--force`.
|
||||
|
|
@ -111,11 +111,17 @@ Chaque module déclare un `lifecycle` dans son manifeste
|
|||
|
||||
| `lifecycle` | Démarre au boot ? | Rendormi si idle ? | Réveillé sur accès ? |
|
||||
|---|---|---|---|
|
||||
| `always-on` | oui, toujours | jamais | — (jamais éteint) |
|
||||
| `always-on` **(défaut)** | oui, toujours | jamais | — (jamais éteint) |
|
||||
| `eager` | oui | oui (si idle et sans réponse `/idle`) | oui (déjà up en général) |
|
||||
| `on-demand` | non | oui | oui — `sbxwaf` proxy vers le waker |
|
||||
| `manual` | non | non | non — opérateur uniquement (`/wake` panel) |
|
||||
|
||||
Le défaut est `always-on`, pas `eager` : sur une flotte de 184 modules, un
|
||||
manifeste sans opinion propre (donc la majorité) ne doit jamais devenir
|
||||
sleep-eligible par accident. Le sommeil est un **opt-in explicite** —
|
||||
`eager` ou `on-demand` doit être déclaré à la main dans le manifeste pour
|
||||
qu'un module participe au sleeper/waker.
|
||||
|
||||
Un module `protected = true` est **toujours** `always-on`, quoi que déclare
|
||||
son `lifecycle` (`effective_lifecycle`, `api/lifecycle.py`) — le cœur ne dort
|
||||
jamais. `wake_class = "urgent"` multiplie le seuil d'inactivité avant sommeil
|
||||
|
|
|
|||
|
|
@ -27,7 +27,13 @@ LIFECYCLES = ("always-on", "eager", "on-demand", "manual")
|
|||
WAKE_CLASSES = ("normal", "urgent")
|
||||
|
||||
DEFAULT_PRIORITY = 50
|
||||
DEFAULT_LIFECYCLE = "eager"
|
||||
# Fleet-safe default (#896 follow-up): a manifest that declares no lifecycle
|
||||
# must never become sleep-eligible by accident. On a 184-module fleet,
|
||||
# `eager` as the silent default made EVERY existing module idle-sleep
|
||||
# eligible, including core services with no manifest opinion of their own
|
||||
# (admin, gitea, nextcloud...). Sleep is now strictly opt-in: a module only
|
||||
# idles/wakes if its manifest explicitly declares `eager` or `on-demand`.
|
||||
DEFAULT_LIFECYCLE = "always-on"
|
||||
DEFAULT_WAKE_CLASS = "normal"
|
||||
|
||||
# Le noyau protégé : éteindre l'un de ceux-là retire à l'utilisateur le moyen
|
||||
|
|
|
|||
|
|
@ -1,3 +1,11 @@
|
|||
secubox-profiles (0.8.1-1~bookworm1) bookworm; urgency=medium
|
||||
|
||||
* default lifecycle is now always-on (fleet-safe; sleep is an explicit
|
||||
opt-in via eager/on-demand) — a module is never auto-slept unless its
|
||||
manifest declares it (ref #896).
|
||||
|
||||
-- Gerald KERMA <devel@cybermind.fr> Mon, 20 Jul 2026 18:09:33 +0200
|
||||
|
||||
secubox-profiles (0.8.0-1~bookworm1) bookworm; urgency=medium
|
||||
|
||||
* Scale-to-zero (ref #896): ships the lifecycle/wake_class manifest policy
|
||||
|
|
|
|||
|
|
@ -158,9 +158,14 @@ def _write(tmp_path, body):
|
|||
return p
|
||||
|
||||
|
||||
def test_lifecycle_defaults_eager_normal(tmp_path):
|
||||
def test_lifecycle_defaults_always_on_normal(tmp_path):
|
||||
# Fleet-safe default (#896 follow-up): a manifest that declares no
|
||||
# lifecycle must NEVER become sleep-eligible by accident — on a
|
||||
# 184-module fleet, "eager" as the silent default would eventually idle
|
||||
# out core services (admin, gitea, nextcloud...) that never opted in to
|
||||
# sleep. Sleep is an explicit opt-in via eager/on-demand.
|
||||
m = load_manifest(_write(tmp_path, ""))
|
||||
assert m.lifecycle == "eager" and m.wake_class == "normal"
|
||||
assert m.lifecycle == "always-on" and m.wake_class == "normal"
|
||||
|
||||
|
||||
def test_lifecycle_and_wake_class_parse(tmp_path):
|
||||
|
|
|
|||
|
|
@ -34,12 +34,13 @@ from api.manifest import PROTECTED_IDS # noqa: E402
|
|||
from api.state import load_profile, load_pins # noqa: E402
|
||||
|
||||
MANIFEST_LYRION = """
|
||||
id = "lyrion"
|
||||
category = "media"
|
||||
runtime = "native"
|
||||
exposure = "lan"
|
||||
units = ["secubox-lyrion.service"]
|
||||
priority = 30
|
||||
id = "lyrion"
|
||||
category = "media"
|
||||
runtime = "native"
|
||||
exposure = "lan"
|
||||
units = ["secubox-lyrion.service"]
|
||||
priority = 30
|
||||
lifecycle = "eager"
|
||||
"""
|
||||
|
||||
MANIFEST_AUTH = """
|
||||
|
|
@ -280,11 +281,14 @@ def test_set_pin_invalid_value_400(client):
|
|||
# lifecycle / wake_class / sleep_state (Task 10) — status payload extension.
|
||||
# `auth` is protected (PROTECTED_IDS) => effective_lifecycle forces
|
||||
# "always-on" regardless of its declared value => sleep_state "n/a". `lyrion`
|
||||
# declares no lifecycle/wake_class => defaults "eager"/"normal", observed on
|
||||
# (client fixture) => sleep_state "up". A third module declared "on-demand"/
|
||||
# "urgent" and left UNOBSERVED by the client fixture's _observe_all stub
|
||||
# proves the None -> False -> "asleep" coalescing (same philosophy as
|
||||
# observe.is_on, see its docstring).
|
||||
# declares lifecycle="eager" explicitly (the fleet-safe default is now
|
||||
# "always-on" — see test_manifest.test_lifecycle_defaults_always_on_normal —
|
||||
# so this test opts lyrion into "eager" on purpose to exercise the sleepable
|
||||
# path) with no wake_class => defaults "normal", observed on (client
|
||||
# fixture) => sleep_state "up". A third module declared "on-demand"/"urgent"
|
||||
# and left UNOBSERVED by the client fixture's _observe_all stub proves the
|
||||
# None -> False -> "asleep" coalescing (same philosophy as observe.is_on,
|
||||
# see its docstring).
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_status_includes_lifecycle_wake_class_and_sleep_state(client, root):
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user