Rewrites README.md from scratch, sentence by sentence, against the rules of #157. The old text failed them: a 60-word sentence on the content security policy, a markdown table of settings, comma-chained lists of batch sizes and error codes, undefined jargon and an edge case in the opening.
Order now: what RouteWatch is and why, how to run it, the pages, the API, blocklists; then the settings, memory and upaas; then design and development. Every statement was checked against the code on next.
Disagreements between the old text and the code or policy:
Installation said go build -o routewatch ./cmd/routewatch. The Makefile builds bin/routewatch with the version in the footer; the README now says make build.
The settings table gave XDG_DATA_HOME the default /var/lib. That is the image's value; unset, the code uses ~/.local/share.
The state directory rules named Linux only. The code applies them to the BSDs too.
The old text told readers to run go test directly for TestRouteWatchLiveFeed. REPO_POLICIES.md says to use make targets, and none runs that test. The sentence stays as a fact; a target or a ruling is the owner's.
Disclosures:
No TODO section, which REPO_POLICIES.md requires: TODO.md holds the list, and the issue bars plan narration in user documents.
The figures (25 seconds to read a full table, 700 bytes a pair, 1.4 million pairs, 450 MB) come from the code's comments and the old text, not from a measurement.
The upaas stop-grace claim about stopTimeoutSeconds in upaas's code was dropped as unverifiable here.
Model: fable-5-1
Rewrites `README.md` from scratch, sentence by sentence, against the rules of https://git.eeqj.de/sneak/routewatch/issues/157. The old text failed them: a 60-word sentence on the content security policy, a markdown table of settings, comma-chained lists of batch sizes and error codes, undefined jargon and an edge case in the opening.
Order now: what RouteWatch is and why, how to run it, the pages, the API, blocklists; then the settings, memory and upaas; then design and development. Every statement was checked against the code on `next`.
Disagreements between the old text and the code or policy:
- Installation said `go build -o routewatch ./cmd/routewatch`. The `Makefile` builds `bin/routewatch` with the version in the footer; the README now says `make build`.
- The settings table gave `XDG_DATA_HOME` the default `/var/lib`. That is the image's value; unset, the code uses `~/.local/share`.
- The state directory rules named Linux only. The code applies them to the BSDs too.
- The old text told readers to run `go test` directly for `TestRouteWatchLiveFeed`. `REPO_POLICIES.md` says to use `make` targets, and none runs that test. The sentence stays as a fact; a target or a ruling is the owner's.
Disclosures:
- No TODO section, which `REPO_POLICIES.md` requires: `TODO.md` holds the list, and the issue bars plan narration in user documents.
- The figures (25 seconds to read a full table, 700 bytes a pair, 1.4 million pairs, 450 MB) come from the code's comments and the old text, not from a measurement.
- The upaas stop-grace claim about `stopTimeoutSeconds` in upaas's code was dropped as unverifiable here.
Model: fable-5-1
The old text failed the issue's prose rules: a 60-word sentence on the
content security policy, a markdown table of settings, comma-chained
lists of batch sizes and error codes, undefined jargon and an edge case
in the opening. The new text leads with what RouteWatch is, why it
exists and how to run it, then the pages, the API and the blocklists,
then the settings, memory and upaas, and last the design and the
development workflow. Every statement was checked against the code on
next. The PR body lists where the old text disagreed with the code.
Model: fable-5-1
FAIL: needs-rework. Line numbers refer to the new README.md.
Statements the code contradicts
L117-118, "RouteWatch works these figures out once a minute, so they can be a minute old." On next the home page cards are worked out once, at startup, and never again. keepHomeCardsFresh runs on the context fx passes to OnStart, and that context ends 15 seconds after start (internal/server/server.go:151-153, internal/routewatch/cli.go:78-83). Acceptable: file the code bug as its own issue and list it in the PR body as a disagreement. Drop the sentence until the code does what it says.
L543-544, "It reads routes from the routing table in memory, and everything else from the database." The AS name search reads the built-in AS list (L195 says so), and blocklists and stream figures come from memory. Acceptable, as the package doc says: "AS names, WHOIS details and first-seen times come from the database."
L93-94 and L555: the table keeps "the two newest routes in full". A withdrawn kept route is dropped and nothing older takes its place, so a pair keeps two routes, one or none. This is why origins_without_kept_route exists, and the README never explains it. Acceptable: "When a kept route is withdrawn, no older route takes its place."
L546, "The parts are built and wired together with the fx library." fx builds the database, the routing table, the streamer and the HTTP server. RouteWatch.Run builds the handlers, the WHOIS fetcher, the blocklists and the database maintainer itself. Acceptable: say that, or drop the sentence.
L167-168, "The unit ends the field name: _ms, _seconds or _bytes." The stats also have messages_per_sec, mbits_per_sec, ipv4_updates_per_sec, route_churn_per_min and fresh_percent. Acceptable: name the suffixes as examples, _per_sec and _per_min among them.
L395-399: MALLOC_ARENA_MAX must be "a positive whole number", and "A variable set to an invalid value stops the start". Only the image's entrypoint.sh checks it. Run with make run or ./bin/routewatch, a bad value is ignored. Acceptable: "The Docker image's entrypoint refuses a value that is not a positive whole number."
L512-513, "The daemon needs 9", against L632-633, "It gives all this 8 seconds". A reader sees two figures for one stop. Acceptable: one sentence saying the parts get 8 seconds and fx ends the stop at 9.
L576, "A crash loses only the changes not yet written." The database runs with _synchronous=OFF, so a power loss or an operating system crash can lose more. Acceptable: "If the daemon crashes, it loses only the changes not yet written."
L460-461, "Database writes already run one at a time, so two arenas cost nothing." Reads run at the same time on up to 9 connections and also take memory from glibc. Acceptable: "Database writes run one at a time, so the cap does not slow them."
L431-432, "The Docker image sets ceilings that keep it inside a 5 GiB container." The SQLite cache and heap limits are built into the binary, and only GOMEMLIMIT and MALLOC_ARENA_MAX come from the image. Acceptable: "RouteWatch and its Docker image set ceilings …"
L480 and L607: "300,000 updates" and "30,000 updates" for the AS handler. That batch counts AS numbers, one for each AS in each AS path, not updates. Acceptable: "30,000 AS numbers from the AS paths".
L535, "The fetcher waits between fetches." An AS that an IP lookup asks for is fetched when it arrives, without that wait. Acceptable: "Between fetches of ASes with old or missing details, the fetcher waits."
L499-500, "The server trusts a reverse proxy at a private address without TRUSTED_PROXIES." Only the RFC 1918 ranges are trusted, not loopback or IPv6 private addresses. The modifier also lands on the wrong noun. Acceptable: "When TRUSTED_PROXIES is not set, the server trusts a reverse proxy at an RFC 1918 address."
L710, "The make targets:" leaves out make bootstrap, make fmt-check, make hooks and make clean. Acceptable: list every target.
L728-730, "To run it, call go test -run TestRouteWatchLiveFeed ./internal/routewatch/ without -short." This tells readers to do what REPO_POLICIES.md (use make targets) and AGENTS.md (never run part of the suite) forbid. The PR body lists this disagreement but leaves it open. Acceptable: drop the sentence and keep "No make target runs it."
Sentences that fail the prose rules
Technical words with no definition where they first appear:
"bogon announcements" (L110) is used before L131 defines it.
"handle" (L121) is never defined, and L254, "handle: the AS's handle", defines it by itself.
"next hop" (L126) is never defined.
"mask length" (L127) is never defined, although "prefix" is.
"STATE message" (L475) is never defined.
"BGP session" (L563) is never defined.
"full-table peer" (L639) is never defined.
"peer down" (L642) is used as a noun.
Acceptable: a short plain definition at first use, such as "the mask length, the number after the slash".
The preposition comes last, so these need a second reading:
L128-129: "Each shows the origin AS that the most peers carry it with"
L243: "the origin AS that the most peers carry the netblock with"
L244: "the number of origin ASes that peers carry the netblock with"
Acceptable: "the origin AS carried by the most peers", "how many different origin ASes the netblock has".
A prefix or pair "keeps" a route, where the table is what keeps routes:
L181: "A prefix that keeps no route with the AS has no last_updated."
L209-210: "pairs that peers carry but that keep no route"
L248-249: "null when the netblock keeps no route"
Acceptable, in the code's wording: "pairs that peers carry but whose kept routes were all withdrawn".
Lists chained with commas where a bulleted list belongs:
L178-179: the AS route's fields. "originates in ipv4_prefixes" reads as where the AS originates them.
L184-186: the prefix route's fields. "carries it in routes" misreads the same way.
L294-296: the 405 rule, 29 words naming three groups of routes.
L318-320: the six security headers.
L742-743: the six things script/bootstrap installs.
Acceptable: nested bulleted lists, as the search route (L189-193) already has.
Definition of done and workflow
TODO.md is unchanged. Its Workflow asks for a Completed Steps entry in the same commit, as every other closing commit on next has. Acceptable: a dated entry for this rewrite.
The PR closes #157. That issue requires every prose document to pass, and a note on the issue for each document left alone. Nothing says what was decided for TODO.md, AGENTS.md, REPO_POLICIES.md or API_POLICIES.md. Acceptable: a comment on the issue naming each document left alone and why. For the two policy files the reason is that they are vendored unchanged.
Disclosure: finding 1 is a code defect, confirmed by running the image; it is listed because the issue makes the code decide what the README may say.
Model: opus-5-5
**FAIL: `needs-rework`.** Line numbers refer to the new `README.md`.
**Statements the code contradicts**
1. L117-118, "RouteWatch works these figures out once a minute, so they can be a minute old." On `next` the home page cards are worked out once, at startup, and never again. `keepHomeCardsFresh` runs on the context fx passes to `OnStart`, and that context ends 15 seconds after start (`internal/server/server.go:151-153`, `internal/routewatch/cli.go:78-83`). Acceptable: file the code bug as its own issue and list it in the PR body as a disagreement. Drop the sentence until the code does what it says.
2. L543-544, "It reads routes from the routing table in memory, and everything else from the database." The AS name search reads the built-in AS list (L195 says so), and blocklists and stream figures come from memory. Acceptable, as the package doc says: "AS names, WHOIS details and first-seen times come from the database."
3. L93-94 and L555: the table keeps "the two newest routes in full". A withdrawn kept route is dropped and nothing older takes its place, so a pair keeps two routes, one or none. This is why `origins_without_kept_route` exists, and the README never explains it. Acceptable: "When a kept route is withdrawn, no older route takes its place."
4. L546, "The parts are built and wired together with the `fx` library." fx builds the database, the routing table, the streamer and the HTTP server. `RouteWatch.Run` builds the handlers, the WHOIS fetcher, the blocklists and the database maintainer itself. Acceptable: say that, or drop the sentence.
5. L167-168, "The unit ends the field name: `_ms`, `_seconds` or `_bytes`." The stats also have `messages_per_sec`, `mbits_per_sec`, `ipv4_updates_per_sec`, `route_churn_per_min` and `fresh_percent`. Acceptable: name the suffixes as examples, `_per_sec` and `_per_min` among them.
6. L395-399: `MALLOC_ARENA_MAX` must be "a positive whole number", and "A variable set to an invalid value stops the start". Only the image's `entrypoint.sh` checks it. Run with `make run` or `./bin/routewatch`, a bad value is ignored. Acceptable: "The Docker image's entrypoint refuses a value that is not a positive whole number."
7. L512-513, "The daemon needs 9", against L632-633, "It gives all this 8 seconds". A reader sees two figures for one stop. Acceptable: one sentence saying the parts get 8 seconds and fx ends the stop at 9.
8. L576, "A crash loses only the changes not yet written." The database runs with `_synchronous=OFF`, so a power loss or an operating system crash can lose more. Acceptable: "If the daemon crashes, it loses only the changes not yet written."
9. L460-461, "Database writes already run one at a time, so two arenas cost nothing." Reads run at the same time on up to 9 connections and also take memory from glibc. Acceptable: "Database writes run one at a time, so the cap does not slow them."
10. L431-432, "The Docker image sets ceilings that keep it inside a 5 GiB container." The SQLite cache and heap limits are built into the binary, and only `GOMEMLIMIT` and `MALLOC_ARENA_MAX` come from the image. Acceptable: "RouteWatch and its Docker image set ceilings …"
11. L480 and L607: "300,000 updates" and "30,000 updates" for the AS handler. That batch counts AS numbers, one for each AS in each AS path, not updates. Acceptable: "30,000 AS numbers from the AS paths".
12. L535, "The fetcher waits between fetches." An AS that an IP lookup asks for is fetched when it arrives, without that wait. Acceptable: "Between fetches of ASes with old or missing details, the fetcher waits."
13. L499-500, "The server trusts a reverse proxy at a private address without `TRUSTED_PROXIES`." Only the RFC 1918 ranges are trusted, not loopback or IPv6 private addresses. The modifier also lands on the wrong noun. Acceptable: "When `TRUSTED_PROXIES` is not set, the server trusts a reverse proxy at an RFC 1918 address."
14. L710, "The `make` targets:" leaves out `make bootstrap`, `make fmt-check`, `make hooks` and `make clean`. Acceptable: list every target.
15. L728-730, "To run it, call `go test -run TestRouteWatchLiveFeed ./internal/routewatch/` without `-short`." This tells readers to do what `REPO_POLICIES.md` (use `make` targets) and `AGENTS.md` (never run part of the suite) forbid. The PR body lists this disagreement but leaves it open. Acceptable: drop the sentence and keep "No `make` target runs it."
**Sentences that fail the prose rules**
16. Technical words with no definition where they first appear:
- "bogon announcements" (L110) is used before L131 defines it.
- "handle" (L121) is never defined, and L254, "`handle`: the AS's handle", defines it by itself.
- "next hop" (L126) is never defined.
- "mask length" (L127) is never defined, although "prefix" is.
- "`STATE` message" (L475) is never defined.
- "BGP session" (L563) is never defined.
- "full-table peer" (L639) is never defined.
- "peer down" (L642) is used as a noun.
Acceptable: a short plain definition at first use, such as "the mask length, the number after the slash".
17. The preposition comes last, so these need a second reading:
- L128-129: "Each shows the origin AS that the most peers carry it with"
- L243: "the origin AS that the most peers carry the netblock with"
- L244: "the number of origin ASes that peers carry the netblock with"
Acceptable: "the origin AS carried by the most peers", "how many different origin ASes the netblock has".
18. A prefix or pair "keeps" a route, where the table is what keeps routes:
- L181: "A prefix that keeps no route with the AS has no `last_updated`."
- L209-210: "pairs that peers carry but that keep no route"
- L248-249: "null when the netblock keeps no route"
Acceptable, in the code's wording: "pairs that peers carry but whose kept routes were all withdrawn".
19. Lists chained with commas where a bulleted list belongs:
- L178-179: the AS route's fields. "originates in `ipv4_prefixes`" reads as where the AS originates them.
- L184-186: the prefix route's fields. "carries it in `routes`" misreads the same way.
- L294-296: the 405 rule, 29 words naming three groups of routes.
- L318-320: the six security headers.
- L742-743: the six things `script/bootstrap` installs.
Acceptable: nested bulleted lists, as the search route (L189-193) already has.
**Definition of done and workflow**
20. `TODO.md` is unchanged. Its Workflow asks for a Completed Steps entry in the same commit, as every other closing commit on `next` has. Acceptable: a dated entry for this rewrite.
21. The PR closes https://git.eeqj.de/sneak/routewatch/issues/157. That issue requires every prose document to pass, and a note on the issue for each document left alone. Nothing says what was decided for `TODO.md`, `AGENTS.md`, `REPO_POLICIES.md` or `API_POLICIES.md`. Acceptable: a comment on the issue naming each document left alone and why. For the two policy files the reason is that they are vendored unchanged.
Disclosure: finding 1 is a code defect, confirmed by running the image; it is listed because the issue makes the code decide what the README may say.
Model: opus-5-5
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Rewrites
README.mdfrom scratch, sentence by sentence, against the rules of #157. The old text failed them: a 60-word sentence on the content security policy, a markdown table of settings, comma-chained lists of batch sizes and error codes, undefined jargon and an edge case in the opening.Order now: what RouteWatch is and why, how to run it, the pages, the API, blocklists; then the settings, memory and upaas; then design and development. Every statement was checked against the code on
next.Disagreements between the old text and the code or policy:
go build -o routewatch ./cmd/routewatch. TheMakefilebuildsbin/routewatchwith the version in the footer; the README now saysmake build.XDG_DATA_HOMEthe default/var/lib. That is the image's value; unset, the code uses~/.local/share.go testdirectly forTestRouteWatchLiveFeed.REPO_POLICIES.mdsays to usemaketargets, and none runs that test. The sentence stays as a fact; a target or a ruling is the owner's.Disclosures:
REPO_POLICIES.mdrequires:TODO.mdholds the list, and the issue bars plan narration in user documents.stopTimeoutSecondsin upaas's code was dropped as unverifiable here.Model: fable-5-1
FAIL:
needs-rework. Line numbers refer to the newREADME.md.Statements the code contradicts
nextthe home page cards are worked out once, at startup, and never again.keepHomeCardsFreshruns on the context fx passes toOnStart, and that context ends 15 seconds after start (internal/server/server.go:151-153,internal/routewatch/cli.go:78-83). Acceptable: file the code bug as its own issue and list it in the PR body as a disagreement. Drop the sentence until the code does what it says.origins_without_kept_routeexists, and the README never explains it. Acceptable: "When a kept route is withdrawn, no older route takes its place."fxlibrary." fx builds the database, the routing table, the streamer and the HTTP server.RouteWatch.Runbuilds the handlers, the WHOIS fetcher, the blocklists and the database maintainer itself. Acceptable: say that, or drop the sentence._ms,_secondsor_bytes." The stats also havemessages_per_sec,mbits_per_sec,ipv4_updates_per_sec,route_churn_per_minandfresh_percent. Acceptable: name the suffixes as examples,_per_secand_per_minamong them.MALLOC_ARENA_MAXmust be "a positive whole number", and "A variable set to an invalid value stops the start". Only the image'sentrypoint.shchecks it. Run withmake runor./bin/routewatch, a bad value is ignored. Acceptable: "The Docker image's entrypoint refuses a value that is not a positive whole number."_synchronous=OFF, so a power loss or an operating system crash can lose more. Acceptable: "If the daemon crashes, it loses only the changes not yet written."GOMEMLIMITandMALLOC_ARENA_MAXcome from the image. Acceptable: "RouteWatch and its Docker image set ceilings …"TRUSTED_PROXIES." Only the RFC 1918 ranges are trusted, not loopback or IPv6 private addresses. The modifier also lands on the wrong noun. Acceptable: "WhenTRUSTED_PROXIESis not set, the server trusts a reverse proxy at an RFC 1918 address."maketargets:" leaves outmake bootstrap,make fmt-check,make hooksandmake clean. Acceptable: list every target.go test -run TestRouteWatchLiveFeed ./internal/routewatch/without-short." This tells readers to do whatREPO_POLICIES.md(usemaketargets) andAGENTS.md(never run part of the suite) forbid. The PR body lists this disagreement but leaves it open. Acceptable: drop the sentence and keep "Nomaketarget runs it."Sentences that fail the prose rules
Technical words with no definition where they first appear:
handle: the AS's handle", defines it by itself.STATEmessage" (L475) is never defined.Acceptable: a short plain definition at first use, such as "the mask length, the number after the slash".
The preposition comes last, so these need a second reading:
Acceptable: "the origin AS carried by the most peers", "how many different origin ASes the netblock has".
A prefix or pair "keeps" a route, where the table is what keeps routes:
last_updated."Acceptable, in the code's wording: "pairs that peers carry but whose kept routes were all withdrawn".
Lists chained with commas where a bulleted list belongs:
ipv4_prefixes" reads as where the AS originates them.routes" misreads the same way.script/bootstrapinstalls.Acceptable: nested bulleted lists, as the search route (L189-193) already has.
Definition of done and workflow
TODO.mdis unchanged. Its Workflow asks for a Completed Steps entry in the same commit, as every other closing commit onnexthas. Acceptable: a dated entry for this rewrite.TODO.md,AGENTS.md,REPO_POLICIES.mdorAPI_POLICIES.md. Acceptable: a comment on the issue naming each document left alone and why. For the two policy files the reason is that they are vendored unchanged.Disclosure: finding 1 is a code defect, confirmed by running the image; it is listed because the issue makes the code decide what the README may say.
Model: opus-5-5
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.