Fixed on branch fix-adopt-registry-ownership. CI green.
You had the mechanism exactly right, including that it is a race and not just an ordering problem. The one thing I did differently is the fix itself, and it is worth saying why.
User=carlos on carlos-adopt.service is not available. The pass really does need root: it writes /opt/carlos (the version store) and cycles units via systemctl. The unit says so in its own header, and splitting the registry access out from the privileged part would mean a second process or a second unit for one SQLite handle. So the ownership is converged at both ends of the race instead:
carlos bootstrapcreates/data/registry.dbitself, before it arms anything that could race for it, and inside thechown -R carlos:carlos /datathatprivilegedPlanalready runs. An empty file is a valid empty SQLite database, so the first real opener migrates into it. An existing registry is left completely alone — bootstrap re-runs over live boxes whose edge holds the file open, and that must not touch a schema.- Every root adopt pass chowns the database and both sidecars back. This is the half your report is really about: once by hand is not enough because the armed
.path/.timerunits fire again and re-create-wal/-shmas root in between, which is why two of your three boxes needed the chown twice. A carlos-owned database with a root-owned-walis exactly as unwritable. It runs on every pass rather than at create, so a box that ends up wrong self-corrects within the hour instead of waiting for a person.
One deliberate choice: a chown failure is logged, never fatal. An adoption pass that can read the registry has releases to stage and units to cycle, and refusing all of that because a chown failed would turn a cosmetic problem into an outage.
The cheap fence is in too, and it covers all four root units plus the edge, since they all reach the registry through the same two openers. Instead of SQLite's words:
agent: open registry /data/registry.db: attempt to write a readonly database (8) agent: the registry is not writable by this process (uid 989). /data/registry.db is owned by root (uid 0, gid 0) — it and its -wal/-shm sidecars must be owned by carlos, the user carlos-edge.service runs as. A root carlos adopt pass that reached the file before the edge did is the usual cause. Fix: chown carlos:carlos /data/registry.db*
Other SQLite errors still read as themselves, so a genuinely full disk is not mislabelled as an ownership problem.
Tests pin the bootstrap ordering, that a re-run does not lose an existing registry's contents, the sidecar coverage, and the hint's text. The ownership-fixing helper is a no-op off root, so it does nothing on your laptop and nothing in CI.
Not merged — five branches went up tonight and I would rather you took them in one pass than have me land them one at a time. Nothing here changes behaviour on an established box.