Files
922886b949 fix(sync): a refused push stays refused, and says why (BEA-403) (#146)
A device whose journal push the hub 403s reported healthy sync between
every pair of remote passes, and never showed the hub's reason for the
refusal — so a user whose device was not registered to their account
re-ran `bdrive login` (which the message told them to), re-checked their
project permissions (write), and had nowhere left to look.

Two causes, both local to the client:

- Cycle recomputed st.Access from scratch at the end of every pass,
  including the daemon's cheap local-only ticks that never reach the hub.
  Three of those run between remote passes, so the daemon alternated
  "read-only on this project" / "access restored; syncing normally" every
  few seconds and `bdrive status` reported OK moments after a refused
  push. Now each leg records its own verdict — pull clears no-access, push
  records read-only or clears it — and a cycle that asked nothing leaves
  the last answer standing.
- The hub's own sentence was summarized into "read-only (pull only)",
  which describes the STATUS CODE. It is the only thing that tells a
  device-registration refusal from a project the user really is a reader
  on. It now rides in SyncState.AccessReason and Result.Reason(), printed
  by `bdrive sync`, `bdrive status` and the daemon log, and dropped unless
  it passes journal.SafeText — hub text reaching a terminal.

The hub's refusal also now names the upgrade: the binding is made by the
login request naming its device, which a CLI older than the gate does not
do, so "run `bdrive login`" alone sent that user in a circle. Hub and CLI
deploy separately, so the skew is the expected state right after the gate
ships.


Claude-Session: https://claude.ai/code/session_01GSHsQU4pBCzKkPyPeXSwTm

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 20:33:26 -07:00
..