Skip to content
edgesync Check-in [40c9b9aff3]
Login

Check-in [40c9b9aff3]

Overview
Comment:feat(hub): Hub.EnsureUser — idempotent user-table registration (closes #183) (#184)

Add Hub.EnsureUser(login, caps string) error as an idempotent counterpart to Hub.AddUser. Consumers (sesh, others) need "register if absent" semantics; AddUser errors on duplicate, forcing each consumer to write check-then-act + race-recheck wrappers. The user-table layer is the right place to own that idempotency.

Implementation choice (a) — GetUser → CreateUser, serialized by a Repo- level mutex. Picked over (b) "catch CreateUser's already-exists error" because libfossil's CreateUser surfaces a wrapped SQLite UNIQUE-constraint error with no sentinel or typed shape — string-matching would be brittle. A defensive GetUser retry after CreateUser failure absorbs the rare cross- process race where the in-process mutex doesn't apply.

Contract: existing caps are PRESERVED on duplicate (documented). Callers wanting replace-on-conflict use SetUserCaps explicitly.

Tests cover first-time registration, duplicate no-op, caps-change-on- duplicate (preservation), 5-goroutine concurrent race (exactly one row lands), and empty-login rejection. Full hub package + leaf + bridge + CLI suites pass; go vet clean; gofmt clean.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

Timelines: family | ancestors | descendants | both | trunk | main
Files: files | file ages | folders
SHA3-256: 40c9b9aff3bde3aa74c81939b13f01e95c5774335b75fb84d521fc1ecffba77b
User & Date: [email protected] on 2026-05-19 16:25:25.000
Other Links: branch diff | manifest | tags
Context
2026-05-19
17:09
fix(hub): resolve TestNotifyCLIPairAndDevices flake (closes #170) (#186)

* fix(hub): resolve TestNotifyCLIPairAndDevices flake (closes #170)

Each of the 4 tests in cmd/edgesync/notify_test.go independently invoked `go build` for the edgesync binary inside its own body. Under cold build cache or parallel test load, this build storm pushed the package's total runtime past `go test -short -timeout=30s ./...` (the pre-push hook gate) and the suite-level timeout fired — surfacing as a hang on os/exec.Wait since the inner `go build` child was still running when the supervisor killed the test process.

Move the build into TestMain so the binary is built once and reused. With -timeout=30s on cold cache, cmd/edgesync drops from ~9s to ~3s and the target test from ~2.8s to ~0.07s — comfortably clear of the timeout.

No production-code change. No sleep band-aids. Verified 200/200 passes (50× per test in the package) and 80/80 under -race.

* fix(test): clean up temp dir before os.Exit (Ousterhout follow-up to PR 186)

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> check-in: 37780572c3 user: [email protected] tags: trunk, main

16:25
feat(hub): Hub.EnsureUser — idempotent user-table registration (closes #183) (#184)

Add Hub.EnsureUser(login, caps string) error as an idempotent counterpart to Hub.AddUser. Consumers (sesh, others) need "register if absent" semantics; AddUser errors on duplicate, forcing each consumer to write check-then-act + race-recheck wrappers. The user-table layer is the right place to own that idempotency.

Implementation choice (a) — GetUser → CreateUser, serialized by a Repo- level mutex. Picked over (b) "catch CreateUser's already-exists error" because libfossil's CreateUser surfaces a wrapped SQLite UNIQUE-constraint error with no sentinel or typed shape — string-matching would be brittle. A defensive GetUser retry after CreateUser failure absorbs the rare cross- process race where the in-process mutex doesn't apply.

Contract: existing caps are PRESERVED on duplicate (documented). Callers wanting replace-on-conflict use SetUserCaps explicitly.

Tests cover first-time registration, duplicate no-op, caps-change-on- duplicate (preservation), 5-goroutine concurrent race (exactly one row lands), and empty-login rejection. Full hub package + leaf + bridge + CLI suites pass; go vet clean; gofmt clean.

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> check-in: 40c9b9aff3 user: [email protected] tags: trunk, main

2026-05-17
18:47
fix(hub): address pulls to publishing peer to resolve .sync race (#162) (#181)

Three+ hubs sharing a project-code all subscribed to the same broadcast `<prefix>..sync` subject. A `.commit` notification on any one of them caused every other peer to fan out a nats.Request on .sync; every subscribed peer answered, but nats.Request only delivers the first reply. If a stale peer won that race the pull converged against zero blobs and the receiver silently stayed behind.

Two changes close the gap:

1. Each hub now also subscribes to a per-peer addressed sync subject `<prefix>..sync.<peer-id>` (peer-id = the hub's NATS server name). The commit notification carries the publishing hub's peer-id; receivers route their pull-back at that addressed subject so exactly that hub responds. No race regardless of how many other hubs share the project-code.
2. The broadcast `.sync` subscription becomes a NATS queue group (`sync-<project-code>`) so external leaf-agent pushes — which still target the broadcast subject — get exactly one responder per request instead of a fan-out race. Project-code in the queue name scopes the group so unrelated projects sharing a NATS server can't cross-pollinate.

The receiver also drops self-notifications (a publish from this hub round-tripping back via an upstream leaf, which NoEcho doesn't catch across leaf links) and notifications missing a peer-id (older publisher that pre-dates this fix — refusing rather than falling back to a broadcast pull keeps #162 from regressing silently).

`TestCrossLeaf_ThreeHubs_CommitAnnouncePropagatesToAll` is now ungated: it ran red under both -short and full modes before this fix, and passes under both modes (and 10x stability check) after.

Closes #162 Closes #179

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> check-in: a96fe486ab user: [email protected] tags: trunk, main

Changes