mirror of
https://github.com/hanzoai/tasks.git
synced 2026-10-04 02:04:03 +00:00
An older binary addressed a shard by org alone (<data>/<org>/<ns>.db). This one reads three levels down, so the first boot after the upgrade finds nothing, creates empty shards beside the full ones, enumerates zero tenants and never fires a schedule again — silently, with every byte still on disk one directory up. store.Upgrade inserts the two sentinel levels uniformly, including the old empty-org dir (_ -> _/_/_). A rename within one filesystem: no page rewritten, no re-encryption. Journals move before their database, so a run that stops half way never leaves a database without the WAL holding its last commits. Idempotent — a moved shard leaves nothing at its old path — so a deploy can run it unconditionally ahead of the daemon. Two refusals, both loud and both non-destructive: A master key is set. Pre-upgrade shards are plaintext and moving one does not encrypt it; the next boot would wrap a fresh DEK around a file it then failed to decrypt. Move first, encrypt second. A destination is occupied (ErrOccupied). The only way there is booting this binary before the move ran, which creates an EMPTY shard where the full one belongs — and anything started after that boot is in the empty one. Both files are kept, the other tenants still move, and the operator picks which is authoritative. upgrade and migrate stay separate axes: upgrade moves EVERY shard forward in time (layout), migrate moves ONE shard across space (nodes). Also: refuse a socket path over sockaddr_un.sun_path by name. The kernel answers "bind: invalid argument" and names neither the path nor the limit, and a socket under a deployment's data directory sits close to it. Co-authored-by: Hanzo Dev <[email protected]>