Fix three verified doc-vs-code mismatches in a single docs-only chang... - #153
Merged
colonelpanik merged 1 commit intoAug 9, 2026
Conversation
colonelpanik
marked this pull request as ready for review
August 9, 2026 18:58
colonelpanik
force-pushed
the
overseer/fix-three-verified-doc-vs-code-mismatches-in-a-single-docs-o
branch
from
August 9, 2026 18:59
278b85a to
80cc41e
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.intervalwhere the real YAML key isinterval_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 asPOST /api/v1/containers/{name}/execwhere 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
.gofile is modified,no test is added, no behaviour changes.
docs/self-upgrade-from-peer.mdauto_upgrade.interval)docs/rest-api.md/api/v1/containers/{name}/exec)docs/networking.mdEvidence (re-verified in this worktree, not taken on trust)
(1)
auto_upgrade.interval→auto_upgrade.interval_minutesinternal/daemon/config.go:241-243:internal/daemon/config.go:250-255—Interval()returns5 * time.Minutewhen
IntervalMinutes <= 0, elseIntervalMinutesminutes. So the unit isminutes as an integer, not a Go duration string;
interval: 5min YAMLwould be silently ignored (unknown key) and misleading about the type.
docs/configuration.md:458already has it right:interval_minutes: 5 # how often to check peers for a newer build; 0 → 5.docs/self-upgrade-from-peer.md:90is the only wrong mention in the tree(
grep -rn "auto_upgrade" docs/returns exactly three hits: the two inself-upgrade-from-peer.md at :90 and :119, and configuration.md:454).
(2)
POST /api/v1/containers/{name}/exec→POST /api/v1/containers/execinternal/restapi/coverage.go:33:That is a literal
net/httppattern with no{...}wildcard and notrailing slash, so it matches only that exact path. There is no
/api/v1/containers/subtree handler and no/catch-all ininternal/restapi/— the complete set of container registrations isparity.go:41(/api/v1/containers) pluscoverage.go:29-34(
create|start|stop|delete|exec|pull). A request toPOST /api/v1/containers/foo/exectherefore 404s.The container name travels in the body:
ExecContainerRequestis{host_name, name, argv}(proto/litevirt/v1/service.proto:924-928), andhandleContainerExec(internal/restapi/coverage.go:223-234) decodes the wholerequest from the body via
protoFromJSON— it never reads a path segment. Thismatches the sibling routes (
start/stop/deleteare all documented as"name in body").
internal/restapi/coverage_test.go:29pins the exact path today(
GET /api/v1/containers/execmust return 405, not 404), which is the strongestexisting 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 proseparenthetical in the "Still gRPC-only" section contradicts it.
grep -rn "containers/" docs/ README.mdshows:323is the sole wrongoccurrence.
(3) "BGP peering between hosts distributes MAC/IP mappings" → static unicast FDB
internal/network/frr.goexportsRenderFRRConfigandWriteFRRConfig.Grepping the whole tree for
frr/FRRoutside_test.goreturns hits ininternal/network/frr.goonly; the sole test file isinternal/network/frr_test.go. The BGP/EVPN renderer has zero productioncallers — nothing writes
/etc/frr/frr.conf, nothing reloads FRR, and alitevirt host never needs FRR installed.
What actually runs:
VTEP membership —
network.Provision(internal/network/provision.go:151-166)does
EnsureVXLAN→UpsertVTEP→SyncFloodEntries, andEnsureIRBwhensubnetis set.UpsertVTEP(provision.go:392) writes the host's VTEP intothe
network_vtepstable, which replicates over corrosion/CRDT;GetVTEPs(provision.go:407) reads the member list back.BUM / flood, pull side (universal) —
SyncFloodEntries(
provision.go:441-455) callsnetwork.FloodEntryper peer, which isbridge fdb add 00:00:00:00:00:00 dev vxlan<VNI> dst <peer>(
internal/network/fdb.go:35-42). Head-end (ingress) replication. This sitsinside
Provision, so every provisioning path performs it.BUM / flood, push side (NOT universal) —
notifyVTEPPeers→ peerSyncVTEPRPC →network.FloodEntry(
internal/grpcapi/networks.go:367-384, 501-520) is what makes existingpeers add a newcomer's entry. It is called from only two of the seven
provisioning entry points. Full census of
SafeProvision/ProvisionForVMcallers:SyncVTEP?ProvisionNetworkRPCnetworks.go:357:368)CreateVMvm.go:510viaprovisionNetworkForVM→vm.go:2160vm.go:516)CreateNetworkRPCnetworks.go:73→provisionAndPersistNetwork:252stacks.go:1506→provisionAndPersistNetwork:252hotplug_nic.go:313daemon.go:1784(reconcileNetworks)internal/health/reconciler.go:1041So a host joining a VXLAN via
lv network create, a stack deploy, a NIChotplug, or a daemon restart programs its own side and stays invisible to
existing peers' flood lists until each of those peers re-provisions. Because
reconcileNetworksruns at startup, a peer restart does re-pull the tableand 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) andGetVTEPsfiltersdeleted_at IS NULL, so the database view of membership shrinks correctly. Kernel floodentries are the add-only layer:
DeleteFloodEntry(fdb.go:45) has noproduction caller (definition +
fdb_test.go+network_extra_test.go), andSyncFloodEntriesonly ever adds — it never diffs the kernel against thetable. 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:
SyncVTEPRPC (internal/grpcapi/networks.go:374-383) callsnetwork.FloodEntrydirectly, so a peer's push adds an entry to this hostbetween local provisioning passes.
Deprovision→DeprovisionVXLAN(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 thechange locally and fans it out to every peer over peer-mTLS gRPC
(
UpdateFDB,internal/grpcapi/networks.go:428-443—requirePeerCert,then
network.AddFDBEntry/DeleteFDBEntry). Three triggers, all VM-scoped:IP discovery (
internal/grpcapi/ip_scanner.go:98), migration(
internal/grpcapi/migrate.go:639→updateFDBForMigration), and delete(
CleanupFDBForVM,ip_scanner.go:154-171).Anycast gateway —
EnsureIRB(internal/network/irb.go:46-70) puts thesame address on
br-vni<VNI>on every host. That address isgatewayForSubnet(irb.go:11-38) = network address + 1, i.e. the firstusable address, not a
.1—10.0.1.128/25yields10.0.1.129. Thefunction's own doc comment (
irb.go:9-10) says "firstHostIP" and its examplehappens to be a
/24, which is how a.1claim gets written; the code iscopy(gw, ipNet.IP.To4()); ipInc(gw).The only other BGP mention in the docs is
docs/federation.md:103, whichalready 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.mdBefore (lines 90–91):
After:
"integer minutes" is the part that earns its keystrokes: the old text's
(default 5m)invited an operator to writeinterval: 5m, which is wrongtwice over.
Edit 2 —
docs/rest-api.mdBefore (lines 321–323):
After:
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.mdBefore (line 41, a single long line):
After (wrapped ~80 cols, matching the isolated-network paragraph at lines 46–48
in the same file):
Deliberate wording choices, each traceable to code:
(
scanVMs,updateFDBForMigration,CleanupFDBForVM). Containers on a VXLANnetwork get flood entries through provisioning but no unicast FDB path, so
saying "workload" would be a new, false claim.
reader who remembers the old sentence has no signal that the claim was
withdrawn, and the sentence is a standing invitation to re-add.
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.
notifyVTEPPeersfires from only two of seven provisioningentry points (census table above), and — worse than the finding stated — the
two that do push are
ProvisionNetworkandCreateVM, while the ordinaryoperator path for adding a network,
lv network create→CreateNetwork→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
Provisionand therefore holds on every path, and hedgethe 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.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 perprovisioning pass" was false in the other direction:
SyncVTEP(
networks.go:374-383) callsFloodEntryoutside any provisioning pass, soentries 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 (
Deprovision→DeprovisionVXLAN,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.
.1". Third review catch,minor and correct.
gatewayForSubnet(irb.go:11-38) is network address + 1,so it is
.1only for subnets whose network address ends in.0;10.0.1.128/25gives10.0.1.129. Worth noting the same mistake is alreadylatent in the code's own doc comment (
irb.go:9-10), whose example is a/24— that comment is where I took
.1from, which is a small lesson abouttreating a neighbouring comment as a source. Not fixing that comment here:
docs-only.
neigh_suppress(set byEnsureIRB,irb.go:63). It isreal, 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:
docs/self-upgrade-from-peer.mdnew line ↔internal/daemon/config.go:242(tag is
interval_minutes) and:250-255(default 5, unit minutes).docs/rest-api.mdnew line ↔internal/restapi/coverage.go:33(literalpath) and
:223-234(body-decoded, no path segment read).docs/networking.mdnew paragraph ↔internal/network/provision.go:151-166and
: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-pathcensus 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:
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_vtepsrows, tombstoned; or kernel flood entries, add-only) andreject any that spans both; and check the gateway sentence against
gatewayForSubnet(irb.go:11-38) with a non-/24example such as10.0.1.128/25→10.0.1.129, not against the/24in the function's owndoc comment.
B. Exhaustiveness greps. The old claim must be gone:
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 neverhave 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:
docs/federation.md:103is correct as it stands (fabric-layer FRR/BGP/ECMPdeclared out of scope) and must stay.
C. Guards. Baseline was captured before any edit and passes:
Re-run the same command after the edit, then
make ci-guardsfor the full setper CLAUDE.md.
go build ./... && go vet ./... && go test ./...are unaffectedby a docs-only change but are cheap and are what CLAUDE.md asks for.
The specific guard interactions worth predicting rather than discovering:
TestDocsDocumentEveryConfigKeyrequires every YAML tag reachable from theconfig struct to appear somewhere in
README.mdordocs/*.md. Removingthe bogus
auto_upgrade.intervalcannot break it (it was never a real key),and
interval_minuteskeeps itsdocs/configuration.md:458mention plusgains a second one. No
undocumentedConfigKeysentry is needed.TestDocsReferenceRealCLICommandsextractslv/litevirtinvocations fromfenced blocks and, outside fences, only from inline-code spans
(
extractInvocations,docs_triangulation_test.go:370-393). None of the newinline spans (
auto_upgrade.interval_minutes,POST /api/v1/containers/exec,bridge fdb,00:00:00:00:00:00,subnet:) begin withlvorlitevirt,so no new invocation is extracted. The retained
`lv host upgrade`spanresolves today and is unchanged. Prose starting with the bare word "litevirt"
is not scanned — which is why networking.md:41 passes today.
TestDocsReferenceRealMetricsonly inspects inlinelitevirt_*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 theedit, the pattern is wrong, not the tree. This step is what would have caught
the bad
bgp\|evpncheck in the first draft: that one does return line 41before 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 beingdeleted.
What a reviewer needs to know about this codebase
"Docs are guarded in both directions" is accurate but narrow:
cmd/litevirt/docs_triangulation_test.gochecks that documented CLI commandsresolve in the cobra tree, that
litevirt_*identifiers exist as stringliterals, 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 herelive 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.HandleFuncregistrations against the docs at all), and a mechanismclaim (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.mdis not in this repository. The task description cites "TODO.md§C" as flagging
frr.goas dead code;find . -iname 'TODO*'returnsnothing, and
docs_triangulation_test.go:23-24explains why — "the privatePlan.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-unwiredclaim is re-verified independently above by grep.
frr.gostays 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.
/api/v1/containers/exec. Fix 2 makes the prose agree with a table in thesame file, so there is no "which one is right" judgement call for the
reviewer — the file already contradicted itself.
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 transportsfor two different kinds of state. Neither is BGP.
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
(
CleanupFDBForVMfromvm.go:1733,updateFDBForMigrationfrommigrate.go:639), but flood entries get neither a delete (DeleteFloodEntryis dead code, exactly like
frr.go) nor a guaranteed announce (only 2 of 7provisioning 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.
strongest argument in this plan. Round 1: "current member list" implied a
withdrawal that
DeleteFloodEntry's dead status disproves. Round 2: "peersthat 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 "
.1gateway" generalised a/24example. Every one was in myreplacement 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 subsysteminvites bad prose.
Deliberately not in scope
internal/network/frr.go, or deleting it.HandleFuncpatterns). This is the durable fix for defect class 2 and isworth doing, but it is a
.gofile and a much larger design question(the table also documents methods, SSE behaviour, and query params).
DeleteFloodEntryinto host removal / network deprovision so staleflood entries are withdrawn. Another
.gochange, and a behavioural one: itneeds 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.
notifyVTEPPeersto the five provisioning paths that lack it, orreplacing the whole add-only scheme with a periodic reconcile of
network_vteps→ kernel FDB (which would subsume both gaps and is probablythe right shape). Same reasoning: behavioural
.gochange, out of scope for adocs 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-entryconvergence test would belong — a single-package test structurally cannot
reach it.
neigh_suppress/ IRB behaviour on VXLAN, or investigatingwhether 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 notsomething a docs commit should assert either way.
docs/configuration.md(already correct) ordocs/federation.md:103(already correct).Commit
Docs-only, three files, one commit. Conventional-commit style per CLAUDE.md:
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.