v0.4.0
Added
- Carrier-down VLAN removal (FR-41). When an owned trunk's physical link stays carrier-down across a two-sample debounce (
BOOT_SETTLE_SECONDSapart) - at boot or on the rescan timer - and the box still has a healthy default route, dynavlan removes all owned VLANs on that trunk through the standard validated + health-gated + auto-reverting apply chain. A carrier-down port cannot pass frames, so this is a full teardown of the trunk's set, not a per-VLAN detection diff. Independent ofRESET_ON_BOOT: governed solely by new config keyREMOVE_ON_CARRIER_LOSS(defaulttrue), so aRESET_ON_BOOT=falsebox still self-heals a dead trunk. Rescan (previously strictly add-only) gains its first removal path; the settle sleep is incurred only when an owned trunk is actually carrier-down, so the steady-state fast path is unchanged. Narrows the prior preservation invariant: preservation now protects only genuine detection uncertainty (carrier-up, no tags) - carrier-down is authoritative kernel evidence and is no longer blanket-preserved.--dry-runpreviews a would-be teardown;--statusreports carrier state per owned trunk. - New pure helpers
carrier_removals(the debounce decision) andhave_routing(the routing pre-condition).have_routingrequires a lowest-metric default route, carrier on its egress iface, AND carrier on at least one physical NIC, so a dead trunk that doubles as the box's own uplink with no redundant route is preserved rather than attempted. - Public APT repository (GitHub Pages + reprepro): this re-tag republishes v0.4.0 to trigger the new
publish-aptCI job for the first time - no dynavlan script change,ver=stays 0.4.0.
Hardware validation (2026-08-04)
All 6 sub-cases PASS on the Protectli box, console-driven: carrier-pull prune (rescan), re-plug re-add, flap-shorter-than-settle preserve, own-uplink-with-no-redundant-route preserve, --boot prune, post-boot re-add. Full detail in dev/features/dynavlan-tests.md (L3-30).
Full changelog: https://github.com/pereljon/dynavlan/blob/main/CHANGELOG.md#040---2026-08-04