board: 19 → in-progress (istos)
This commit is contained in:
@@ -0,0 +1,100 @@
|
|||||||
|
# 19 — Boards sync through origin/main: push on move, pull on a beat
|
||||||
|
|
||||||
|
**Status:** In Progress
|
||||||
|
**Assignee:** istos
|
||||||
|
**Priority:** High — this is the multi-user feature; 18 without it is bookkeeping
|
||||||
|
**Type:** Feature
|
||||||
|
**Depends on:** 18 — the claim commit is what gets pushed, and its
|
||||||
|
same-commit atomicity is what makes the races below resolve correctly
|
||||||
|
|
||||||
|
One machine's `tasks/` is the truth today; everyone else's is stale
|
||||||
|
until someone remembers to push and pull. Make origin/main the truth
|
||||||
|
and every board a converging replica: board-made moves push
|
||||||
|
immediately, every board pulls on a short beat, remote moves appear in
|
||||||
|
the ticker attributed to their author, and losing a same-card race is
|
||||||
|
a toast, not a mystery.
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
- `watch.py` polls the stage directories every 2s — remote changes
|
||||||
|
that arrive via pull are already noticed and narrated; today they
|
||||||
|
would read "disk", this card upgrades them to the commit author.
|
||||||
|
- Task 14 (landed) already points fresh agent branches at
|
||||||
|
origin/main; this card gives the *board state* the same treatment.
|
||||||
|
- The precondition that makes pulling safe is bench's own discipline:
|
||||||
|
code work lives in worktrees and PRs, so the main checkout stays
|
||||||
|
clean and fast-forwardable. Team mode assumes — and the docs must
|
||||||
|
say — that local main advances only through the board and origin.
|
||||||
|
- Push publishes every local-ahead commit, not just the board's —
|
||||||
|
the piggyback hazard below is the sharpest edge in this card.
|
||||||
|
|
||||||
|
**Affected areas:** a new small `sync.py` (or a sibling thread beside
|
||||||
|
`watch.py`), `config.py` (settings), `state.py`/ticker attribution,
|
||||||
|
AGENTS.md (team-mode discipline).
|
||||||
|
|
||||||
|
## What to build
|
||||||
|
|
||||||
|
- **Gate**: `BOARD_SYNC=1` (default off; implies `BOARD_COMMIT_MOVES`).
|
||||||
|
Off = today's behaviour exactly.
|
||||||
|
- **Push, event-driven**: after each board-made task commit, push. On
|
||||||
|
non-fast-forward: fetch, rebase the board commits, push again. If
|
||||||
|
the rebase conflicts on a task file, the local move loses: revert
|
||||||
|
it, re-read the remote version, and toast who took it
|
||||||
|
("07 claimed by elena — your move was undone").
|
||||||
|
- **Piggyback guard**: before any auto-push, every local-ahead commit
|
||||||
|
on main must be `board: `-prefixed. Anything else → no push, one
|
||||||
|
clear ticker warning naming the stray commit. Never publish a
|
||||||
|
human's unpushed work as a side effect.
|
||||||
|
- **Pull, periodic**: fetch + fast-forward-only merge every
|
||||||
|
`BOARD_SYNC_INTERVAL` (default ~30s) and once at startup. Never
|
||||||
|
pull into a non-clean tree or past a divergence — narrate and wait
|
||||||
|
for a human instead. The watcher then narrates arrived moves with
|
||||||
|
the commit author's name instead of "disk".
|
||||||
|
- **Offline**: unreachable origin degrades to local-only silently
|
||||||
|
sane — commits queue, a quiet ticker note says sync is behind,
|
||||||
|
next successful fetch catches up. No errors every 30s.
|
||||||
|
|
||||||
|
**Out of scope** — tempting neighbours left alone:
|
||||||
|
|
||||||
|
- Reacting to synced state with side effects — replicas render only
|
||||||
|
(task 20 owns the etiquette).
|
||||||
|
- Syncing `local/state/` — liveness stays per-board by design.
|
||||||
|
- Any transport other than git via origin; any branch other than main.
|
||||||
|
|
||||||
|
## Acceptance
|
||||||
|
|
||||||
|
- [ ] Given two clones with `BOARD_SYNC=1`, when A moves a card, then
|
||||||
|
within one beat B's board shows the move and B's ticker
|
||||||
|
attributes it to A's git name.
|
||||||
|
- [ ] Given both move the same card in one window, then exactly one
|
||||||
|
claim survives on origin, the loser's board reverts with the
|
||||||
|
take-over toast, and both converge to the same file bytes.
|
||||||
|
- [ ] Given a human's non-board commit sitting on local main, when
|
||||||
|
the board would push, then it refuses with the named warning
|
||||||
|
and the human's commit stays private.
|
||||||
|
- [ ] Edge case — origin unreachable: boards keep working locally,
|
||||||
|
one quiet ticker note, full convergence after connectivity
|
||||||
|
returns; no error spam.
|
||||||
|
- [ ] With the gate off, no fetch, no push, no behaviour change.
|
||||||
|
|
||||||
|
## Open questions
|
||||||
|
|
||||||
|
- None.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
Middle card of the multi-user arc (18 claim → 19 sync → 20 etiquette).
|
||||||
|
Git is the lock server and main the linearizer: push races are the
|
||||||
|
concurrency control, which is why the claim must ride in the move's
|
||||||
|
own commit (18) and why replicas must not react (20).
|
||||||
|
|
||||||
|
**Risks**
|
||||||
|
|
||||||
|
- ff-only pulls plus the piggyback guard mean a divergent main stalls
|
||||||
|
sync until a human resolves it — correct, but the stall must be
|
||||||
|
loudly visible or two halves of a team quietly stop converging.
|
||||||
|
- A 30s beat × N boards is N fetches/min against origin — fine for
|
||||||
|
GitHub, worth a doc line for rate-limited setups.
|
||||||
|
- Reverting a lost move while the user is mid-drag needs care in the
|
||||||
|
UI — the board re-reads disk on every render, which should make
|
||||||
|
this safe, but test the race deliberately.
|
||||||
Reference in New Issue
Block a user