v0.2 slice 4: sync modes (+ doc site) #4

Merged
erik merged 3 commits from v02-slice4-sync-modes into main 2026-05-27 00:36:30 +00:00
Owner

Summary

  • Slice 4: sync modes — wires the mode field in root.toml into upload.Run via three predicates on internal/config.Mode (Downloads(), Uploads(), AllowsLocalDelete()). The four modes now have real behaviour:

    • bidirectional — every phase runs
    • upload-only — peer download + peer-tombstone application skipped
    • download-only — manifest build / blob upload / own tombstone / head publish skipped
    • archive-only — downloads + records peer tombstones as cached, but never deletes local files (new ArchivedTombstonesSkipped counter; archived_tombstones_skipped JSON field)
  • Renames upload_only / download_only / archive_only → hyphenated forms to match the project convention. The on-disk test-fixture filename keeps its underscore (fixture naming convention).

  • Retires Options.EnablePeers in favour of Mode.

  • New ADR at wiki/decisions/sync-modes.md capturing the phase-by-mode matrix and the archive-only no-delete property.

  • Four new integration tests in internal/upload/integration_test.go covering each mode end-to-end against a shared fake bucket.

  • Doc site bootstrap — second commit adds a Retype-based documentation site under ./doc, cherry-picked from ~/src/tfks/logbus/doc. Six top-level orientation pages (index, why, trade-offs, audience, failure-modes, setup) plus a use-cases section seeded with sync-modes. Wired into the root Justfile via mod doc — recipes are just doc::dev, just doc::build, just doc::deploy. The url in doc/retype.yml and the rsync target in doc/justfile are placeholders and likely need adjustment to whatever the real doc host is.

Test plan

  • just validate green (format / vet / lint / tests / secrets / vulns)
  • Four new mode-specific integration tests pass: TestIntegrationUploadOnlyIgnoresPeer, TestIntegrationDownloadOnlyConsumesButDoesNotPublish, TestIntegrationArchiveOnlyRetainsAfterPeerTombstone, TestIntegrationArchiveOnlyDoesNotPublish
  • Existing JSON-shape test extended to assert the mode field is present in the envelope
  • Manual smoke test: two roots against a fake bucket, walking through the §6 manual-verification recipe in doc/use-cases/sync-modes.md

🤖 Generated with Claude Code

## Summary - **Slice 4: sync modes** — wires the `mode` field in `root.toml` into `upload.Run` via three predicates on `internal/config.Mode` (`Downloads()`, `Uploads()`, `AllowsLocalDelete()`). The four modes now have real behaviour: - `bidirectional` — every phase runs - `upload-only` — peer download + peer-tombstone application skipped - `download-only` — manifest build / blob upload / own tombstone / head publish skipped - `archive-only` — downloads + records peer tombstones as cached, but never deletes local files (new `ArchivedTombstonesSkipped` counter; `archived_tombstones_skipped` JSON field) - Renames `upload_only` / `download_only` / `archive_only` → hyphenated forms to match the project convention. The on-disk test-fixture filename keeps its underscore (fixture naming convention). - Retires `Options.EnablePeers` in favour of `Mode`. - New ADR at `wiki/decisions/sync-modes.md` capturing the phase-by-mode matrix and the archive-only no-delete property. - Four new integration tests in `internal/upload/integration_test.go` covering each mode end-to-end against a shared fake bucket. - **Doc site bootstrap** — second commit adds a Retype-based documentation site under `./doc`, cherry-picked from `~/src/tfks/logbus/doc`. Six top-level orientation pages (`index`, `why`, `trade-offs`, `audience`, `failure-modes`, `setup`) plus a use-cases section seeded with `sync-modes`. Wired into the root Justfile via `mod doc` — recipes are `just doc::dev`, `just doc::build`, `just doc::deploy`. The url in `doc/retype.yml` and the rsync target in `doc/justfile` are placeholders and likely need adjustment to whatever the real doc host is. ## Test plan - [x] `just validate` green (format / vet / lint / tests / secrets / vulns) - [x] Four new mode-specific integration tests pass: `TestIntegrationUploadOnlyIgnoresPeer`, `TestIntegrationDownloadOnlyConsumesButDoesNotPublish`, `TestIntegrationArchiveOnlyRetainsAfterPeerTombstone`, `TestIntegrationArchiveOnlyDoesNotPublish` - [x] Existing JSON-shape test extended to assert the `mode` field is present in the envelope - [ ] Manual smoke test: two roots against a fake bucket, walking through the §6 manual-verification recipe in `doc/use-cases/sync-modes.md` 🤖 Generated with [Claude Code](https://claude.com/claude-code)
When the download decider returns `ConflictSkip` for a file (peer has sha256) or symlink (peer has target) and the peer isn't the multi-peer sentinel, materialize the peer's content alongside the local copy at `<stem>.conflict-<peer-device>-<UTC-ts><ext>` and flip the local state row to `StatusConflict`. The `StatusConflict` row is excluded from `manifestEntriesFromState` and `blobUploadsFromState`, so the device publishes neither version this run — the user resolves by editing the original (which reclassifies the row off conflict) and `rm`ing the unwanted conflict siblings. Multi-peer disagreement, dir collisions, and entries missing payload remain skip-with-warning.

Also fixes a plan/sync divergence: `dropbear plan` runs the walker read-only, so the persisted row could lag the walker's reclassification (e.g. row=Conflict, walker=Modified after `touch`). `enumeratePeerContribs` now overlays the walker's projection onto the row passed to `Decide`, so plan output matches what sync would actually do. Exported `fswalk.ToStateFile` to share the walker→state.File projection.

New `internal/upload/conflict.go` carries `ConflictFilename` + `writeConflictFile`. `Result` and the sync JSON envelope gained `ConflictFilesCreated` / `conflict_files_created`; the human summary line gained `⚠N`. Wiki updated: PROJECT.md "Where We Are" + "Recent Changes" mark slice 3 shipped; new `wiki/ideas/resolve-command.md` sketches a future `dropbear resolve` for clearing conflicts without the implicit touch+rm+sync dance.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Wire the mode field in root.toml into upload.Run. Adds
internal/config.Mode with Downloads / Uploads / AllowsLocalDelete
predicates that gate pipeline phases.

upload-only skips the peer download phase and peer-tombstone application;
download-only skips manifest build, blob upload, own-tombstone emission, and
head publish; archive-only runs the download / tombstone-apply phases but
records peer tombstones as cached without deleting local files (new
ArchivedTombstonesSkipped counter; archived_tombstones_skipped JSON field).
The legacy EnablePeers option is retired in favor of Mode.

Mode values are renamed from upload_only / download_only / archive_only to
hyphenated forms to match the project naming convention. The on-disk
fixture filename remains valid_upload_only.toml (test-fixture
convention).

New ADR at wiki/decisions/sync-modes.md captures the phase-by-mode matrix
and the archive-only no-delete property.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
doc: bootstrap retype-based documentation site under ./doc
All checks were successful
Validate / validate (push) Successful in 38s
Validate / validate (pull_request) Successful in 37s
cf737d3a37
Cherry-picks the structure from ~/src/tfks/logbus/doc. Six top-level
orientation pages (index, why, trade-offs, audience, failure-modes,
setup) plus a use-cases section seeded with the sync-modes page. Pages
are tight, newcomer-focused, and biased toward "what is this and is it
worth my time."

Wired into the root Justfile via `mod doc`; recipes are available as
`just doc::dev`, `just doc::build`, and `just doc::deploy`. The url and
rsync target in retype.yml/justfile are placeholders (dropbear.tfks.net)
and will need adjustment if the doc host is different.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
erik merged commit 8c24ddbed1 into main 2026-05-27 00:36:30 +00:00
erik deleted branch v02-slice4-sync-modes 2026-05-27 00:36:30 +00:00
Sign in to join this conversation.
No description provided.