# Handoff 2026-09-10 — TP44 Teil B: CF-Estate-Sync als Nautobot-Job LIVE > **Ergebnis:** Der Cloudflare-Estate→Nautobot-Sync läuft als **täglicher Nautobot-Job** > (`CFEstateSync`, grouping `TP44 / Cloudflare`). Dry-Run + Commit-Run sauber: **4 CloudNetworks / > 14 CloudServices, alle CF-Endpoints lesbar, 0 Drift.** CF bleibt operativer Master, Nautobot ist > read-only Dokumentations-SSoT. **TP44 ist damit vollständig durch.** ## Governance (User, 2026-09-09) Periodische externe→Nautobot-Syncs leben als **Nautobot-Jobs** in `bf-infra/jobs/` und werden **in Nautobot geplant** — kein per-Task-Container, kein Cron. Künftige Syncs = weitere Jobs in `jobs/`. ## Was live ist - **Runtime:** `jobs/cf_estate_sync.py` (`CFEstateSync`, ORM). Pull der CF-API (urllib+Bearer), Upsert per `extra_config`-Match (`tunnel_id`/`script`/`id`/`service_id`), Verwaisung = `stale`-Flag statt Löschen. 403-tolerant (fehlender Scope kippt den Sync nicht; Verwaisung wird dann übersprungen). - **Registrierung:** `jobs/__init__.py` ruft `register_jobs(CFEstateSync)`; **Repo-Wurzel-`__init__.py`** macht `bf_infra` als reguläres Paket sichtbar. - **Token:** Nautobot-Secret `cf-api-token` (Provider `environment-variable`) → `CF_API_TOKEN` am `nautobot-worker`, verdrahtet im Anchor-`environment` von `docs/nas-vlan20/stacks/210-nautobot.yml` (gitignored). Wert = der breite `CF_API_TOKEN` aus `~/.secrets/credentials.env` (liest 14/14). - **Schedule:** `ScheduledJob "CF Estate Sync – taeglich"`, `interval=daily`, `start_time=2026-09-11T03:00:00Z` (= 05:00 Europe/Vienna, vor den Golden-Config-Läufen), `enabled=True`, `dryrun=False`. - **GitRepository:** `bf-infra` (`provided_contents=['extras.job']`) — war schon verdrahtet. ## Drei load-bearing Funde (sonst lädt/plant der Job nicht) 1. **Repo-Wurzel `__init__.py` ist Pflicht.** Nautobot 3.1.7 lädt Git-Repo-Jobs über `import_modules_privately()` → `pkgutil.walk_packages([GIT_ROOT])`. walk_packages entdeckt **nur** reguläre Pakete (mit `__init__.py`), **keine** Namespace-Packages. Ohne die Datei ist der geklonte `GIT_ROOT/bf_infra/` unsichtbar → `jobs/` wird nie importiert. Symptom: Git-Sync **SUCCESS**, aber Log „No jobs were registered on loading the `bf_infra.jobs` submodule" und **kein** Job-DB-Eintrag. bf-infra ist das **erste** Git-Repo mit `extras.job` — daher nie zuvor aufgetreten. Der `module_path`- Filter des Loaders importiert nur `bf_infra` + `bf_infra.jobs.*`; `nautobot/`, `routeros/` etc. bleiben unberührt. 2. **`register_jobs()` gehört ins Paket-`__init__`**, nicht nur ins Submodul (Doku bestätigt). 3. **`Meta.has_sensitive_variables = False`** nötig — Default `True` ⇒ Nautobot verweigert das Scheduling („Job may have sensitive input variables"). Der Job nimmt nur `dryrun` (Boolean); das Token kommt aus dem Secret, nicht aus einer Job-Variable. ## Zwei Daten-/Code-Funde beim Dry-Run - **VPC-Bug:** VPC-Directory-Services tragen ihren Identifier in **`service_id`**, nicht `id`. Der Job las `id` (=None) → per `.get(mk)==None` matchte er ein Fremdobjekt (App Launcher) und ließ den echten `terpene-db` auf stale laufen. Fix: VPC nutzt `service_id`; `_upsert` überspringt `mv=None` generell. - **Seed-Artefakt:** der geseedete CloudService `terpene-db` hatte `extra_config.service_id: null` (der echte `019fa3d5-…` stand beim Seed nur in den Hyperdrive-Einträgen als `vpc_service_id`). **Einmalig reconciled** (PATCH `service_id=019fa3d5-…`) → seither matcht der Sync sauber, 0 Drift. ## Drift-Korrektur (nicht stillschweigend) Die frühere Notiz „Repo-`CF_API_TOKEN` darf kein Workers-VPC → über den `cloudflare`-MCP-Credential" (TP44-Prosa + `handoff-20260902-…`) war **falsch**. Live getestet liest der `CF_API_TOKEN` aus `~/.secrets/credentials.env` Hyperdrive/VPC/Workers (alle 200). Der schmale Token ist `CF_DNS_API_TOKEN` (403). Der `cloudflare`-MCP ist der **remote OAuth-MCP** (kein API-Token) und für einen serverseitigen Job ohnehin nicht nutzbar. ## Nächster CF-Sync = neuer Job im selben Muster Modul in `jobs/` anlegen, Klasse importieren + `register_jobs(...)` im `jobs/__init__.py` ergänzen, `Meta.has_sensitive_variables=False` setzen, Git-Sync, enablen, Dry-Run, dann Schedule. Die Wurzel- `__init__.py` gilt schon.