Skip to content

ADR-0096 — Lab Deployment Staging wizard Phase B-late (5-PR wave)

  • Status: Accepted
  • Date: 2026-05-25
  • Deciders: André Luiz Gallon
  • Surface: Dashboard /admin/lab-staging (multi-phase wizard) + new K8s CronJob slot
  • Wave: WIZ-B1 + WIZ-B2 + WIZ-B3 + WIZ-B4 + WIZ-B5 (5 sub-PRs)
  • Strategy memo: discuss_lab_deployment_staging_2026_05_09.md
  • Related ADRs: ADR-0095 (Lab Staging wizard wave) · ADR-0011 (Topology axes) · ADR-0017 (Backup/DR) · ADR-0088 (ml-cortex sidecar)
  • Patent claim: none (UX/UI + ops feature)

Context

ADR-0095 shipped the 6-PR wizard wave with 8 explicit Phase B-late items in its carry-forward list. ADR-0096 closes those 8 items via 5 thematic sub-PRs (WIZ-B1..B5) while honoring two scope decisions:

  • react-flow swap stays deferred — Q4 LOCKED in ADR-0095 says HTML5 native drag-drop is sufficient for MVP; adding a +50KB dep at Phase B-late is wrong scope (operator can request the swap in v5.x+ once UX has stabilised)
  • Heavy help (videos + AI chat) is shipped as scaffold ONLY — actual video recordings + AI backend land later; the API surface lets the rest of the wizard build against a stable contract

Decisions

7 locked decisions for the Phase B-late wave (BL1-BL7):

  1. BL1 — Scan engine adapter pattern: Disabled / Fake / SSHReal triple (scan-engine.ts). SSHRealScanner orchestrates 5 recipes per switch (interface-prepare → discovery-protocols-enable → show-mac-table → lldp-neighbors → snmp-walk-ifoper). interface-prepare is a HARD prerequisite (host fails); discovery-protocols-enable is SOFT-fail (falls back to MAC + ARP). Pluggable SshExecutor interface lets the Ansible-orchestration sidecar plug in without code changes.

  2. BL2 — OUI scraper pure-function + weekly CronJob: oui-scraper.ts parses IEEE oui.csv stdlib-only (no csv-parse dep). Sanity-gate maxShrinkRatio=0.5 guards against broken upstream feeds (last-known-good preserved). Weekly CronJob Sun 03:30 (after Saturday backup window); air-gap mode bypasses upstream fetch + logs skip.

  3. BL3 — Phase 4-7 panels are presentation layers: All 4 panels (capacity / prod-cabling / auto-config / stack-deploy) are pure React wrappers over already-tested lib functions (computeCapacity, cabling.planCabling, connectivity-engine.aggregateVerdicts, deploy-plan.planDeploy). No new business logic — keeps test coverage concentrated in the libs.

  4. BL4 — YAML download via GET + Content-Disposition: Browser handles the file save via ?attachment=1 query flag rather than client-side Blob + URL.createObjectURL. Simpler client; works in air-gap. Optional git commit via POST + LAB_STAGING_GIT_BRIDGE_URL env (503 + hint when missing — never silent failure).

  5. BL5 — react-flow swap STAYS DEFERRED: Q4 LOCKED in ADR-0095. HTML5 drag-drop UX shipped in WIZ-4 is sufficient. ADR-0096 logs this in carry-forward — operator may request when v5.x+ UX stabilises.

  6. BL6 — ML affinity engine adapter pattern matches scan engine: Disabled / Fake / Onnx triple (ml-affinity.ts). OnnxMLAffinity falls back to heuristic suggester silently with confidence=0 when bridge unreachable — ML failure NEVER blocks the wizard (mirrors validator-art memo's locked behavior). pickMLEngine env-driven; LAB_STAGING_ML_AFFINITY=fake|onnx + LAB_STAGING_ML_AFFINITY_URL.

  7. BL7 — Heavy help shipped as scaffold ONLY (Q1 v5.x+): getVideoTutorial returns null (empty catalog) until videos recorded; askAIChat throws ErrAIChatNotWired. Feature flags via getHeavyHelpFlags() default OFF + degrade gracefully to v5.0 Medium (CLI snippets).

Consequences

Positive

  • Phase 2 connectivity engine is wired end-to-end: Operator can run scan against synthetic equipment today (Fake), against real equipment once the SSH bridge sidecar ships (SSHReal) — no wizard code changes needed at that point.
  • OUI catalogue stays fresh without operator intervention: Weekly CronJob + IEEE upstream; air-gap installations get release-embedded fallback.
  • All 7 phase panels are visible + interactive: Operator walks the wizard from Phase 1 to Phase 7; panels render verdict/status from libs; gating buttons enforce ADR-0095's locked decisions (T0 hard-floor; engineWired flags).
  • YAML export round-trips deployments through git: Per Q5 LOCKED both paths; operator commits to bench repo; reproducible.
  • ML affinity engine has a stable contract for v5.1 swap-in; OnnxMLAffinity fallback prevents ML failures from blocking the wizard.
  • 271 Vitest tests (from 205 at end of ADR-0095) — +66 tests covering scan engine + OUI scraper + ML affinity + E2E + Heavy help stubs.

Negative

  • ~~SSH bridge sidecar not yet shipped~~ ✅ Closed by post-WIZ-B5: pkg/ssh-bridge-sidecar/ Go service + k8s/oobi/73-ssh-bridge-sidecar.yaml (Deployment + Service + NetworkPolicy restricting ingress to Dashboard). Production operator sets LAB_STAGING_SCANNER=ssh + LAB_STAGING_SSH_BRIDGE_URL=http://ssh-bridge-sidecar.oobi-fabric.svc.cluster.local:8089.
  • ~~CLONER OUI CronJob image not yet built~~ ✅ Closed by post-WIZ-B5: build/cloner-fn-oui-scrape/ (Python stdlib script + Dockerfile + GHCR CI workflow); k8s/oobi/72-cloner-fn-oui-scrape.yaml now points at ghcr.io/nollagluiz/cloner-oui-scrape:v0.1.0. Built + pushed by .github/workflows/cloner-oui-image.yml.
  • react-flow still on the carry-forward list: Operator UX is functional but lacks zoom/pan/minimap.
  • ML model not trained: FakeMLAffinity confidence is heuristic (based on input completeness, not real model output). Real training data pipeline lands when validator-ml-cortex Phase F retraining produces a lab-affinity-v0.1 ONNX export.
  • Heavy help videos not recorded: VIDEO_CATALOG is empty; flag degrades to v5.0 Medium silently.

Alternatives considered

  • Ship react-flow swap in this wave: Rejected. Q4 LOCKED in ADR-0095 + +50KB dep mid-Phase-B-late is wrong scope. HTML5 drag-drop is functional.
  • Embed real ML model training in WIZ-B5: Rejected. Training pipeline requires historical run data the bench doesn't have yet; FakeMLAffinity + OnnxMLAffinity stub is the right v5.1 shape.
  • Record Heavy help videos now: Rejected per Q10 LOCKED (v5.x+ scope). Scaffolding the API surface is sufficient to unblock UI work.
  • Wire SSH executor implementation inline: Rejected. The orchestration sidecar is a separate concern (cross-ref discuss_ssh_telnet_orchestration_plane_2026_05_09); pluggable SshExecutor interface keeps the wizard decoupled.

Compliance / governance

  • CLAUDE.md invariant preserved: Dashboard-only operator interface — all 4 Phase B-late additions surface through /admin/lab-staging + API routes; no kubectl exec.
  • Audit trail extended: Phase 2 scan results land in lab_deployments_history with reason='oobi-scan' per Q6 LOCKED.
  • Air-gap support: OUI scraper respects LAB_STAGING_AIRGAP=true; YAML export works without any external bridge; Heavy help scaffold degrades gracefully when flags off.

References

  • Strategic memo: discuss_lab_deployment_staging_2026_05_09
  • ADR-0095 (wizard wave): docs/ADR/0095-lab-deployment-staging-wizard.md
  • Recovery memo: project_lab_staging_phase_b_late_complete_2026_05_25.md
  • Companion sidecar memo: discuss_ssh_telnet_orchestration_plane_2026_05_09
  • ml-cortex base: ADR-0088
  • Topology axes: ADR-0011