TP45 Schicht B1 Fix: Befehlspraefix nicht doppelt rendern

Erster Lauf nach dem Einbau ergab 144/145 -- und die eine Drift war mein Fehler,
nicht der des Geraets: backup_lines() strippte mit l[1:] nur das fuehrende '/',
nicht den ganzen Befehl. Das gerenderte Soll enthielt dadurch
`/ip dhcp-server lease ip dhcp-server lease add ...`.

Gefunden hat das die Compliance-Zahl selbst. Genau deshalb ist der NENNER die
Abnahme und nicht der Zaehler: 140 -> 145 beweist, dass die neue Rule auf allen
fuenf Geraeten auswertet; 145/145 beweist, dass das Soll stimmt.

Ausserdem: seed --force fuer einen bewussten Neuschnitt (der Guard gegen
versehentliches Re-Baseline bleibt).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-09-21 14:59:14 +02:00
parent 4d273117b6
commit b509a2325e

View File

@@ -46,8 +46,9 @@ Drift mehr (genau der Grund, warum `ingest-core-firewall.py` manuell bleibt).
python3 nautobot/tp45-leases.py check # Soll vs. Backup-Diff python3 nautobot/tp45-leases.py check # Soll vs. Backup-Diff
python3 nautobot/tp45-leases.py seed # Dry-Run python3 nautobot/tp45-leases.py seed # Dry-Run
python3 nautobot/tp45-leases.py seed --apply # B1 python3 nautobot/tp45-leases.py seed --apply # B1
python3 nautobot/tp45-leases.py move TP45-B2-v30 # Dry-Run, s. MOVES python3 nautobot/tp45-leases.py seed --force --apply # Neuschnitt, ueberschreibt
python3 nautobot/tp45-leases.py move TP45-B2-v30 --apply # B2 python3 nautobot/tp45-leases.py move # Dry-Run, s. MOVES
python3 nautobot/tp45-leases.py move --apply # B2
Token: docs/nas-vlan20/stacks/210-nautobot.yml (gitignored), NAUTOBOT_SUPERUSER_API_TOKEN. Token: docs/nas-vlan20/stacks/210-nautobot.yml (gitignored), NAUTOBOT_SUPERUSER_API_TOKEN.
Der Wert wird nie ausgegeben. Der Wert wird nie ausgegeben.
@@ -164,7 +165,14 @@ def backup_lines():
if dev == DEVICE: if dev == DEVICE:
cfg = r.get("backup_config") or "" cfg = r.get("backup_config") or ""
when = (r.get("backup_last_success_date") or "?")[:19] when = (r.get("backup_last_success_date") or "?")[:19]
return ([l[1:] for l in cfg.splitlines() if l.startswith(PREFIX)], when) # 🔴 len(PREFIX), NICHT l[1:]: das Argument-Teil ist ohne den ganzen Befehl
# zu speichern — genau wie firewall_filter, wo `add action=…` steht und
# das Template `/ip firewall filter ` davorsetzt. Ein blosses l[1:] liess
# `ip dhcp-server lease add …` stehen und erzeugte im gerenderten Soll
# den Befehl DOPPELT (`/ip dhcp-server lease ip dhcp-server lease add …`).
# Gefunden am 2026-09-21 an der ersten Compliance-Zahl nach dem Einbau:
# 144/145 statt 145/145.
return ([l[len(PREFIX):] for l in cfg.splitlines() if l.startswith(PREFIX)], when)
raise SystemExit("kein GC-Eintrag für " + DEVICE) raise SystemExit("kein GC-Eintrag für " + DEVICE)
@@ -217,11 +225,13 @@ def main():
return return
if cmd == "seed": if cmd == "seed":
if soll: if soll and "--force" not in argv:
raise SystemExit("Soll ist NICHT leer (%d Zeilen) — `seed` ist ein Re-Baseline. " raise SystemExit("Soll ist NICHT leer (%d Zeilen) — `seed` ist ein Re-Baseline. "
"Fuer eine geplante Aenderung `move` benutzen." % len(soll)) "Fuer eine geplante Aenderung `move` benutzen; fuer einen "
"bewussten Neuschnitt `--force`." % len(soll))
new, when = backup_lines() new, when = backup_lines()
print("== seed aus Backup %s (%d Zeilen) ==" % (when, len(new))) print("== seed aus Backup %s (%d Zeilen)%s =="
% (when, len(new), " [--force, ueberschreibt %d Zeilen]" % len(soll) if soll else ""))
else: else:
if not soll: if not soll:
raise SystemExit("Soll ist leer — erst `seed` laufen lassen (Schicht B1).") raise SystemExit("Soll ist leer — erst `seed` laufen lassen (Schicht B1).")