
What building it corrected
The specification survived contact with the compiler better than I expected and worse than I claimed. Here are the corrections, because the corrections are the part worth reading.
Watching a directory does not close the race. The plan was to add an inotify watch when a new directory appears. A watch can only be added when the event is drained, up to fifty milliseconds later, and anything written in that window lands in a directory nobody is watching. Creating a folder and writing index.md in the same breath hits this every time. The fix is to sweep the directory at the moment the watch is added.
Every editor save triggered a full rescan. Vim, and sed -i, and most editors, save by writing a temporary file and renaming it over the target. That produces a delete event for the temporary name, and I was treating a delete as a reason to reconcile the whole tree. At a thousand documents a one file edit took four seconds. The path is known, so the row can go directly.
nginx does not pass the environment to workers. It clears everything except TZ, so os.getenv returned nil in Lua for every configuration variable unless declared with an env directive. This went unnoticed for weeks because the values in the container happened to equal the hardcoded defaults, and it surfaced only when I set one to something different.
ngx.now() is a cached clock. It only advances when the event loop yields. Index scans were reported as 0.0ms for every scan ever run, including one over a thousand documents, because a scan holds the loop. It also means a plugin trying to time itself with it spins forever, which is how I found it.
Reading an mtime per document meant a process per document. The scan spawned stat a thousand times, about two of the three and a half seconds a rebuild took. find reports it in the same pass.
An empty Lua table is ambiguous in JSON. cjson encodes it as an object, so every empty array in the generated OpenAPI document came out as {} where the specification requires []. My own structural checks looked at presence and missed it. A real validator caught it in a second.
A cursor taken from the visible row silently truncates. Pagination excluded rows a read rule hid, and took the next cursor from the last row the caller could see. A page whose rows were all hidden produced no next link, so everything after a single hidden draft became unreachable. I had recorded per row filtering as a performance note. It was data loss.
Listing a collection returned one row per translation. Invisible until a second translation existed, at which point the frontend rendered the same post twice in two languages.
The length operator is undefined on a table with nil holes. Bind lists routinely contain nulls, and a hole made every parameter after it silently vanish. It showed up as failed logins never reaching the audit log.
ngx.status and ngx.header are properties, not fields. Copying them into a curated table for plugins yields nil and loses the header metatable, so a plugin route could not produce a response at all.
Trash could not live under var/. A delete is a directory rename, rename cannot cross a filesystem, and content and state are separate mounts.
The master was running as root to bind a port above 1024, which needs no privilege.
Two general lessons.
The first is that the measurements were wrong before the code was. Three performance budgets were being reported as passing by instruments that measured nothing. Writing the benchmark found more bugs than writing the feature did.
The second is that every one of these was in a seam. Not one was a wrong algorithm. They were all a boundary where two correct things met and disagreed about something neither of them stated: nginx and the environment, Lua and JSON, a filesystem and a mount point, an editor and a file watcher. The specification described the parts, and the parts were mostly right.