v5.3.29 — Runtime Hygiene, Fail-Closed Startup, and Honest Cron Tracing
This release closes a class of problems that are easy to dismiss as “local mess” until they turn into user-facing drift. The audit behind Block 1 found that the published repo was cleaner than the live worktree looked, but that was not comforting: duplicate * 2 artifacts were being hidden instead of rejected, the shell update path still carried logic parallel to the Python core, server startup could keep mutating state in the background, and cron evidence still depended too much on SQLite being healthy at the exact moment the wrapper tried to write.
v5.3.29 turns those soft failure paths into explicit contracts. The runtime now fails closed where it used to improvise, and the public release surface is finally forced to stay aligned with the packaged line before a tag can ship.
1. Duplicate artifacts are now contamination, not tolerated noise
Files such as alpha 2.py or duplicated test files were not always in HEAD, but they could still poison a live checkout because .gitignore hid them and several loaders walked the tree too permissively. That meant a local repo could behave differently from the clean published tree without anyone noticing.
Now:
.gitignorestops hiding those duplicates- runtime/plugin/update loaders skip duplicate artifacts explicitly
nexo-preflight.shandverify_release_readiness.pyfail if they reappear
The point is simple: if the tree is contaminated, release gates should say so loudly instead of hoping the operator notices.
2. There is one update core again
src/scripts/nexo-update.sh no longer carries a parallel shell implementation of update behavior. It now delegates to the canonical Python update handler, so packaged/runtime updates stop diverging depending on which entry point happened to run.
That matters because update logic is not a harmless convenience layer. Divergent paths create different runtime states, and one of them can quietly preserve bugs the other already fixed.
3. Startup now fails closed on corrupt DB state
The server no longer starts while a mutating preflight still runs in the background, and corrupt SQLite state no longer spawns a brand-new empty brain by default. If the DB is broken, the runtime now stops and tells the truth unless the operator explicitly opts into fresh-DB recovery with NEXO_ALLOW_FRESH_DB_ON_CORRUPTION=1.
That change matters more than it sounds. “Keep the process alive” is the wrong priority if staying alive means erasing the very continuity the product exists to preserve.
4. Cron tracing survives transient DB failure
The cron wrapper used to create an open row and then patch it later. If the DB write path failed at the wrong time, the command could still run while leaving misleading or incomplete telemetry behind.
Now the wrapper records a complete row after execution, and if SQLite is unavailable it writes a spool JSON artifact under ~/.nexo/operations/cron-spool. The evidence survives long enough to be recovered instead of vanishing into a blank spot in history.
5. Release discipline is now enforced more honestly
This patch also hardens the release audit itself. verify_release_readiness.py now checks repo-facing public surfaces too: README.md, llms.txt, index.html, blog/index.html, changelog/index.html, and sitemap.xml. A tag is no longer considered “ready” just because code and npm metadata line up while the public copy still describes an older release.
That is the release-engineering lesson behind this patch: the code can be correct and the release can still be dishonest if the outward-facing surfaces drift.
Update
npm update -g nexo-brain
nexo update
If your runtime is clean, the update should stay boring. If it is not, boring is exactly what the new gates are designed to restore.