Skip to content

Fix three verified doc-vs-code mismatches in a single docs-only chang... - #153

Merged
colonelpanik merged 1 commit into
mainfrom
overseer/fix-three-verified-doc-vs-code-mismatches-in-a-single-docs-o
Aug 9, 2026
Merged

Fix three verified doc-vs-code mismatches in a single docs-only chang...#153
colonelpanik merged 1 commit into
mainfrom
overseer/fix-three-verified-doc-vs-code-mismatches-in-a-single-docs-o

Conversation

@colonelpanik

Copy link
Copy Markdown
Owner

Goal

Fix three verified doc-vs-code mismatches in a single docs-only change: (1) docs/self-upgrade-from-peer.md:90 says auto_upgrade.interval where the real YAML key is interval_minutes (internal/daemon/config.go:242, already correctly named in docs/configuration.md:458); (2) docs/rest-api.md:322-323 documents the ExecContainer route as POST /api/v1/containers/{name}/exec where the actually-registered route takes no path parameter — POST /api/v1/containers/exec (internal/restapi/coverage.go:33, matching the table entry already at rest-api.md:280); (3) docs/networking.md:41 claims 'BGP peering between hosts distributes MAC/IP mappings' for VXLAN overlays, but internal/network/frr.go's BGP/EVPN renderer has zero non-test callers — the live mechanism is the static unicast FDB in internal/network/fdb.go, wired from internal/network/provision.go.

Plan

Plan: fix three doc-vs-code mismatches (docs-only)

Scope

One docs-only commit touching exactly three files. No .go file is modified,
no test is added, no behaviour changes.

# File Line(s) today Defect
1 docs/self-upgrade-from-peer.md 90–91 Names a config key that does not exist (auto_upgrade.interval)
2 docs/rest-api.md 322–323 Documents a REST route that is not registered (/api/v1/containers/{name}/exec)
3 docs/networking.md 41 Claims a BGP control plane distributes MAC/IP mappings; no BGP code runs

Evidence (re-verified in this worktree, not taken on trust)

(1) auto_upgrade.intervalauto_upgrade.interval_minutes

internal/daemon/config.go:241-243:

type AutoUpgradeConfig struct {
	FromPeer        *bool `yaml:"from_peer,omitempty"`
	IntervalMinutes int   `yaml:"interval_minutes,omitempty"` // 0 = default (5)
}

internal/daemon/config.go:250-255Interval() returns 5 * time.Minute
when IntervalMinutes <= 0, else IntervalMinutes minutes. So the unit is
minutes as an integer, not a Go duration string; interval: 5m in YAML
would be silently ignored (unknown key) and misleading about the type.

docs/configuration.md:458 already has it right:
interval_minutes: 5 # how often to check peers for a newer build; 0 → 5.
docs/self-upgrade-from-peer.md:90 is the only wrong mention in the tree
(grep -rn "auto_upgrade" docs/ returns exactly three hits: the two in
self-upgrade-from-peer.md at :90 and :119, and configuration.md:454).

(2) POST /api/v1/containers/{name}/execPOST /api/v1/containers/exec

internal/restapi/coverage.go:33:

s.mux.HandleFunc("/api/v1/containers/exec", s.wrap(s.handleContainerExec))

That is a literal net/http pattern with no {...} wildcard and no
trailing slash, so it matches only that exact path. There is no
/api/v1/containers/ subtree handler and no / catch-all in
internal/restapi/ — the complete set of container registrations is
parity.go:41 (/api/v1/containers) plus coverage.go:29-34
(create|start|stop|delete|exec|pull). A request to
POST /api/v1/containers/foo/exec therefore 404s.

The container name travels in the body: ExecContainerRequest is
{host_name, name, argv} (proto/litevirt/v1/service.proto:924-928), and
handleContainerExec (internal/restapi/coverage.go:223-234) decodes the whole
request from the body via protoFromJSON — it never reads a path segment. This
matches the sibling routes (start/stop/delete are all documented as
"name in body").

internal/restapi/coverage_test.go:29 pins the exact path today
(GET /api/v1/containers/exec must return 405, not 404), which is the strongest
existing evidence that the registered path is the one without a parameter.

The correct form is already in the route table at docs/rest-api.md:280
(| /api/v1/containers/exec | POST | ExecContainer |); only the prose
parenthetical in the "Still gRPC-only" section contradicts it.
grep -rn "containers/" docs/ README.md shows :323 is the sole wrong
occurrence.

(3) "BGP peering between hosts distributes MAC/IP mappings" → static unicast FDB

internal/network/frr.go exports RenderFRRConfig and WriteFRRConfig.
Grepping the whole tree for frr/FRR outside _test.go returns hits in
internal/network/frr.go only; the sole test file is
internal/network/frr_test.go. The BGP/EVPN renderer has zero production
callers — nothing writes /etc/frr/frr.conf, nothing reloads FRR, and a
litevirt host never needs FRR installed.

What actually runs:

  • VTEP membershipnetwork.Provision (internal/network/provision.go:151-166)
    does EnsureVXLANUpsertVTEPSyncFloodEntries, and EnsureIRB when
    subnet is set. UpsertVTEP (provision.go:392) writes the host's VTEP into
    the network_vteps table, which replicates over corrosion/CRDT;
    GetVTEPs (provision.go:407) reads the member list back.

  • BUM / flood, pull side (universal)SyncFloodEntries
    (provision.go:441-455) calls network.FloodEntry per peer, which is
    bridge fdb add 00:00:00:00:00:00 dev vxlan<VNI> dst <peer>
    (internal/network/fdb.go:35-42). Head-end (ingress) replication. This sits
    inside Provision, so every provisioning path performs it.

  • BUM / flood, push side (NOT universal)notifyVTEPPeers → peer
    SyncVTEP RPC → network.FloodEntry
    (internal/grpcapi/networks.go:367-384, 501-520) is what makes existing
    peers add a newcomer's entry. It is called from only two of the seven
    provisioning entry points. Full census of SafeProvision /
    ProvisionForVM callers:

    Entry point Site Pushes SyncVTEP?
    ProvisionNetwork RPC networks.go:357 yes (:368)
    CreateVM vm.go:510 via provisionNetworkForVMvm.go:2160 yes (vm.go:516)
    CreateNetwork RPC networks.go:73provisionAndPersistNetwork:252 no
    Stack deploy stacks.go:1506provisionAndPersistNetwork:252 no
    NIC hotplug hotplug_nic.go:313 no
    Daemon startup reconcile daemon.go:1784 (reconcileNetworks) no
    Health reconciler internal/health/reconciler.go:1041 no

    So a host joining a VXLAN via lv network create, a stack deploy, a NIC
    hotplug, or a daemon restart programs its own side and stays invisible to
    existing peers' flood lists until each of those peers re-provisions. Because
    reconcileNetworks runs at startup, a peer restart does re-pull the table
    and converge it.

  • The two layers behave differently, and the prose must not conflate them.
    Replicated VTEP rows are tombstoned: removing a host runs
    UPDATE network_vteps SET deleted_at = ? WHERE host_name = ?
    (internal/corrosion/hosts.go:368) and GetVTEPs filters deleted_at IS NULL, so the database view of membership shrinks correctly. Kernel flood
    entries
    are the add-only layer: DeleteFloodEntry (fdb.go:45) has no
    production caller (definition + fdb_test.go + network_extra_test.go), and
    SyncFloodEntries only ever adds — it never diffs the kernel against the
    table. Net effect: the departed host disappears from the DB while every
    surviving host keeps a flood entry aimed at its address.
    Two further constraints on how this can be phrased:

    • Kernel entries are not written only during a provisioning pass. The
      SyncVTEP RPC (internal/grpcapi/networks.go:374-383) calls
      network.FloodEntry directly, so a peer's push adds an entry to this host
      between local provisioning passes.
    • A host's own entries do vanish wholesale with its VXLAN device on
      DeprovisionDeprovisionVXLAN (internal/network/vxlan.go:86-103) —
      so "nothing ever removes a flood entry" is too strong; what is true is that
      no code removes an individual entry.
  • Unicast MAC→VTEP — programmed explicitly, never learned from a routing
    protocol. broadcastFDBUpdate (internal/grpcapi/fdb.go:52-80) applies the
    change locally and fans it out to every peer over peer-mTLS gRPC
    (UpdateFDB, internal/grpcapi/networks.go:428-443requirePeerCert,
    then network.AddFDBEntry / DeleteFDBEntry). Three triggers, all VM-scoped:
    IP discovery (internal/grpcapi/ip_scanner.go:98), migration
    (internal/grpcapi/migrate.go:639updateFDBForMigration), and delete
    (CleanupFDBForVM, ip_scanner.go:154-171).

  • Anycast gatewayEnsureIRB (internal/network/irb.go:46-70) puts the
    same address on br-vni<VNI> on every host. That address is
    gatewayForSubnet (irb.go:11-38) = network address + 1, i.e. the first
    usable address, not a .110.0.1.128/25 yields 10.0.1.129. The
    function's own doc comment (irb.go:9-10) says "firstHostIP" and its example
    happens to be a /24, which is how a .1 claim gets written; the code is
    copy(gw, ipNet.IP.To4()); ipInc(gw).

The only other BGP mention in the docs is docs/federation.md:103, which
already says fabric-layer FRR/BGP/ECMP is out of scope — so the corrected
networking.md text is consistent with what the rest of the docs claim.


The edits

Edit 1 — docs/self-upgrade-from-peer.md

Before (lines 90–91):

- Config-gated: `auto_upgrade.from_peer` (default **on**); `auto_upgrade.interval`
  (default 5m). Set off to require manual `lv host upgrade`.

After:

- Config-gated: `auto_upgrade.from_peer` (default **on**);
  `auto_upgrade.interval_minutes` (integer minutes, default **5**). Set off to
  require manual `lv host upgrade`.

"integer minutes" is the part that earns its keystrokes: the old text's
(default 5m) invited an operator to write interval: 5m, which is wrong
twice over.

Edit 2 — docs/rest-api.md

Before (lines 321–323):

- `StreamEvents`, `GetVMLogs`, `ConsoleVM`, `ProxyVNC` — bidirectional or
  WebSocket-shaped. (`ExecContainer` IS wired in REST — `POST
  /api/v1/containers/{name}/exec`.)

After:

- `StreamEvents`, `GetVMLogs`, `ConsoleVM`, `ProxyVNC` — bidirectional or
  WebSocket-shaped. (`ExecContainer` IS wired in REST —
  `POST /api/v1/containers/exec`, with the container name in the body.)

The reflow keeps the whole path inside one inline-code span on one line, which
the old two-line split did not.

Edit 3 — docs/networking.md

Before (line 41, a single long line):

litevirt manages VTEP configuration and FDB entries. BGP peering between hosts distributes MAC/IP mappings.

After (wrapped ~80 cols, matching the isolated-network paragraph at lines 46–48
in the same file):

litevirt manages VTEP and forwarding-database (FDB) state itself — there is no
BGP or EVPN control plane, and no routing daemon to install. When a host
provisions the network it records its VTEP address in the replicated cluster
database, then reads that table back and installs an all-zeros
`00:00:00:00:00:00` flood entry for each peer VTEP already listed, so BUM
traffic is head-end replicated to the peers it knows about; a peer that
provisions later can also push its VTEP straight to this host, which adds the
entry on the spot. Those kernel flood entries are only ever added — an
individual entry is never withdrawn when a host leaves the network (they go away
only with the local VXLAN device), and nothing re-derives the set from the
database outside a provisioning pass, so a newly-joined host may stay absent
from an existing peer's entries until that peer next provisions, which a daemon
restart does. Unicast MAC→VTEP entries are programmed explicitly rather than
learned: when a VM's address is discovered, and again when that VM migrates or
is deleted, its host fans a `bridge fdb` add/delete out to every peer over the
cluster's mTLS gRPC, so remote hosts point the MAC at whichever host now owns
it. Setting `subnet:` also gives every host the same anycast gateway — the first
usable address in the subnet — on the VNI bridge, so a VM's default route is
host-local.

Deliberate wording choices, each traceable to code:

  • "VM's address", not "workload's" — every unicast-FDB trigger is VM-scoped
    (scanVMs, updateFDBForMigration, CleanupFDBForVM). Containers on a VXLAN
    network get flood entries through provisioning but no unicast FDB path, so
    saying "workload" would be a new, false claim.
  • "there is no BGP or EVPN control plane" stated explicitly. Without it, a
    reader who remembers the old sentence has no signal that the claim was
    withdrawn, and the sentence is a standing invitation to re-add.
  • "may not appear in an existing peer's entries until that peer next
    provisions"
    , rather than the earlier draft's "peers that provision later
    push their own entry out over the cluster's mTLS gRPC". Second review catch,
    also correct. notifyVTEPPeers fires from only two of seven provisioning
    entry points (census table above), and — worse than the finding stated — the
    two that do push are ProvisionNetwork and CreateVM, while the ordinary
    operator path for adding a network, lv network createCreateNetwork
    provisionAndPersistNetwork, does not. So the reviewer's first option
    ("narrow this claim to the specific gRPC path") would itself have read as
    "creating a network propagates your VTEP", which is false for the command an
    operator would actually run. I took the second option: describe the pull side,
    which lives inside Provision and therefore holds on every path, and hedge
    the reverse direction. The doc deliberately does not enumerate which call
    sites push — a list of internal call sites in an operator doc is precisely the
    kind of claim that rots into the next one of these bugs. "which a daemon
    restart does" is the operator-actionable part, and is true via
    reconcileNetworks.
  • "Those kernel flood entries are only ever added", with an explicit noun,
    rather than the earlier draft's "That state is written once per provisioning
    pass and is add-only". Third review catch, correct on both halves. "That
    state" pointed back at a sentence covering the replicated VTEP rows and the
    kernel entries, and the two behave oppositely — the rows are tombstoned
    (hosts.go:368), only the kernel entries are add-only. And "written once per
    provisioning pass" was false in the other direction: SyncVTEP
    (networks.go:374-383) calls FloodEntry outside any provisioning pass, so
    entries can arrive between passes. The replacement names the noun ("those
    kernel flood entries"), keeps the push path in the preceding clause, and
    splits the two true statements: no code removes an individual entry, and
    nothing re-derives the set from the database outside a provisioning pass.
    "They go away only with the local VXLAN device" preserves the one real
    removal path (DeprovisionDeprovisionVXLAN,
    internal/network/vxlan.go:86-103).
    If a reviewer reads this whole caveat as scope creep — documenting a gap in a
    commit that was only meant to delete a false claim — cutting that sentence is
    safe and nothing else in the paragraph depends on it. I recommend keeping it:
    an operator debugging BUM traffic aimed at a decommissioned host has no other
    way to find out.
  • "the first usable address in the subnet", not ".1". Third review catch,
    minor and correct. gatewayForSubnet (irb.go:11-38) is network address + 1,
    so it is .1 only for subnets whose network address ends in .0;
    10.0.1.128/25 gives 10.0.1.129. Worth noting the same mistake is already
    latent in the code's own doc comment (irb.go:9-10), whose example is a /24
    — that comment is where I took .1 from, which is a small lesson about
    treating a neighbouring comment as a source. Not fixing that comment here:
    docs-only.
  • No mention of neigh_suppress (set by EnsureIRB, irb.go:63). It is
    real, but describing ARP suppression correctly means describing what populates
    the neighbour table, which is exactly the EVPN machinery that does not exist
    here. Documenting it is a separate question from fixing a false BGP claim —
    see "Deliberately not in scope".

Verification

The docs guard cannot catch any of these three (see "What a reviewer needs to
know"), so grep-plus-tests is necessary but not sufficient. The plan is
verify-by-reading first, tests second.

A. Re-read each corrected sentence against the cited code. For each of the
three edits, open the doc line and the code line side by side and confirm the
sentence is true of that code:

  1. docs/self-upgrade-from-peer.md new line ↔ internal/daemon/config.go:242
    (tag is interval_minutes) and :250-255 (default 5, unit minutes).

  2. docs/rest-api.md new line ↔ internal/restapi/coverage.go:33 (literal
    path) and :223-234 (body-decoded, no path segment read).

  3. docs/networking.md new paragraph ↔ internal/network/provision.go:151-166
    and :441-455, internal/network/fdb.go:15-49,
    internal/grpcapi/fdb.go:52-80, internal/grpcapi/networks.go:428-443,
    internal/network/irb.go:46-70. Additionally, re-run the provisioning-path
    census before accepting the flood-entry sentences — the claim is about which
    paths do what, so it is only as good as the call-site list:

    grep -rn 'SafeProvision(\|ProvisionForVM(' --include='*.go' . | grep -v _test
    grep -rn 'notifyVTEPPeers' --include='*.go' . | grep -v _test

    The second list must remain a strict subset of the first, and the doc must
    not claim propagation that holds only on that subset. Two more checks on this
    paragraph specifically, both of which caught real errors in earlier drafts:
    read every sentence asking which layer it is about (replicated
    network_vteps rows, tombstoned; or kernel flood entries, add-only) and
    reject any that spans both; and check the gateway sentence against
    gatewayForSubnet (irb.go:11-38) with a non-/24 example such as
    10.0.1.128/2510.0.1.129, not against the /24 in the function's own
    doc comment.

B. Exhaustiveness greps. The old claim must be gone:

grep -rn 'auto_upgrade\.interval\b' docs/ README.md          # expect: none
grep -rn 'containers/{name}/exec' docs/ README.md            # expect: none
grep -rn 'BGP peering between hosts' docs/ README.md         # expect: none

And the replacements must be present — note the third is not an
absence check. A review round caught the original form of this step
(grep -rni 'bgp\|evpn' docs/networking.md # expect: none), which could never
have passed: the new sentence says "there is no BGP or EVPN control plane", so
the words survive on purpose. What must be checked is that every surviving
occurrence is a negation, not a claim — which is an eyeball check on one line,
not a grep exit status:

grep -rn 'interval_minutes' docs/                   # configuration.md:458 + self-upgrade-from-peer.md
grep -rn 'api/v1/containers/exec' docs/rest-api.md  # :280 table + the prose line, same path
grep -rni 'bgp\|evpn' docs/networking.md            # expect exactly 1 line, the "there is no …" negation
grep -rni 'bgp\|evpn' docs/ README.md               # the above + docs/federation.md:103 only

docs/federation.md:103 is correct as it stands (fabric-layer FRR/BGP/ECMP
declared out of scope) and must stay.

C. Guards. Baseline was captured before any edit and passes:

$ go test ./cmd/litevirt/ -run 'TestDocsReferenceReal|TestDocsDocumentEvery|TestValidateInvocation|TestResolveInvocation|TestCheckIdentifier|TestExtractInvocations'
ok  	github.com/litevirt/litevirt/cmd/litevirt	0.206s

Re-run the same command after the edit, then make ci-guards for the full set
per CLAUDE.md. go build ./... && go vet ./... && go test ./... are unaffected
by a docs-only change but are cheap and are what CLAUDE.md asks for.

The specific guard interactions worth predicting rather than discovering:

  • TestDocsDocumentEveryConfigKey requires every YAML tag reachable from the
    config struct to appear somewhere in README.md or docs/*.md. Removing
    the bogus auto_upgrade.interval cannot break it (it was never a real key),
    and interval_minutes keeps its docs/configuration.md:458 mention plus
    gains a second one. No undocumentedConfigKeys entry is needed.
  • TestDocsReferenceRealCLICommands extracts lv/litevirt invocations from
    fenced blocks and, outside fences, only from inline-code spans
    (extractInvocations, docs_triangulation_test.go:370-393). None of the new
    inline spans (auto_upgrade.interval_minutes, POST /api/v1/containers/exec,
    bridge fdb, 00:00:00:00:00:00, subnet:) begin with lv or litevirt,
    so no new invocation is extracted. The retained `lv host upgrade` span
    resolves today and is unchanged. Prose starting with the bare word "litevirt"
    is not scanned — which is why networking.md:41 passes today.
  • TestDocsReferenceRealMetrics only inspects inline litevirt_* identifiers;
    none are added.

D. Mutation-check the greps, per CLAUDE.md's "a passing test proves nothing
until you have seen it fail".
These are greps, not tests, so the cheap version
is: run the three "expect: none" patterns before the edit and confirm each
returns exactly the one line being fixed — self-upgrade-from-peer.md:90,
rest-api.md:323, networking.md:41. If a pattern returns nothing before the
edit, the pattern is wrong, not the tree. This step is what would have caught
the bad bgp\|evpn check in the first draft: that one does return line 41
before the edit, but it also returns the replacement after it, so an
absence-based expectation was never satisfiable. A pattern used as a
regression check has to be one the new text cannot match — hence
BGP peering between hosts, a phrase that exists only in the sentence being
deleted.


What a reviewer needs to know about this codebase

  • The docs guard is a membership check, not a truth check. CLAUDE.md's
    "Docs are guarded in both directions" is accurate but narrow:
    cmd/litevirt/docs_triangulation_test.go checks that documented CLI commands
    resolve in the cobra tree, that litevirt_* identifiers exist as string
    literals, and that every command and every config YAML tag is mentioned
    somewhere. Its own comment says so — "Membership, not prose quality: a key
    that appears anywhere in the docs passes" (:192-193). All three defects here
    live in the gap: a fabricated key name (the guard checks real→doc, never
    doc→real, for config keys), a REST path (no guard walks
    s.mux.HandleFunc registrations against the docs at all), and a mechanism
    claim
    (no guard could). So "ci-guards is green" is not evidence for this
    change; the side-by-side re-read in step A is.
  • TODO.md is not in this repository. The task description cites "TODO.md
    §C" as flagging frr.go as dead code; find . -iname 'TODO*' returns
    nothing, and docs_triangulation_test.go:23-24 explains why — "the private
    Plan.md/TODO/MEMORY aren't in this repo, so the docs ARE the claim surface
    here." A reviewer should not go looking for that file. The frr.go-is-unwired
    claim is re-verified independently above by grep.
  • frr.go stays exactly as it is. Not deleted, not wired, not annotated.
    Deleting ~95 lines of working-but-dead code is defensible but is a code
    change, and the constraint is docs-only; wiring it up is a large networking
    feature. This change only stops the docs from promising it.
  • The rest-api.md route table was already correct. Line 280 has
    /api/v1/containers/exec. Fix 2 makes the prose agree with a table in the
    same file, so there is no "which one is right" judgement call for the
    reviewer — the file already contradicted itself.
  • VXLAN unicast FDB is push-based, not learned. The mental model that makes
    the new networking.md paragraph read correctly: corrosion/CRDT carries VTEP
    membership
    (which hosts are on the network), and peer-mTLS gRPC RPCs
    (SyncVTEP, UpdateFDB) carry the per-MAC entries. Two different transports
    for two different kinds of state. Neither is BGP.
  • The two layers converge differently, which is the subtlety a reviewer should
    check hardest — and the one the prose has got wrong most often.
    The CRDT
    membership view is tombstoned and self-healing; the kernel FDB is written by
    one-shot imperative commands with no reconciler behind them. Any sentence
    whose subject is "this state" or "that state" is therefore suspect: it almost
    certainly spans both layers and is false of one of them. Name the layer.
    Unicast entries get an explicit delete on VM delete/migrate
    (CleanupFDBForVM from vm.go:1733, updateFDBForMigration from
    migrate.go:639), but flood entries get neither a delete (DeleteFloodEntry
    is dead code, exactly like frr.go) nor a guaranteed announce (only 2 of 7
    provisioning paths call notifyVTEPPeers). So the honest summary is
    "eventually consistent membership, add-only and announce-on-some-paths
    forwarding state, re-pulled on daemon restart", and the replacement prose is
    worded to say exactly that. Both gaps are real code questions; neither is
    this commit's.
  • Three review rounds, four findings, all on the same paragraph, are the
    strongest argument in this plan.
    Round 1: "current member list" implied a
    withdrawal that DeleteFloodEntry's dead status disproves. Round 2: "peers
    that provision later push their own entry" implied a universal announce that
    the call-site census disproves. Round 3: "that state … written once per
    provisioning pass and is add-only" fused two layers that behave oppositely,
    and ".1 gateway" generalised a /24 example. Every one was in my
    replacement prose, not the original doc, and the guard suite was green at
    every step. Two lessons a reviewer should carry into this file: (a) read the
    paragraph against the census table and the two-layer bullet, not against
    plausibility — it is plausible-sounding wrong sentences that keep surviving
    here; (b) three of the four errors were over-generalisation from a true
    narrow case
    (one path that does push, one layer that does converge, one
    subnet shape that does yield .1), which is the specific way this subsystem
    invites bad prose.

Deliberately not in scope

  • Wiring internal/network/frr.go, or deleting it.
  • Adding a REST-route triangulation test (docs route table ↔ registered
    HandleFunc patterns). This is the durable fix for defect class 2 and is
    worth doing, but it is a .go file and a much larger design question
    (the table also documents methods, SSE behaviour, and query params).
  • Wiring DeleteFloodEntry into host removal / network deprovision so stale
    flood entries are withdrawn. Another .go change, and a behavioural one: it
    needs a decision about who prunes (the departing host cannot, since it is
    often gone) and how that interacts with a host that is fenced but returns.
    The docs change only stops over-claiming; it does not close the gap.
  • Adding notifyVTEPPeers to the five provisioning paths that lack it, or
    replacing the whole add-only scheme with a periodic reconcile of
    network_vteps → kernel FDB (which would subsume both gaps and is probably
    the right shape). Same reasoning: behavioural .go change, out of scope for a
    docs commit, and worth its own design pass. Worth noting for whoever picks it
    up that the fleet harness (tests/fleet/) is where a multi-node flood-entry
    convergence test would belong — a single-package test structurally cannot
    reach it.
  • Documenting neigh_suppress / IRB behaviour on VXLAN, or investigating
    whether ARP suppression without an EVPN control plane leaves remote-VM
    neighbour entries unpopulated. That is a potential code issue surfaced while
    reading irb.go:63; it is noted here so it is not lost, and it is not
    something a docs commit should assert either way.
  • Any change to docs/configuration.md (already correct) or
    docs/federation.md:103 (already correct).

Commit

Docs-only, three files, one commit. Conventional-commit style per CLAUDE.md:

docs: correct auto_upgrade interval key, container exec route, and VXLAN control-plane claim

Final Codex review

No blocking findings remained.


Opened by overseer. The plan and the code each converged to zero blocking findings from Codex before this pull request was created.

@colonelpanik
colonelpanik marked this pull request as ready for review August 9, 2026 18:58
@colonelpanik
colonelpanik force-pushed the overseer/fix-three-verified-doc-vs-code-mismatches-in-a-single-docs-o branch from 278b85a to 80cc41e Compare August 9, 2026 18:59
@colonelpanik
colonelpanik merged commit cb1229a into main Aug 9, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant