Artifact 4fdeea10085d23024ebda34308a7ff09329ffb5ccc3c05aa35fa0b36afd56f07:
- File
Makefile
— part of check-in
[ad1d55f5b9]
at
2026-05-04 22:05:56
on branch trunk
— 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. (user: [email protected], size: 5389) [annotate] [blame] [check-ins using]
.PHONY: build test clean leaf bridge edgesync iroh-sidecar wasm-wasi wasm-browser wasm dst dst-full dst-hostile dst-drivers sim sim-full setup-hooks setup test-interop test-iroh update-libfossil
# --- Build ---
build: edgesync leaf bridge iroh-sidecar
VERSION ?= $(shell git describe --tags --always --dirty 2>/dev/null || echo dev)
edgesync:
go build -buildvcs=false -o bin/edgesync ./cmd/edgesync
leaf:
cd leaf && go build -buildvcs=false -ldflags "-X main.version=$(VERSION)" -o ../bin/leaf ./cmd/leaf
bridge:
cd bridge && go build -buildvcs=false -o ../bin/bridge ./cmd/bridge
iroh-sidecar:
cd iroh-sidecar && cargo build --release
cp iroh-sidecar/target/release/iroh-sidecar bin/
wasm-wasi:
@mkdir -p bin
cd leaf && GOOS=wasip1 GOARCH=wasm go build -buildvcs=false -o ../bin/leaf.wasm ./cmd/leaf
wasm-browser:
@mkdir -p bin
cd leaf && GOOS=js GOARCH=wasm go build -buildvcs=false -o ../bin/leaf-browser.wasm ./cmd/wasm
cp "$$(go env GOROOT)/lib/wasm/wasm_exec.js" bin/
wasm: wasm-wasi wasm-browser
clean:
rm -rf bin/
# --- Test (what CI runs) ---
# Unit tests run in parallel across modules; sim/dst run sequentially after.
test:
@pids=""; fail=0; \
(cd leaf && go test ./... -short -count=1) & pids="$$pids $$!"; \
(cd bridge && go test ./... -short -count=1) & pids="$$pids $$!"; \
for pid in $$pids; do wait $$pid || fail=1; done; \
if [ $$fail -ne 0 ]; then echo "FAIL: unit tests"; exit 1; fi
(cd sim && go test . -run 'TestFaultProxy|TestGenerateSchedule|TestBuggify' -count=1)
(cd sim && go test . -run 'TestServeHTTP|TestLeafToLeaf|TestAgentServe|TestHubLeafE2E|TestAgentNew_|TestHubNATSFossilSync_' -count=1 -timeout=120s)
(cd sim && go test . -run 'TestInterop' -count=1 -short -timeout=60s)
(cd sim && go test . -run 'TestSimulation' -sim.seed=1 -count=1 -timeout=120s)
vet:
go vet ./...
cd leaf && go vet ./...
cd bridge && go vet ./...
# --- Setup (first-time onboarding) ---
setup: setup-hooks build test
@echo ""
@echo "Setup complete. Binaries in bin/. Try:"
@echo " bin/edgesync repo info"
@echo " bin/leaf --help"
setup-hooks:
git config core.hooksPath .githooks
@echo "Pre-commit hook installed. Runs ~5s of tests before each commit."
@echo "Skip with: git commit --no-verify"
# --- Sim (Integration Simulation) — run locally, requires fossil ---
# Note: DST tests now live in libfossil (github.com/danmestas/libfossil/dst)
# Quick: 1 seed, normal severity
sim:
cd sim && go test . -run TestSimulation -sim.seed=1 -v -timeout=120s
# Full: 16 seeds × 3 severities
sim-full:
@echo "=== Sim full (16 seeds × 3 severities) ==="
@for severity in normal adversarial hostile; do \
for seed in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16; do \
echo " seed=$$seed severity=$$severity ..."; \
(cd sim && go test . -run TestSimulation -sim.seed=$$seed -sim.severity=$$severity -timeout=120s) || true; \
done; \
done
@echo "=== Sim full done ==="
# Fossil interop: Tier 1 + Tier 2 (requires fossil binary, Tier 2 samples 5K+2K blobs)
test-interop:
cd sim && go test -buildvcs=false . -run TestInterop -timeout=10m -v
# Iroh P2P: build sidecar then run iroh unit + integration tests
test-iroh: iroh-sidecar
cd leaf && go test ./agent/ -run TestIroh -count=1 -v
cd sim && go test . -run TestIroh -count=1 -timeout=120s -v
# --- Dependency update ---
update-libfossil:
go get github.com/danmestas/libfossil@latest
go get github.com/danmestas/libfossil/db/driver/modernc@latest
go get github.com/danmestas/libfossil/db/driver/ncruces@latest
go mod tidy
# CI mirror — must match .github/workflows/ci.yml verbatim.
.PHONY: ci ci-vet ci-leaf-bridge ci-cmd ci-fast
ci: ci-vet ci-leaf-bridge ci-cmd
ci-vet:
go vet ./...
cd leaf && go vet ./...
cd bridge && go vet ./...
ci-leaf-bridge:
cd leaf && go test ./... -short -count=1
cd bridge && go test ./... -short -count=1
ci-cmd:
go build -buildvcs=false ./cmd/edgesync/
go test -buildvcs=false ./cmd/edgesync/ -count=1 -timeout=60s
# Fast subset for pre-push hook.
# Mirrors the bones-side of ci.yml in -short mode, including descending into
# the leaf/ and bridge/ sub-modules (separate go.mod) which `go test ./...`
# from root would miss. Skips the cmd/edgesync build+test (the slowest CI lane).
ci-fast: ci-vet
go build ./...
go test -short -count=1 -timeout=30s ./...
cd leaf && go test -short -count=1 -timeout=30s ./...
cd bridge && go test -short -count=1 -timeout=30s ./...
.PHONY: release
release:
@test -n "$(VERSION)" || { echo "VERSION=vX.Y.Z required"; exit 1; }
@echo "$(VERSION)" | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+(-.+)?$$' || { echo "bad version format: $(VERSION)"; exit 1; }
@git diff --quiet || { echo "tree dirty; commit or stash first"; exit 1; }
@git fetch origin --tags
@if git rev-parse "$(VERSION)" >/dev/null 2>&1; then echo "tag $(VERSION) already exists"; exit 1; fi
@$(MAKE) ci
@PREV=$$(git describe --tags --abbrev=0 2>/dev/null || echo ""); \
TMPL=.github/RELEASE_TEMPLATE.md; \
TMP=$$(mktemp); \
{ echo "Release $(VERSION)"; echo; \
[ -f $$TMPL ] && { cat $$TMPL; echo; }; \
echo "## Changes"; \
if [ -n "$$PREV" ]; then git log --oneline $$PREV..HEAD; else git log --oneline; fi; } > $$TMP; \
$${EDITOR:-vi} $$TMP; \
git tag -a "$(VERSION)" -F $$TMP; \
rm $$TMP
@echo ""
@echo "Tag $(VERSION) created locally. To publish:"
@echo " git push origin $(VERSION)"