Diff

GET /api/bng/snapshots/{id}/diff                       admin_only
GET /api/bng/snapshots/diff?from=<a>&to=<b>            admin_only

The first captures live state without storing it and compares it against a stored
snapshot — "what has changed since". The second compares two stored snapshots —
"what changed between these two points".

{
  "diff": {
    "qt_settings": [
      { "path": "bng.audit.retention.max_size_mb", "current": 100, "snapshot": 250 }
    ],
    "controller_config": [
      { "path": "plans.Silver-20M.dn_cir_bps", "current": 20000000, "snapshot": 25000000 }
    ],
    "vpp_startup_conf": {
      "changed": true,
      "unified_diff": "--- currentn+++ snapshotn@@ …",
      "current": "…", "snapshot": "…"
    },
    "grub_cmdline":     { "changed": false },
    "sysctl_hugepages": { "changed": false }
  },
  "requires_reboot": true,
  "reboot_reasons": ["grub_cmdline_changed"]
}

Structured domains are flattened to dot-paths and compared leaf by leaf. Lists
compare whole rather than element-wise
— a DNS server list with one entry
changed reports as one changed path, not as an insertion and a deletion. A value
present on one side and absent on the other appears as null, so additions,
removals, and modifications are structurally identical and a client renders them
uniformly. Text domains report either {"changed": false} or a unified diff.

requires_reboot is computed, not guessed, and reboot_reasons can only ever
contain grub_cmdline_changed or hugepages_count_changed. Note that a changed
VPP startup configuration does not set it — that is an in-band service
restart, not a reboot.