feat: Fenris persistent TUI monitoring redesign
Implement the complete redesign per fenris-redesign spec: - Observation store: SQLite WAL mode, six entities, schema versioning - Collector: smartctl acquisition, sysfs identity, normalization - Projection: sustained regime rate, habit change, confidence states - Panes TUI: Textual keyboard-first layout with four normative regions - Status CLI: read-only composition with four service facts - Monitor helper: polkit-guarded toggle, collect, baseline ops - Legacy migration: idempotent single-transaction import - Hour classification, day aggregates, monitoring periods - Pruning, segmentation, drive health facts Cross-cutting acceptance sweep (CI-1 through CI-4): - 59 tests covering state matrix, TUI/CLI parity, prohibition set, required wording and six disclosures - Full suite: 289 tests, all green Issues #20, #32 closed.
This commit is contained in:
@@ -94,6 +94,26 @@ def cmd_import(args: argparse.Namespace) -> None:
|
||||
print("Legacy import: use fenris-import directly")
|
||||
|
||||
|
||||
def cmd_migrate(args: argparse.Namespace) -> None:
|
||||
"""Apply forward-only schema migrations (IN-5, IN-6).
|
||||
|
||||
Called by 'sudo make upgrade'. Raises on newer-schema store.
|
||||
"""
|
||||
from fenris.store import migrate_to_latest
|
||||
from pathlib import Path
|
||||
|
||||
store_path = Path("/var/lib/fenris/observations.db")
|
||||
if not store_path.exists():
|
||||
print("No observation store found — nothing to migrate.")
|
||||
return
|
||||
|
||||
steps = migrate_to_latest(store_path)
|
||||
if steps:
|
||||
print(f"Migration complete: {steps} step(s) applied.")
|
||||
else:
|
||||
print("Schema already current.")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="fenris",
|
||||
@@ -145,6 +165,10 @@ def main() -> None:
|
||||
import_parser.add_argument("path", help="Path to history.jsonl")
|
||||
import_parser.set_defaults(func=cmd_import)
|
||||
|
||||
# Migrate (IN-5, IN-6) — called by upgrade, not for human use
|
||||
migrate_parser = subparsers.add_parser("migrate", help=argparse.SUPPRESS)
|
||||
migrate_parser.set_defaults(func=cmd_migrate)
|
||||
|
||||
# Rejected commands
|
||||
for cmd in ["start", "stop", "run"]:
|
||||
reject_parser = subparsers.add_parser(cmd, help=argparse.SUPPRESS)
|
||||
@@ -172,6 +196,8 @@ def main() -> None:
|
||||
args.func(args)
|
||||
elif args.command == "import":
|
||||
cmd_import(args)
|
||||
elif args.command == "migrate":
|
||||
cmd_migrate(args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user