50 descendants and 44 ancestors of 49b9471550a37004
|
2026-05-19
| ||
| 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>. Two changes close the gap:
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> | |
| 17:44 |
test(hub): add 3-hub cross-leaf propagation test (#180)
Mirrors the 2-node TestCrossLeaf_SharedProjectCode_PropagatesCommit shape but with a third leaf attached to the same topology root. Asserts that a commit on any peer propagates to BOTH of the other two peers, both directions. Reproduces the cross-leaf propagation gap reported in #179 and confirms its underlying cause is the multi-responder .sync race filed as #162: with 3+ hubs subscribing to the same .sync subject every peer answers nats.Request, only the first reply wins, and a stale responder silently starves the pull. The first hop (B -> A) is already enough to surface this — A's .sync request can race against C's empty responder even though A and B are the only nodes that need to talk for that direction. Gated under -short with a skip pointing to #162 so pre-commit and CI stay green. Without -short the test fails at the 10s wait in ~10s, demonstrating the bug. Remove the skip once #162 is fixed; the test then doubles as the regression sentinel for the 3+ hub topology that production sesh relies on. No production code changes. Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> check-in: 5c36d5498d user: [email protected] tags: trunk, main | |
|
2026-05-16
| ||
| 18:01 | chore: bump submodule pins (leaf/v0.0.12,bridge/v0.0.4) leaf check-in: 5070f96573 user: 41898282+github-actions[bot]@users.noreply.github.com tags: trunk, v0.0.20 | |
| 17:55 |
feat(hub): expose embedded NATS WebSocket endpoint (#177)
Adds Config.EnableWebSocket (opt-in; default off for security since no TLS/WSS is wired up here) plus Config.NATSWebSocketPort (0 = auto-pick, mirroring the sibling NATSClientPort / NATSLeafPort convention). When enabled, the embedded nats-server binds a WebSocket listener on 127.0.0.1:NNNN with NoTLS=true. Surfaces the resolved listener URL via the new Hub.NATSWebSocketURL() getter, which returns "ws://127.0.0.1:NNNN" when WebSocket is enabled and "" otherwise — same shape as NATSURL() / LeafURL(). Tests cover the disabled-by-default contract, the auto-pick URL shape, and an end-to-end round trip: a nats.go client dials the ws:// URL, subscribes to a subject, and receives a payload published by a TCP client on the same embedded server. Fixes #176 Co-authored-by: Dan Mestas <[email protected]> check-in: ae52558ba4 user: [email protected] tags: trunk, leaf/v0.0.12, bridge/v0.0.4 | |
|
2026-05-14
| ||
| 16:08 |
feat(agent): implement AutosyncCommit on libfossil v0.6.2 Checkout API (#173) (#174)
The v0.2.0 deferral premise — "Checkout type is internal to libfossil" — is stale: v0.6.2 (current dep) exposes Checkout as a public handle with Version, WouldFork, Checkin, HasChanges, plus Repo.OpenCheckout/CreateCheckout. This wires up the long-deferred autosync workflow on that public API: pull -> fork check -> checkin -> push, with AutosyncOff/On/PullOnly modes. Errors are typed: ErrWouldFork on detected fork (without creating a checkin), and *PushFailedError when the local checkin succeeds but the follow-up push fails — the latter carries the local rid/uuid so callers can decide whether to retry, abandon, or surface the partial state. Sync.Push and Sync.Pull on the input opts are now rejected (loud failure beats silent override) since AutosyncCommit drives them per-mode. ErrCkinLockHeld is removed — it was an exported sentinel that no code path ever returned, so it would have invited misleading errors.Is checks downstream. When libfossil exposes a real lock-held signal, the sentinel can come back with a return path. The four previously-skipped tests are restored as real coverage. The fork-test fixture clones a "remote" repo from local at C0 via httptest (HandleSync doesn't speak the clone protocol — CDG-148), then commits C1 on the remote so the pull genuinely brings it back to local and triggers WouldFork. Three additional tests cover OffMode's branch-tip advance, rejection of caller-set Push/Pull, and PushFailedError's unwrap/format contract. Closes #173 check-in: ebcb65b235 user: [email protected] tags: trunk | |
| 09:32 |
feat(hub): expose Ready() channel for serve-loop start barrier (#172)
Adds Hub.Ready() returning a channel closed when ServeHTTP has wired up the http.Server and is about to enter the Accept loop. Consumers that advertise hub URLs externally (e.g. sesh writing session state JSON) can wait on Ready() before publishing, closing the window where the TCP listener is bound but the goroutine running ServeHTTP hasn't started processing requests. Closes #171. Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> check-in: 0f4bfb32df user: [email protected] tags: trunk | |
|
2026-05-13
| ||
| 21:03 |
fix: exclude modernc driver registration from wasm builds (#169)
The init()-based modernc registration added in #166 (v0.0.18) blocked wasm builds because modernc references syscalls unavailable under js/wasm. Adds a //go:build !js && !wasm constraint to hub/driver.go so the registration is skipped on wasm targets — wasm consumers register their own driver (libfossil's ncruces driver supports wasm via its ncruces_js.go file). Native builds are unchanged: hub still auto-registers modernc. Wasm builds: hub package now compiles successfully under GOOS=js GOARCH=wasm; consumer brings its own driver. Verified: GOOS=js GOARCH=wasm go vet ./hub/... # clean GOOS=js GOARCH=wasm go build ./hub/... # success go test ./hub/... -timeout 120s # 38/38 pass on native Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> check-in: 0fa13d595b user: [email protected] tags: trunk, v0.0.19 | |
| 20:49 |
chore: bump modernc driver to v0.2.0 (#168)
Picks up the _txlock=immediate DSN fix from libfossil#33/#34 so concurrent writers serialize at BEGIN (where busy_timeout applies) instead of racing the SHARED→RESERVED upgrade. Required upstream for sesh's --scope=project mode to remain safe after sesh drops its direct libfossil/db/driver/modernc require. Without this bump, EdgeSync's hub init() (added in v0.0.18) would register a pre-fix driver and consumers regress on contention. Closes #167. Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> check-in: d5a46f76de user: [email protected] tags: trunk | |
| 20:27 |
refactor(hub): register modernc SQL driver internally (#166)
The hub package promised consumers wouldn't need to import libfossil directly, but they still had to blank-import libfossil's SQL driver to satisfy the underlying db layer's registration requirement. Move the blank import into hub itself (hub/driver.go) so the package fulfills its own dependency. Consumers like sesh can now reduce their main.go to a plain `import "github.com/danmestas/EdgeSync/hub"` (or whatever wrapper they use) without taking a direct dep on libfossil's internal SQL machinery. modernc is registered as the production default. If a consumer ever needs ncruces (WASM/WASI), an escape hatch can be added then. Tests in hub/ drop their now-redundant blank imports. Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> check-in: 754914fd82 user: [email protected] tags: trunk, v0.0.18 | |
| 00:13 |
refactor(agent): drop mergeWithParent now that libfossil v0.6.2 ships the fix (#165)
libfossil#30 / EdgeSync#163 shipped consumer-side and upstream respectively. With libfossil v0.6.2 (PR #32, commit e6d34fb), Repo.Commit merges parent F-cards with supplied Files internally when ParentID > 0 — the exact behavior agent.mergeWithParent was emulating. Drop the duplicate. Repo policy (CLAUDE.md "Upstream-fix policy") is unambiguous: when a fix lands in a repo we own, the workaround in the consuming repo should be removed, not left as defense-in-depth. libfossil is ours; a regression there gets fixed there. Bumps: - root go.mod: v0.6.1 → v0.6.2 - leaf/go.mod: v0.5.0 → v0.6.2 (was lagging; auto-bump workflow has been failing per #146/#158) - bridge/go.mod: v0.5.0 → v0.6.2 (same) Keeps the regression tests TestAgent_Commit_PreservesParentFiles and TestAgent_Commit_SuppliedFileOverridesParent — they assert behavior, not implementation, and now serve as integration coverage so any future libfossil regression on full-tree manifest emission surfaces in EdgeSync CI too. Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> check-in: fabb4e3453 user: [email protected] tags: trunk | |
|
2026-05-12
| ||
| 23:50 |
feat(hub): persist .commit publish watermark across hub restarts (#164)
publishedRID was in-memory only. initPublishWatermark snapshotted the current max ci rid on every start, so any commit that landed in the repo but didn't get its .commit notification out before the hub died (crash mid-publish, HTTP push that completed the SQLite write but crashed before the wrapper ran publishNewCommits, or an offline tool writing to the repo while the hub was down) became silently un-propagated. Peers caught up incidentally on the next commit or not at all. Real gap. Persist the watermark to the repo's config table under "edgesync:hub-published-rid" after each successful publish. On startup, initPublishWatermark reads the persisted value first and falls back to the max-rid snapshot only when nothing is persisted (cold start). Schedule a one-shot publishNewCommits call publishCatchupDelay (3s) after NewHub so it runs after NATS leaf clients have had time to reconnect on the same leaf port — an immediate catchup fires into a void because nats-core has no per-subscriber queuing. Stop cancels the pending catchup via catchupStop so the goroutine doesn't outlive the hub. Regression test TestCrossLeaf_HubRestart_RepublishesUnannouncedCommits: two hubs leaf-linked on a stable leaf port; stop A, open the repo file directly via libfossil and commit (no hub, no NATS, no publish hook), restart A; assert B reads the offline file via the delayed startup catchup. The "open repo file directly" trick is morally equivalent to "hub crashed after libfossil's write but before the wrapper's publish" — both produce a commit that's in the repo but unannounced. Known limitations (deferred): - If no peer reconnects within publishCatchupDelay, the catchup publish is still lost. JetStream-durable .commit subject would close the gap; tracked as a follow-up. - The .commit subscriber on the pull side doesn't advance the receiving hub's watermark, so a pulled-in commit will be republished on the receiver's next restart. Idempotent on the far side (the subscriber's pull converges to zero blobs) but wasteful chatter; can be tightened in a follow-up. - v1→v2 upgrade with un-announced backlog at the time of upgrade treats those commits as "already known" — same silent-loss behavior as v1; not made worse. Refs #161 review notes. Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> check-in: 00bc87892b user: [email protected] tags: trunk | |
| 23:38 |
fix(agent): preserve parent's tracked files in agent.Commit (#152) (#163)
agent.Commit passed opts.Files straight through to libfossil.Commit, which emits a partial manifest containing only the supplied subset. Parent files not in opts.Files were silently dropped — bones' multi-slot workflow (each slot commits only its own paths on a linear trunk chain) materialised only the last slot's files into git, every prior slot's work lost. Bug chains to libfossil#30, but EdgeSync can shield consumers regardless of upstream's resolution. opts.Files now reads with `fossil ci` semantics — "files this commit changes." A new mergeWithParent helper enumerates the parent's F-cards, overlays supplied files (supplied wins by name), and hands the full set to libfossil.Commit. Inherited files preserve the parent's Perm; supplied files get libfossil's default (agent.FileToCommit has no Perm field today). parentID==0 stays an orphan root. Two regression tests: - TestAgent_Commit_PreservesParentFiles — disjoint paths (alpha, bravo) on a linear chain. Both must surface at trunk's tip. - TestAgent_Commit_SuppliedFileOverridesParent — same-name supplied content must win over parent's. Tests verify against BranchTip + ReadFile directly. a.Read can't be used because libfossil ResolveVersion("trunk") goes through the sym-trunk singleton tag, which libfossil.Commit doesn't propagate forward; only the first commit on trunk is reachable that way. Orthogonal to #152; flagging separately. Closes #152 Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> check-in: 7f0fbfa6ef user: [email protected] tags: trunk | |
| 23:19 |
feat(hub): auto-publish on HTTP xfer push (#160) (#161)
`fossil push <hub-http>` from an external CLI agent writes artifacts straight into the SQLite repo via libfossil's xfer protocol, bypassing Repo.Commit and therefore the .commit auto-publish hook installed by NewHub. Peers stay at the prior commit count even though the push succeeded. Funnel the publish through a new Repo.publishNewCommits helper that enumerates 'ci' events past a per-repo watermark and emits one notification per rid in order. Both Repo.Commit and a thin wrapper around XferHandler in Hub.ServeHTTP call it; the watermark is seeded to the current max ci rid in startCommitSubscriber so SeedFromUpstream clones don't republish their history. publishMu serialises the scan/publish/advance triple so concurrent local-commit and HTTP-push callers cannot republish the same rid. Regression test TestCrossLeaf_HTTPPush_PropagatesCommit mirrors TestCrossLeaf_SeedFromUpstream_PropagatesCommit: two leaf-linked hubs plus a standalone libfossil.Repo as the external agent that clones from A, commits locally, and pushes back via HTTP. Asserts B reads the new file within 10s. Closes #160 Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> check-in: 3fa4ea607f user: [email protected] tags: trunk | |
| 21:09 |
feat(cli): expose hub --project-code and --seed-from-upstream flags (#159)
The hub.Config.ProjectCode and hub.Config.SeedFromUpstream fields landed in #157 but were unreachable from the `edgesync hub serve` CLI. Wires them through and fixes a latent auth bug in seedFromUpstream that prevented it from cloning against the standard nobody-caps=gio deployment. Changes: * cli/hub_serve.go — adds --project-code (40-char lowercase hex, format pre-validated for a friendlier error than libfossil's) and --seed-from-upstream (http(s) URL of upstream hub xfer endpoint). * hub/clone.go — drop bootstrapUser from the clone CloneOpts. Passing a local-creation user as the upstream-auth identity caused libfossil's clone to send a login card for a user the upstream had never heard of ("authentication failed"). Anonymous clone falls through to the upstream's "nobody" caps, matching the typical NobodyCaps:"gio" deploy. * hub/cross_leaf_test.go — adds TestCrossLeaf_SeedFromUpstream_PropagatesCommit covering the seed-then-sync path end-to-end (clone over HTTP, leaf-link, observe A→B commit propagation via NATS). Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> check-in: e8190a881f user: [email protected] tags: trunk, v0.0.17 | |
| 20:59 |
feat(cli): expose hub --project-code and --seed-from-upstream flags
The hub.Config.ProjectCode and hub.Config.SeedFromUpstream fields landed in #157 but were unreachable from the `edgesync hub serve` CLI. Wires them through and fixes a latent auth bug in seedFromUpstream that prevented it from cloning against the standard nobody-caps=gio deployment. Changes: * cli/hub_serve.go — adds --project-code (40-char lowercase hex, format pre-validated for a friendlier error than libfossil's) and --seed-from-upstream (http(s) URL of upstream hub xfer endpoint). * hub/clone.go — drop bootstrapUser from the clone CloneOpts. Passing a local-creation user as the upstream-auth identity caused libfossil's clone to send a login card for a user the upstream had never heard of ("authentication failed"). Anonymous clone falls through to the upstream's "nobody" caps, matching the typical NobodyCaps:"gio" deploy. * hub/cross_leaf_test.go — adds TestCrossLeaf_SeedFromUpstream_PropagatesCommit covering the seed-then-sync path end-to-end (clone over HTTP, leaf-link, observe A→B commit propagation via NATS). leaf check-in: 001f82d3d2 user: 41898282+github-actions[bot]@users.noreply.github.com tags: trunk, feat/cli-project-code-and-seed-from-upstream | |
| 20:27 |
feat(hub): cross-leaf fossil sync over NATS (#156) (#157)
Add Config.ProjectCode + Config.SeedFromUpstream so multiple hubs sharing a project-code propagate commits over a single .sync subject. Adds an auto-publish notification on a sibling .commit subject + pull-on-notify subscriber, closing the cross-process Fossil sharing gap from #156. - hub/nats_transport.go: libfossil.Transport over nats request/reply - hub/clone.go: HTTP-based SeedFromUpstream helper (NATS-clone deferred) - hub/repo.go: Repo.publish hook fired after successful Commit - hub/hub.go: startCommitSubscriber + Config plumbing + NoEcho - hub/cross_leaf_test.go: gold-path propagation, subject alignment, drift guard - libfossil pin -> v0.6.1 (CreateOpts.ProjectCode) Design: docs/superpowers/specs/2026-05-12-cross-leaf-fossil-sync-design.md Protocol: docs/architecture/sync-protocol.md ("NATS-Wrapped Sync") Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> check-in: 6f9e3e40d0 user: [email protected] tags: trunk | |
|
2026-05-11
| ||
| 17:56 |
refactor(hub): rename Hub.LeafUpstream() → Hub.LeafURL() (#155)
The getter returns the local leafnode listener URL — what remote leaves dial as their upstream. The name LeafUpstream collided with Config.LeafUpstream (the input URL THIS hub solicits to), making the API read backwards: same word, opposite directions on the same struct family. LeafURL is symmetric with the existing NATSURL() and matches the data: "the URL of my leaf listener". Config.LeafUpstream stays — that's correctly named on the input side. Mechanical changes: - (h *Hub) LeafUpstream() → (h *Hub) LeafURL() - private field h.leafUpstream → h.leafURL - hub_test.go callers updated - cli/hub_serve.go log key "leaf_upstream" → "leaf_url" No callers outside this repo in the v0.0.16 dependency graph (bones pins v0.0.16). Downstream consumers on a local replace (sesh) get a one-line update. Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> check-in: 385682e7ce user: [email protected] tags: trunk | |
| 17:13 |
feat(hub): expose Hub.NumLeafs() for auto-shutdown semantics (#154)
Adds a thin getter over the embedded NATS server's NumLeafNodes(). Consumers that want to implement a "hub auto-shuts down when the last leaf disconnects" lifecycle can poll this — that's the immediate use case from the sesh wrapper spike, where the hub spawns on first session and exits on last. Three lines, no behavior change. Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> check-in: 4caf310202 user: [email protected] tags: trunk | |
| 15:26 |
feat(hub): machine-level hub CLI + ServerName / LeafUpstream config (#153)
Introduces `edgesync hub serve` and extends the hub package so a hub can be named at the NATS level and optionally solicit a leaf connection upstream. This is the substrate piece; session/agent vocabulary lives in a consumer (sesh) above this layer. hub.Config: - ServerName — sets opts.ServerName on the embedded NATS server; surfaces in leafnode handshakes, varz, gossip. Empty leaves nats-server's auto-generated random name. - LeafUpstream — optional URL to solicit a leafnode connection toward. Wired to opts.LeafNode.Remotes. Lets one hub act as a leaf of another, enabling multi-hub topologies. - NATSStoreDir default renamed `nats-store` → `messaging` — implementation- agnostic on disk; contents underneath are still NATS JetStream format. The config field name stays `NATSStoreDir` (Go code is honest about what it is). hub.Hub.ServerName() — getter returning the live identity (Config.ServerName or the auto-generated name). cmd: `edgesync hub serve --name=<server-name> --leaf-upstream=<url> ...` defaults --name to <hostname>-hub and repo path to ~/.edgesync/<name>/hub.repo. docs/architecture/hub-package.md updated to reflect the new fields, the messaging/ default, and a generic repo-file naming convention (the package itself doesn't impose a filename; the CLI defaults to hub.repo). Verified end-to-end: - Hub solicits a hub: bidirectional pub/sub flows across two hubs on this machine via the leafnode handshake. - 3-edge star: 1 root hub + 3 leaf processes — fan-in, cross-leaf, subject filter all behave as expected. - ServerName visible at server-side truth (h.server.Name()). - 31 tests pass, vet clean. Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> check-in: f3d5aa96bc user: [email protected] tags: trunk | |
|
2026-05-06
| ||
| 21:17 | chore: bump submodule pins (leaf/v0.0.11) check-in: 8871efad56 user: 41898282+github-actions[bot]@users.noreply.github.com tags: trunk, v0.0.16 | |
| 21:15 |
feat(leaf/agent): CommitOpts.Tags — symmetric to hub.CommitOpts.Tags (#150)
Adds Tags []TagSpec to agent.CommitOpts (mirroring hub.CommitOpts from v0.0.15) and plumbs it straight through to libfossil.CommitOpts.Tags. Synthetic agent slots (bones ADR 0050) commit through the leaf path, so the hub-only change in #148 didn't reach them. With this, callers can land commits on agent/<id> branches via the propagating fossil branch tag pair. Closes #149. check-in: aa902c6e06 user: [email protected] tags: trunk, leaf/v0.0.11 | |
| 20:40 |
feat(hub): CommitOpts.Tags — attach tags at commit time (incl. branch=) (#148)
Plumbs libfossil's `Tags []TagSpec` primitive through `hub.CommitOpts` so callers can land a checkin on a non-current branch (or attach any other tag) without going around the EdgeSync API to libfossil. Fossil branches are propagating tags: `branch=<name>` + `sym-<name>=*` together place a checkin on a branch. Exposing `Tags` rather than a git-flavored `Branch string` keeps the API fossil-native and is strictly more expressive — any tag (closed, release, custom) can be attached at commit time, not just branch creators. Empty `Tags` preserves the existing trunk-default behavior; current callsites compile and run unchanged. Closes #147 check-in: 7536beb856 user: [email protected] tags: trunk, v0.0.15 | |
|
2026-05-05
| ||
| 16:00 |
chore: bump goreleaser-action v6 → v7 (Node 24) (#145)
v6 runs on Node.js 20, deprecated 2026-09-16. v7.0.0 (released 2026-02-21) moves to Node 24. The latest v7.2.1 was published 2026-04-26. Release pipeline only — no consumer impact. check-in: 6caf568e6c user: [email protected] tags: trunk | |
| 15:42 |
fix(hub): periodic WAL checkpoint so vanilla fossil can read hub.fossil (#144)
Hub.fossil opens in WAL journal mode, so without checkpointing the on-disk file stays as a 4 KiB stub plus a multi-hundred-KiB *-wal sidecar — vanilla fossil tooling (fossil ui / fossil info / fossil timeline) refuses it because its validity probe stats the main file before consulting the WAL. Wire a goroutine in NewHub that runs PRAGMA wal_checkpoint(PASSIVE) every Config.CheckpointInterval (default 10s) so vanilla fossil can always read hub.fossil while the hub is running. PASSIVE never blocks readers or writers. Hub.Stop drains the goroutine before closing the libfossil handle; libfossil v0.6.0's own (*Repo).Close runs a TRUNCATE checkpoint, so the post-Stop on-disk file is self-contained. Closes #143. Bumps libfossil v0.5.0 → v0.6.0 (danmestas/libfossil#28). check-in: 72cfc528b2 user: [email protected] tags: trunk, v0.0.14 | |
| 00:41 | chore: bump submodule pins (bridge/v0.0.3) check-in: 5a798c1a36 user: 41898282+github-actions[bot]@users.noreply.github.com tags: trunk, v0.0.13 | |
|
2026-05-04
| ||
| 23:42 |
chore: bump actions/checkout v4→v6 + actions/setup-go v5→v6 (#142)
Quiets the Node.js 20 deprecation annotation that surfaced on the auto-release.yml dry-run. v6 of both actions ships on Node.js 24, ahead of the June 2026 forced-upgrade. No behavior change expected — same API surface. check-in: 0707c5f3d4 user: [email protected] tags: trunk, bridge/v0.0.3 | |
| 23:30 |
chore: add auto-release workflow + scripts/release.sh (#141)
Mechanizes the multi-step release flow that today is hand-driven via: 1. tag leaf submodule, push 2. open chore/bump-leaf PR, merge 3. tag root, push (fires release.yml + bones dispatch) scripts/release.sh detects which submodules have changes since their last <name>/v* tag, computes next versions (patch by default; --bump= minor|major), tags and pushes them, rewrites the matching require pin in root go.mod, commits straight to main as github-actions[bot], then tags the next root v* and pushes — firing release.yml. Local invocation prompts before each push so the operator can intervene. Workflow invocation (auto-release.yml) passes --ci to suppress prompts and uses the bot identity. --dry-run reports the plan without touching anything. Direct-to-main bot commits is the right tradeoff here: the change is deterministic robot work (one-line go.mod edit derived from the just- tagged version) and avoiding a PR round-trip lets a release ship in a single workflow run rather than coordinating across two. If branch protection blocks the github-actions bot, configure RELEASE_PUSH_TOKEN as a fine-grained PAT with contents:write. Side observation: dry-run flags that bridge/ has unreleased changes since bridge/v0.0.2 (libfossil bump + README), so the next auto-release run will tag bridge/v0.0.3 alongside whatever else changes. check-in: 8414300b13 user: [email protected] tags: trunk | |
| 22:15 |
chore: bump leaf v0.0.9 → v0.0.10 (#139)
Bumps the root module's leaf pin from `v0.0.9` to `v0.0.10`. `leaf/v0.0.10` is already tagged and pushed. `leaf/v0.0.10` ships: - **feat(leaf/agent)** Config.CloneFromHubURL + agent.CloneRepo helper (#137 gap A, PR #138). Lets downstream consumers bootstrap a leaf repo against an existing hub without importing libfossil directly. After this merges, the next root tag (`v0.0.12`) cuts the full release including the matching hub additions (#137 gaps B + C, also PR #138) — that's enough for bones to drop libfossil from go.mod entirely. check-in: ddb923acb9 user: [email protected] tags: trunk, v0.0.12 | |
| 22:05 |
feat: close three gaps blocking bones go.mod libfossil drop (#137) (#138)
* feat(leaf/agent): clone-if-absent via Config.CloneFromHubURL (#137 gap A) Adds agent.Config.CloneFromHubURL — when set and Config.RepoPath does not yet exist on disk, agent.New clones the leaf repo from the hub HTTP URL before opening it. Matching project-codes between leaf and hub are required for downstream NATS sync subjects to align, so callers that need to bootstrap a leaf against an existing hub no longer have to import libfossil directly for the pre-agent clone step. Also exposes agent.CloneRepo(ctx, repoPath, hubURL) as a standalone helper for callers that need a clone independent of agent construction. Existing behavior is preserved: when CloneFromHubURL is empty, agent.New errors on missing RepoPath as before (covered by TestAgentNew_RejectsMissingRepoWithoutCloneURL). Closes part of #137 (gap A — bones internal/coord/leaf.go). * feat(hub): subscribe to fossil-sync NATS subject in NewHub (#137 gap B) NewHub now connects a local NATS client to its embedded server and subscribes to "fossil.<project-code>.sync" so peer leaf agents can push xfer requests to the hub over NATS — alongside the existing HTTP path. Previously, achieving this required running a leaf-agent in serve mode on top of the hub (Pull=false, Push=false, Autosync=AutosyncOff, ServeNATSEnabled=true), which transitively pulled libfossil into the consumer's import graph. The subscriber is enabled by default. Read-only HTTP-only mirrors that shouldn't accept NATS pushes can opt out via Config.DisableFossilSyncOverNATS. Note on naming: the issue brief proposed Config.ServeFossilSyncOverNATS defaulting to true. Go bool zero-value semantics make default-true awkward without pointers, so the inverted-name shape (DisableX, defaults false = enabled) was used instead. Closes part of #137 (gap B — bones internal/coord/hub.go). * feat(hub): FileToCommit.Perm + CommitOpts.Time pass-through (#137 gap C) Adds two additive fields to the public hub commit surface so consumers no longer need libfossil-direct access to preserve executable bits or seed reproducible commit timestamps: - hub.FileToCommit.Perm — "x" marks the file executable on the resulting manifest's F-card. Empty preserves prior behavior. Other libfossil perm markers pass through verbatim. - hub.CommitOpts.Time — explicit commit timestamp; zero defaults to time.Now().UTC() at commit time, matching the prior behavior. Tests cover round-tripping the executable bit via libfossil.Repo.ListFiles, honoring an explicit time via Timeline, and falling back to "now" on the zero value. Closes part of #137 (gap C — bones internal/hub/hub.go seed path). * ci: include #137 sim tests in the serve-tests allowlist Adds TestAgentNew_ and TestHubNATSFossilSync_ to the sim serve-tests regex in both Makefile and test.yml so the new clone-if-absent and hub-NATS-subscriber tests actually run in CI alongside TestHubLeafE2E. check-in: ad1d55f5b9 user: [email protected] tags: trunk, leaf/v0.0.10 | |
| 21:08 |
chore: bump leaf v0.0.8 → v0.0.9 (#136)
Pulls in the Agent.Commit ParentID/MergeParents fix (#125, PR #133). Without it, agent.Commit produces orphan checkins that fragment the timeline. Required so downstream consumers can pin to a root release exposing the fix. check-in: 2945dc8dce user: [email protected] tags: trunk, v0.0.11 | |
| 21:04 |
chore: bump leaf v0.0.8 → v0.0.9
Pulls in the Agent.Commit ParentID/MergeParents fix (#125, PR #133). Without it, agent.Commit produces orphan checkins that fragment the timeline. Required so downstream consumers can pin to a root release exposing the fix. leaf check-in: ab5b55e3b0 user: [email protected] tags: trunk, chore/bump-leaf-v0.0.9 | |
| 20:59 |
feat(hub): SetUserCaps + Config.NobodyCaps for unauth-clone hubs (#135)
Adds Repo.SetUserCaps(login, caps) and a Hub delegate, wrapping libfossil's Repo.SetCaps. Lets consumers grant or change caps on existing users without leaving the hub package — primarily for the common bones case where the libfossil-pre-populated 'nobody' user needs 'gio' to allow unauthenticated clone/pull/push. Also adds Config.NobodyCaps so the typical setup is one declarative field instead of a manual SetUserCaps call after every NewHub. NobodyCaps is applied at bootstrap by calling SetUserCaps("nobody", v) internally — no method-body duplication. Empty NobodyCaps leaves nobody at whatever libfossil set it to (idempotent re-open path preserves prior caps, doesn't overwrite). Tests: - TestRepo_SetUserCaps_RoundTrip — set + read back - TestRepo_SetUserCaps_RejectsEmptyLogin - TestNewHub_NobodyCaps_AppliedAtBootstrap — Config field works - TestNewHub_NobodyCaps_Idempotent — re-running with same caps OK - TestNewHub_NobodyCaps_EmptyDoesNotOverwrite — empty doesn't reset Closes #130 check-in: 93ab4aa0e9 user: [email protected] tags: trunk, leaf/v0.0.9 | |
| 20:53 |
feat(hub): SetUserCaps + Config.NobodyCaps for unauth-clone hubs
Adds Repo.SetUserCaps(login, caps) and a Hub delegate, wrapping libfossil's Repo.SetCaps. Lets consumers grant or change caps on existing users without leaving the hub package — primarily for the common bones case where the libfossil-pre-populated 'nobody' user needs 'gio' to allow unauthenticated clone/pull/push. Also adds Config.NobodyCaps so the typical setup is one declarative field instead of a manual SetUserCaps call after every NewHub. NobodyCaps is applied at bootstrap by calling SetUserCaps("nobody", v) internally — no method-body duplication. Empty NobodyCaps leaves nobody at whatever libfossil set it to (idempotent re-open path preserves prior caps, doesn't overwrite). Tests: - TestRepo_SetUserCaps_RoundTrip — set + read back - TestRepo_SetUserCaps_RejectsEmptyLogin - TestNewHub_NobodyCaps_AppliedAtBootstrap — Config field works - TestNewHub_NobodyCaps_Idempotent — re-running with same caps OK - TestNewHub_NobodyCaps_EmptyDoesNotOverwrite — empty doesn't reset Closes #130 leaf check-in: a8e62ce82d user: [email protected] tags: trunk, feat/130-hub-setusercaps | |
| 20:51 |
feat(hub): add OpenRepo + *hub.Repo type for server-less operations (#134)
Adds hub.OpenRepo(path) (*Repo, error) — a server-less handle for one-off CLI invocations and short-lived helpers (bones #186 needs this for `bones hub user list`, `bones plan finalize`, and slot-user provisioning, all of which currently drop to direct libfossil.Open because hub.NewHub spins up a redundant NATS server every time). The new *hub.Repo type holds the libfossil handle and carries every user / commit / read method that *hub.Hub used to define on itself. *hub.Hub now holds a *hub.Repo internally and one-line-delegates each method, so both code paths share method bodies — no duplication. Adds Hub.Repo() *Repo accessor for sharing the live handle out of a running hub. Documented contract: don't call Close on a *Repo returned by Hub.Repo while the Hub is still serving — Hub.Stop is the right teardown for that case. Repo.Close is idempotent so accidental double- close from teardown ordering is safe. Tests: - TestOpenRepo_RoundTrip — every *Repo method against a path bootstrapped by NewHub. Same observable state. - TestOpenRepo_RejectsEmptyPath, TestOpenRepo_RejectsMissingPath. - TestRepo_Close_IsIdempotent. - TestHub_Repo_AccessorReturnsLiveHandle — operations on the *Repo accessor are observable via the *Hub. Closes #131 check-in: 5f025def67 user: [email protected] tags: trunk | |
| 20:48 |
fix(leaf/agent): chain Commit onto trunk tip by default (ParentID/MergeParents) (#133)
Adds ParentID and MergeParents to agent.CommitOpts. When ParentID is 0, Agent.Commit auto-resolves the current trunk tip and uses that as the parent — matching `fossil ci`'s default behavior. On an empty repo (BranchTip returns sql.ErrNoRows), the resolution falls through to 0 and the commit is a legitimate orphan root (the first commit). Before this fix, agent.Commit always passed ParentID=0 to libfossil, producing P-card-less manifests. Every commit became its own root, fragmenting the timeline and breaking trunk-based development for downstream consumers (notably bones). Tests added: - TestAgent_Commit_ChainsOntoTrunkTipByDefault — two consecutive Commit calls produce a chained pair with linked Parents. - TestAgent_Commit_ExplicitParentIDOverride — caller-supplied ParentID is used verbatim. The hub-leaf e2e test (sim/hub_leaf_test.go) was reading by "trunk" tip on the hub; switched to reading by the explicit RevID returned from Agent.Commit so the test exercises the transport contract (file lands on hub) without coupling to branch-tip resolution semantics on the hub side after sync — which appears to be a separate concern worth its own investigation. Closes #125 check-in: 82e4b0075d user: [email protected] tags: trunk | |
| 20:41 |
fix(leaf/agent): chain Commit onto trunk tip by default (ParentID/MergeParents)
Adds ParentID and MergeParents to agent.CommitOpts. When ParentID is 0, Agent.Commit auto-resolves the current trunk tip and uses that as the parent — matching `fossil ci`'s default behavior. On an empty repo (BranchTip returns sql.ErrNoRows), the resolution falls through to 0 and the commit is a legitimate orphan root (the first commit). Before this fix, agent.Commit always passed ParentID=0 to libfossil, producing P-card-less manifests. Every commit became its own root, fragmenting the timeline and breaking trunk-based development for downstream consumers (notably bones). Tests added: - TestAgent_Commit_ChainsOntoTrunkTipByDefault — two consecutive Commit calls produce a chained pair with linked Parents. - TestAgent_Commit_ExplicitParentIDOverride — caller-supplied ParentID is used verbatim. The hub-leaf e2e test (sim/hub_leaf_test.go) was reading by "trunk" tip on the hub; switched to reading by the explicit RevID returned from Agent.Commit so the test exercises the transport contract (file lands on hub) without coupling to branch-tip resolution semantics on the hub side after sync — which appears to be a separate concern worth its own investigation. Closes #125 leaf check-in: 93966ff081 user: [email protected] tags: trunk, fix/125-commit-parent-id | |
| 20:26 |
ci(bump-upstream): walk every go.mod in the repo (#132)
The auto-generated upstream-bump PR for libfossil v0.5.0 (#121) only updated the root go.mod. leaf/go.mod and bridge/go.mod independently pin libfossil and stayed on v0.4.5 until I extended the PR by hand. Walk every go.mod file in the repo, skip ones that don't depend on the target module (so go get doesn't add it as a new dep), then run go get + go mod tidy in each. Verify-build and the git-add for the commit also iterate the same set. The find-based discovery automatically picks up future modules — when the hub package is promoted to its own go.mod (#118) or any other new module is added, no workflow change is needed. Verified by dry-running the find/grep logic locally against this repo's main: correctly identifies ./, leaf/, bridge/ as libfossil dependants and skips ./docs/site/ (Hugo site, no libfossil). Closes #127 check-in: adb39a9af9 user: [email protected] tags: trunk | |
| 20:21 |
ci(bump-upstream): walk every go.mod in the repo
The auto-generated upstream-bump PR for libfossil v0.5.0 (#121) only updated the root go.mod. leaf/go.mod and bridge/go.mod independently pin libfossil and stayed on v0.4.5 until I extended the PR by hand. Walk every go.mod file in the repo, skip ones that don't depend on the target module (so go get doesn't add it as a new dep), then run go get + go mod tidy in each. Verify-build and the git-add for the commit also iterate the same set. The find-based discovery automatically picks up future modules — when the hub package is promoted to its own go.mod (#118) or any other new module is added, no workflow change is needed. Verified by dry-running the find/grep logic locally against this repo's main: correctly identifies ./, leaf/, bridge/ as libfossil dependants and skips ./docs/site/ (Hugo site, no libfossil). Closes #127 leaf check-in: a6d596273d user: [email protected] tags: trunk, feat/127-bump-walk-all-gomods | |
| 20:20 |
feat(notify): add NewServiceFromPath constructor (#129)
Adds a path-based Service constructor for callers that want notify without managing the *libfossil.Repo lifecycle separately: type ServiceFromPathConfig struct { RepoPath string NATSConn *nats.Conn From string FromName string } func NewServiceFromPath(cfg ServiceFromPathConfig) (*Service, error) If the repo already exists at RepoPath it's opened; otherwise it's created via InitNotifyRepo. Service.Close() now closes the repo when the Service was constructed via NewServiceFromPath; the existing NewService path is unchanged (caller still owns the repo). The new constructor uses the same open-or-create pattern hub.NewHub uses, so re-entry on an existing path Just Works. Closes #115 check-in: 5ceb63c701 user: [email protected] tags: trunk | |
| 20:19 |
docs(architecture): add hub-package.md ADR (#128)
Documents the top-level hub/ package shipped in PR #109 + revised in PR #122 (deadlock fix). Covers public API surface, lifecycle ordering (HTTP listener bound during NewHub for immediate HTTPAddr availability, then NATS server ReadyForConnections), the SQLite tuning decision (busy_timeout yes, MaxOpenConns(1) explicitly no — see #120), the in-root vs. separate- module choice (with #118 as the migration path), and integration with the leaf agent (sim/hub_leaf_test.go::TestHubLeafE2E locks the contract). Cross-referenced from agent-deployment.md and listed at the top of CLAUDE.md alongside the other ADRs. Closes #114 check-in: afa1002be5 user: [email protected] tags: trunk | |
| 19:44 |
test(sim): hub + leaf agent end-to-end integration test (#126)
Adds sim/hub_leaf_test.go: a real *hub.Hub bound to auto-allocated ports, a real *agent.Agent built against a leaf repo cloned from the hub, and a round-trip Commit + SyncTo + Hub.ReadAt that exercises the HTTP-xfer path end-to-end. Without this test, the kind of cross-component regression bones surfaced in #120 (the SetMaxOpenConns deadlock under concurrent clones) only shows up downstream. With it, EdgeSync's own CI catches it. Wired into the existing 'Sim serve tests' step (Makefile + test.yml) alongside TestServeHTTP/TestLeafToLeaf/TestAgentServe so the test runs on every PR. Closes #113 check-in: 995066ac2a user: [email protected] tags: trunk | |
| 19:34 |
test(sim): hub + leaf agent end-to-end integration test
Adds sim/hub_leaf_test.go: a real *hub.Hub bound to auto-allocated ports, a real *agent.Agent built against a leaf repo cloned from the hub, and a round-trip Commit + SyncTo + Hub.ReadAt that exercises the HTTP-xfer path end-to-end. Without this test, the kind of cross-component regression bones surfaced in #120 (the SetMaxOpenConns deadlock under concurrent clones) only shows up downstream. With it, EdgeSync's own CI catches it. Wired into the existing 'Sim serve tests' step (Makefile + test.yml) alongside TestServeHTTP/TestLeafToLeaf/TestAgentServe so the test runs on every PR. Closes #113 leaf check-in: 7275783d59 user: [email protected] tags: trunk, feat/113-hub-leaf-e2e | |
| 19:33 |
refactor(leaf/agent): use libfossil v0.5.0 UUIDFromRID + whole-checkin Diff (#124)
Cleans up two workarounds in code_artifact.go that existed only because libfossil v0.4.x didn't expose them as first-class APIs: - Delete the ridToUUID helper (raw SQL probe of libfossil's blob table). Replaced with r.UUIDFromRID(rid) at both callsites: Agent.Tip and Agent.toSyncResult. - Collapse Agent.Diff from ~25 LOC of file enumeration to a single r.Diff(ridA, ridB, "") call. Uses libfossil v0.5.0's Option-A whole- checkin form. Also fixes wrong-ish rename-detection in the previous hand-rolled implementation. Adds TestAgent_Diff_RenameAcrossRevs to exercise the rename case the old enumeration got wrong. Closes #112 check-in: ab2db30284 user: [email protected] tags: trunk | |
| 19:24 |
chore: bump leaf v0.0.7 → v0.0.8 (#123)
Pulls in the SetMaxOpenConns(1) deadlock fix (#120/PR #122) and the libfossil v0.5.0 baseline (PR #121). This is the version downstream consumers should pin to instead of v0.0.7 (which deterministically deadlocks under concurrent clones). check-in: ca812c4f85 user: [email protected] tags: trunk, v0.0.10 | |
| 19:18 |
chore: bump leaf v0.0.7 → v0.0.8
Pulls in the SetMaxOpenConns(1) deadlock fix (#120/PR #122) and the libfossil v0.5.0 baseline (PR #121). This is the version downstream consumers should pin to instead of v0.0.7 (which deterministically deadlocks under concurrent clones). leaf check-in: 89133cefa6 user: [email protected] tags: trunk, chore/bump-leaf-v0.0.8 | |
| 19:17 |
fix(leaf,hub): drop SetMaxOpenConns(1) to unblock concurrent clones (#122)
The applySQLiteTuning helper added in #108 (and propagated to hub/ in #109) capped the libfossil DB connection pool at 1 connection. The intent was to make libfossil's per-connection PRAGMAs stick, but libfossil's clone path has internal goroutines that all need a DB connection — capping the pool deadlocks every concurrent clone, with one goroutine holding the conn while another waits for it. Bones reported (issue #120) that bumping leaf v0.0.6 → v0.0.7 deterministically deadlocks examples/hub-leaf-e2e/'s 3-leaf concurrent clone test. Reproduced here in hub/clone_concurrency_test.go: 3 parallel libfossil.Clone calls against a shared hub. Without the fix the test hangs until its 25s timeout. With the fix it completes in ~80ms. Keeping the busy_timeout=30000 PRAGMA — that's the correct primitive for concurrent-write safety; SQLite retries SQLITE_BUSY internally up to the timeout. The pool cap was always overkill. If we later need PRAGMA stickiness across pooled connections, route it through the modernc driver's per-connection init hook instead of a pool cap. Closes #120 check-in: b251b87613 user: [email protected] tags: trunk, leaf/v0.0.8 | |
| 19:17 |
chore: bump libfossil → v0.5.0 (#121)
* chore: bump libfossil → v0.5.0 * chore: bump libfossil → v0.5.0 in leaf and bridge modules The auto-generated bump-upstream PR only updated the root module's go.mod. The leaf and bridge modules pin libfossil independently and need the bump too — without this, the upcoming #112 cleanup (delete ridToUUID + simplify Agent.Diff using libfossil v0.5.0's UUIDFromRID + whole-checkin Diff) can't land on leaf since leaf's go.mod still resolves to v0.4.5. Worth fixing the bump-upstream workflow separately to walk every go.mod under the repo, but treating that as a follow-up rather than blocking this PR on it. --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Dan Mestas <[email protected]> check-in: c52b0b3b44 user: [email protected] tags: trunk | |
| 19:11 |
chore: bump libfossil → v0.5.0 in leaf and bridge modules
The auto-generated bump-upstream PR only updated the root module's go.mod. The leaf and bridge modules pin libfossil independently and need the bump too — without this, the upcoming #112 cleanup (delete ridToUUID + simplify Agent.Diff using libfossil v0.5.0's UUIDFromRID + whole-checkin Diff) can't land on leaf since leaf's go.mod still resolves to v0.4.5. Worth fixing the bump-upstream workflow separately to walk every go.mod under the repo, but treating that as a follow-up rather than blocking this PR on it. leaf check-in: c299ef11e2 user: [email protected] tags: trunk, chore/bump-libfossil-v0.5.0 | |
| 19:04 | chore: bump libfossil → v0.5.0 check-in: 8e8db6b593 user: 41898282+github-actions[bot]@users.noreply.github.com tags: trunk, chore/bump-libfossil-v0.5.0 | |
| 18:58 |
chore: bump leaf v0.0.3 → v0.0.7 (#119)
Pulls in the libfossil-hidden Agent code-artifact API (Commit, Sync, SyncTo, Read, Files, Diff, Tip, ExtractTo, Config, plus internal SQLite tuning) and the libfossil-hidden notify Service methods (ListThreads, ReadThread, MessageCount, AddDevice/ListDevices/RemoveDevice, CreatePairingToken/ValidateToken). Carries through the v0.0.5/v0.0.6 fixes for sync-error classification and the notify Subscriber race. Required so downstream consumers (bones) can pin to a root EdgeSync release that exposes the new agent surface. Supersedes the in-flight chore/bump-leaf-v0.0.6 branch. check-in: 49b9471550 user: [email protected] tags: trunk, v0.0.9 | |
| 18:42 |
feat(notify): libfossil-hidden Service methods (#111)
Adds methods on *Service that wrap the existing libfossil-typed free functions, so consumers can use notify's chat-over-NATS-with-fossil- durability behavior without seeing *libfossil.Repo in any signature: - ListThreads(ctx, project) — wraps free ListThreads - ReadThread(ctx, project, threadShort) — wraps free ReadThread - MessageCount(ctx, project, threadShort) — len(ReadThread) - ListDevices / AddDevice / RemoveDevice (Device type) - CreatePairingToken / ValidateToken (PendingToken type) The free *libfossil.Repo-typed functions are unchanged; advanced consumers and existing callers continue to work. Service.Repo() stays as an escape hatch with an updated doc comment recommending the new methods. Note: ListThreads / ReadThread / MessageCount take an explicit project parameter (the brief listed them without one). The notify repo holds threads for multiple projects and the underlying free functions need project; carrying a default project on Service would be a reasonable follow-up but is out of scope here. Closes #104 check-in: 02c7e2b781 user: [email protected] tags: trunk, leaf/v0.0.7 | |
| 18:38 |
feat(cli/repo): EdgeSync-owned repo subcommand library (#110)
Adds github.com/danmestas/EdgeSync/cli/repo, a kong-compatible command tree for fossil repo operations (ci, co, timeline, diff, cat, config, merge, …) that consumers can embed without importing libfossil/cli. Cmd and Globals are type aliases of libfossil/cli.RepoCmd and libfossil/cli.Globals. Re-declaring them as wrapper structs would require mirroring ~38 subcommands' worth of kong flags by hand, brittle to libfossil flag changes. Aliases give consumers source-level isolation (no "github.com/danmestas/libfossil/cli" import in their code) while reusing libfossil's tested CLI implementation. cmd/edgesync/cli.go migrates to the new package as the first consumer. The flag surface, subcommand set, and behavior are unchanged from the end-user perspective. Closes #105 check-in: bb432d52a6 user: [email protected] tags: trunk | |
| 18:34 |
feat(hub): new package for hub-fossil management and HTTP serving (#109)
Adds a top-level hub/ package that hosts an EdgeSync hub in-process: fossil hub repo bootstrap, embedded NATS server with JetStream, auto-port allocation, libfossil HTTP server (timeline UI + xfer endpoint), user management, and hub-side commits/reads. All libfossil and nats-server types are wrapped internally — the public surface exposes only stdlib types and types defined here. Public API: - Config + NewHub + ServeHTTP + Stop - NATSURL / LeafUpstream / HTTPAddr accessors - AddUser / GetUser / HasUser / ListUsers / RemoveUser (User type) - Commit / Read / ReadAt (RevID, FileToCommit, CommitOpts types) Auto port allocation: passing 0 for FossilHTTPPort, NATSClientPort, or NATSLeafPort yields three distinct bound ports. The HTTP listener is bound during NewHub so HTTPAddr() is immediately usable, even before ServeHTTP is called. SQLite tuning (SetMaxOpenConns(1) + busy_timeout=30s) applied at hub bootstrap, matching the leaf agent's tuning. Lives inside the EdgeSync root module (not a separate go.mod) so it ships in lockstep with the leaf agent. Can be promoted to its own module later if downstream consumers need to depend on it independently. Closes #102 check-in: 67ea682587 user: [email protected] tags: trunk | |
| 18:29 |
feat(leaf/agent): libfossil-hidden code-artifact API (#108)
Adds Commit, Sync, SyncTo, Read, Files, Diff, Tip, ExtractTo, Config methods on *Agent that take/return only primitives or types defined in this package. No *libfossil.* in any new signature, so consumers can perform the bones-style code-artifact operations without importing libfossil. New types: RevID (opaque commit handle), FileToCommit, CommitOpts, SyncOpts, SyncResult — all libfossil-free. Internal SQLite tuning (SetMaxOpenConns(1) + busy_timeout=30s) is now applied on every agent boot. libfossil PRAGMAs are per-connection so the single-conn cap is what makes them stick — concurrent-write safety that any production agent needs. Repo() and NotifyRepo() stay as escape hatches for sim/test and advanced consumers; doc comments updated to recommend the new methods. Closes #103 check-in: 3309ec60d5 user: [email protected] tags: trunk | |
| 16:50 |
chore(agents): scaffold ## Agent skills section + docs/agents/ (#107)
Adds the per-repo configuration that the engineering skills (triage, to-issues, to-prd, diagnose, tdd, improve-codebase-architecture) assume: - Issue tracker mapping → docs/agents/issue-tracker.md (GitHub via gh CLI) - Triage label vocabulary → docs/agents/triage-labels.md (canonical names; needs-triage, needs-info, ready-for-agent, ready-for-human, wontfix) - Domain doc layout → docs/agents/domain.md (single-context; ADRs live in docs/architecture/, no CONTEXT.md so skills fall back to CLAUDE.md + MEMORY.md) CLAUDE.md gets an ## Agent skills block pointing at the three docs. The four missing triage labels (needs-triage, needs-info, ready-for-agent, ready-for-human) were created on the GitHub repo at the same time. check-in: f894e896c1 user: [email protected] tags: trunk | |
| 16:44 |
fix(leaf/notify): lock s.subs append/clear in Subscriber (#106)
Concurrent Subscribe/SubscribeThread callers raced on the unsynchronized append in subscribeReturn (and the iterate/clear in Unsubscribe), which the race detector flagged in downstream bones tests under -race -short. Subscriber already had s.mu for dedup state — extending it to cover the subs slice is the cleanest fix. Unsubscribe snapshots the slice under the lock and iterates outside it so we never hold s.mu across NATS calls (the dispatch callback path takes s.mu via isDuplicate). Adds a concurrent-Subscribe stress test that fires the race reliably under -race without the fix. Closes #95 check-in: 0577a01d66 user: [email protected] tags: trunk | |
|
2026-05-01
| ||
| 22:47 | fix(leaf/notify): mutex-gate Watch teardown to prevent send-on-closed-channel panic (#101) check-in: 1fe42a59e7 user: [email protected] tags: trunk, leaf/v0.0.6 | |
| 22:20 | fix(leaf): classify ErrNoResponders and ctx.Canceled as benign sync errors (#99) check-in: 8ca34471cd user: [email protected] tags: trunk, leaf/v0.0.5 | |
| 21:57 | fix(leaf): demote per-target sync errors when another target succeeded (#97) check-in: 5bbb70b342 user: [email protected] tags: trunk, leaf/v0.0.4 | |
| 00:01 |
build(release): publish standalone leaf brew formula (#94)
Add a second goreleaser build target for the leaf daemon (built from the EdgeSync/leaf submodule via dir: leaf) and a separate Homebrew formula at danmestas/tap/leaf so downstream tools (notably bones, which spawns leaf via exec) can declare it as a brew dependency instead of requiring users to clone EdgeSync and run make leaf. Existing edgesync formula is now scoped via ids: [edgesync] so it doesn't pick up the new leaf binary. check-in: b601f8a1af user: [email protected] tags: trunk, v0.0.8 | |
|
2026-04-30
| ||
| 16:01 |
chore: bump libfossil → v0.4.6-rc3 (#92)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> check-in: f26dbee41f user: [email protected] tags: trunk | |
| 16:00 |
Merge pull request #93 from danmestas/fix/goreleaser-skip-prerelease-brews
fix(release): skip Homebrew formula update on prereleases check-in: 929d7783dc user: [email protected] tags: trunk | |
| 15:55 |
fix(release): skip Homebrew formula update on prereleases
Adds skip_upload: auto to the brews block. With release.prerelease: auto, GoReleaser correctly marks rc/beta/alpha tags as GitHub prereleases — but without skip_upload: auto, the Homebrew formula was still being updated for those prereleases. Effect: brew upgrade edgesync would pull the rc binary as a stable update. skip_upload: auto skips brew formula updates whenever the release is marked as prerelease. Stable releases (vX.Y.Z without suffix) still update the formula. check-in: 233d9d94d5 user: [email protected] tags: trunk | |
| 14:31 |
Merge pull request #90 from danmestas/feat/upstream-bump-and-dispatch
feat(ci): receive libfossil dispatches, dispatch to bones, group changelog check-in: 4f299fe932 user: [email protected] tags: trunk | |
| 14:23 |
feat(release): add changelog groups (Features/Bug fixes/Other)
Replaces flat commit list with conventional-commit-grouped sections in the auto-generated GitHub release notes. Existing exclude patterns preserved. check-in: e557d54f0d user: [email protected] tags: trunk | |
| 14:22 |
feat(ci): dispatch upstream-bump to bones on tag
After goreleaser publishes a release, fires repository_dispatch to danmestas/bones so it can open a chore-bump PR. Uses DISPATCH_PAT (fine-grained, scoped to bones contents:write). This closes the edgesync→bones link of the cascade. Combined with PR #6 (libfossil's release.yml), pushing a libfossil tag will fire the full chain: libfossil tag → EdgeSync bump PR → merge+tag → bones bump PR. check-in: e831226826 user: [email protected] tags: trunk | |
| 14:21 |
feat(ci): add bump-upstream receiver workflow
Listens for repository_dispatch event_type=upstream-bump from libfossil's release workflow. Validates payload (repo/module_path/version regex), checks for existing open PR (idempotency), retries go get up to 3 times to absorb GOPROXY propagation lag, opens a chore PR via github-actions[bot]. Falls back to workflow_dispatch with the same inputs for manual recovery. EXPECTED_REPO=libfossil; EXPECTED_MODULE=github.com/danmestas/libfossil. Other dispatch sources are silently ignored. Mirrors the production-tested shape from bones #90. check-in: 0e93a687f0 user: [email protected] tags: trunk | |
| 13:22 |
Merge pull request #89 from danmestas/chore/local-dev-hygiene
chore: add local dev hygiene targets and pre-push hook check-in: 5ea1ad56be user: [email protected] tags: trunk | |
| 13:15 |
fix(make): ci-fast also tests leaf and bridge sub-modules
leaf/ and bridge/ are separate Go modules (each has go.mod), so 'go test ./...' from repo root does not descend into them. Without this fix, both pre-commit and pre-push hooks would pass even when leaf or bridge is broken — exactly the class of bug the old pre-commit was trying to catch. Reuse ci-vet (which already runs vet across all three modules) as a dependency, then run -short tests in root, leaf, and bridge sequentially. Sub-module tests run in ~2s each on a dev box; total ci-fast runtime approximately doubles to ~12s (still well under the 60s pre-push budget). check-in: 3f4035e5e2 user: [email protected] tags: trunk | |
| 13:09 |
fix(hooks): replace broken pre-commit with make ci-fast
The previous pre-commit ran 'go test ./leaf/agent/' from repo root, which fails because leaf is a separate Go module. The hook had been masked by a global core.hooksPath override; activating local .githooks/ via 'make setup-hooks' (added in this PR) exposed the breakage. Replace with the same pattern as pre-push: 'make ci-fast' (~6s in this repo). Skip with: git commit --no-verify. check-in: 9197f23350 user: [email protected] tags: trunk | |
| 13:03 |
feat: add release annotation template
Used by 'make release VERSION=...' as the body skeleton for annotated tags. check-in: 8a43ebc3f7 user: [email protected] tags: trunk | |
| 13:03 |
feat: add pre-push hook running make ci-fast
Catches obvious breakage before push. ~60s. Skip with: git push --no-verify check-in: 1c034a7e90 user: [email protected] tags: trunk | |
| 13:01 |
feat(make): add release VERSION=... helper
Validates clean tree, runs make ci, opens $EDITOR with auto-populated tag annotation template, creates annotated tag locally. Push is intentionally manual. check-in: 3295f73d5d user: [email protected] tags: trunk | |
| 13:01 |
feat(make): add ci and ci-fast targets
ci mirrors .github/workflows/ci.yml verbatim: ci-vet — vet root + leaf + bridge ci-leaf-bridge — leaf and bridge short tests (sequential; CI runs parallel) ci-cmd — cmd/edgesync build + test ci-fast runs vet + build + short tests (~60s) for pre-push hook. Note: test.yml's sim/interop tests are slower and need fossil; not mirrored here. Run them on demand for releases. check-in: 13ba906678 user: [email protected] tags: trunk | |
| 10:40 |
Merge pull request #88 from danmestas/docs/changelog-v0.0.7
docs: changelog entry for v0.0.7 + skip CI on docs-only changes check-in: 31f055cc4c user: [email protected] tags: trunk | |
| 10:36 | ci: skip docs-only changes (paths-ignore) check-in: 60ccb8082f user: [email protected] tags: trunk | |
| 10:34 | docs: changelog entry for v0.0.7 check-in: 9831416916 user: [email protected] tags: trunk | |
| 10:31 |
Merge pull request #87 from danmestas/chore/bump-libfossil-v0.4.5
chore: bump libfossil v0.4.3 → v0.4.5 check-in: c77ecea4f7 user: [email protected] tags: trunk, v0.0.7 | |
| 10:27 |
chore: bump libfossil v0.4.3 → v0.4.5
Pulls in fixes from two libfossil releases: - v0.4.4 (libfossil#14): hub xfer handler no longer panics with "content.MakePublic: rid must be positive" when a client cancels mid-transfer. Affects EdgeSync hubs that serve concurrent clones. - v0.4.5 (libfossil#17, libfossil#18): sync.Clone now converges against a hub being written to during the clone session. Three compounding root causes fixed: server-side rid cap (snapshot via CookieCard), client-side CloneSeqNoCard cursor emission, and cookie roundtrip. Wire-format compatible with older peers. No EdgeSync source changes required — both fixes are libfossil-internal. Closes #86. leaf check-in: 6c16b3e646 user: [email protected] tags: trunk, chore/bump-libfossil-v0.4.5 | |
|
2026-04-28
| ||
| 00:44 |
Merge pull request #85 from danmestas/polish/libfossil-level
polish: CHANGELOG + CONTRIBUTING (libfossil-level hygiene) check-in: c345d7737b user: [email protected] tags: trunk | |
| 00:40 |
polish: add CHANGELOG + CONTRIBUTING (libfossil-level hygiene)
Matches the bones polish template. CHANGELOG covers root-module versions (v0.0.1 initial through v0.0.6 latest) and notes the multi-module tagging scheme (leaf/, bridge/ submodules carry their own tags). CONTRIBUTING covers the make setup bootstrap, test/sim commands, the four-binary build (edgesync + leaf + bridge + iroh-sidecar), wasm targets, and submodule coordination guidance for releases. check-in: d9574335d0 user: [email protected] tags: trunk | |
| 00:02 |
Merge pull request #84 from danmestas/release-tooling
release: GoReleaser config + tag-push workflow + edgesync --version check-in: 30cd2bb5fe user: [email protected] tags: trunk, v0.0.6 | |
| 00:00 |
release: GoReleaser config + tag-push workflow + edgesync --version
Cross-compile edgesync for darwin/linux × amd64/arm64, archive as tarballs, generate checksums, publish a GitHub release, and update the Homebrew formula at danmestas/homebrew-tap on every git tag matching v*. Mirrors the bones release pipeline (PRs danmestas/bones#25, #26). Files: - .goreleaser.yml — build matrix, archive shape, brews stanza pointing at danmestas/homebrew-tap, ldflags for version injection. - .github/workflows/release.yml — runs on tag push, sets up Go 1.26.0, calls goreleaser-action@v6 with `release --clean`. Needs HOMEBREW_PAT_TOKEN secret on this repo (PAT with contents:write on danmestas/homebrew-tap). - cmd/edgesync/main.go — version/commit/date package vars filled by ldflags. `edgesync --version` and `-V` short-circuit before Kong (avoids collision with `repo extract --version` sub-flag). After merge: 1. Add HOMEBREW_PAT_TOKEN secret to this repo. 2. Tag v0.0.6 and push — release workflow takes it from there. 3. Verify: brew install danmestas/tap/edgesync. check-in: 176fae9490 user: [email protected] tags: trunk | |
|
2026-04-27
| ||
| 20:39 |
Merge pull request #83 from danmestas/readme-dx-improvements
README: front-load value prop, add prereqs, badges, deep-dive links check-in: 8cbcae9e10 user: [email protected] tags: trunk | |
| 20:32 |
README: front-load value prop, add prereqs, badges, status, deep-dive links
DX-audit pass for public-repo readiness: 1. New tagline + Why EdgeSync section that explains the use case in terms a non-Fossil-aware reader can evaluate (real-time over messaging, no central server, Fossil-compatible wire). 2. Status section sets pre-1.0 expectations honestly. 3. Prerequisites section before Quick Start: Go 1.26+, NATS server, optional Fossil binary, optional direnv. 4. Quick Start now creates a demo repo, starts NATS, and shows a stock fossil clone hitting the leaf — the cold-start path actually works end-to-end. 5. New "Use as a Go library" section with go get for each module and a pkg.go.dev link. 6. New "Deep Dive" section linking docs/architecture/* by topic (was filesystem-grep-only before). 7. Badges: CI status, Go reference, license, Go report card. 8. License section references the Apache-2.0 file added in PR #82. Project Layout updated: cli/ added, libfossil moved to its own repo note, hugo site path corrected. check-in: f7d05cf9ac user: [email protected] tags: trunk | |
| 20:15 |
Merge pull request #82 from danmestas/pre-public-cleanup
pre-public: LICENSE, IP placeholders, module READMEs, WASM build fix check-in: 83d2a03038 user: [email protected] tags: trunk | |
| 19:51 | pre-public: LICENSE, untrack .mcp.json, placeholder IPs, module READMEs, fix wasm targets check-in: 28f11dc3c4 user: [email protected] tags: trunk | |
| 17:37 |
Merge pull request #81 from danmestas/fix-root-module-deps
go.mod: pin sub-module deps to real tags so EdgeSync/cli is consumable check-in: 0d8829ccf1 user: [email protected] tags: trunk, v0.0.5 | |
| 17:29 |
go.mod: pin sub-module dependencies to real tags
Replace v0.0.0-00010101 placeholders with leaf v0.0.3 and bridge v0.0.2 in the root and bridge go.mod files. Local replace directives stay so intra-repo development against unreleased changes still works; the real version pins make EdgeSync/cli consumable by external repos via `go get`. Without this, `go get github.com/danmestas/EdgeSync/cli` fails with "unknown revision 000000000000" when the consumer tries to resolve EdgeSync/bridge. check-in: e6c688849f user: [email protected] tags: trunk | |
| 16:57 |
Merge pull request #80 from danmestas/extract-cli-package
cli: extract command tree into reusable package check-in: 18a10cd49e user: [email protected] tags: trunk, v0.0.4 | |
| 16:36 |
cli: extract command tree into reusable package
Move sync, bridge, notify, and doctor commands from cmd/edgesync/ into a new top-level cli/ package. The integration struct in cmd/edgesync/cli.go now imports from EdgeSync/cli (aliased as edgecli) and assembles the binary's command surface. This mirrors libfossil/cli's pattern — the cli/ package exports embeddable Kong command structs that downstream consumers (e.g. agent-infra) can integrate alongside libfossil/cli.RepoCmd into their own unified CLI. No behavior change: cmd/edgesync still builds the same binary with the same flags, and the existing notify_test.go end-to-end suite passes unchanged. check-in: 845d1d229e user: [email protected] tags: trunk | |
|
2026-04-26
| ||
| 15:37 |
Merge pull request #79 from danmestas/bump-libfossil-v0.4.3
deps: bump libfossil v0.1.0 → v0.4.3 across all modules check-in: efeaf4c90f user: [email protected] tags: trunk, v0.0.3, leaf/v0.0.3, bridge/v0.0.2 | |
| 15:29 |
deps: bump libfossil v0.1.0 → v0.4.3 across all modules
Picks up four releases worth of upstream fixes and surface additions: - v0.4.0 → v0.4.3: xfer protocol fix (always emit project-code on push/pull cards, libfossil#12), diff byte-fidelity ("\\ No newline at end of file", libfossil#10), repo.ResolveVersion + ReadFileAt for symbolic version lookups (libfossil#11). - v0.2.0 / v0.3.0: prior 5xx error mapping, transport rewrites, repo admin surface, content cache, observer rework — all consumed transitively without code changes here. `go build ./...` clean. `make test` green across leaf/, bridge/, sim/, notify, telemetry. No code changes needed in EdgeSync — the libfossil public API stayed stable across these releases. This bump is needed by the sibling agent-infra repo, which already pins libfossil v0.4.2 directly for chat/workspace and was relying on the local `replace ../EdgeSync/leaf` directive to mask the version drift. With this bump and a follow-up tag (leaf/v0.0.3), agent-infra can drop the replace and pin clean module versions. check-in: 7db3317617 user: [email protected] tags: trunk | |
| 15:20 |
Merge pull request #78 from danmestas/add-docs-site
docs: Hugo+hextra docs site with Cloudflare Workers deploy check-in: d59e2dece9 user: [email protected] tags: trunk, v0.0.2 | |