hav
Concepts

The harbor

A harbor is a thin HTTP server over a directory of bare hubs:

hav harbor serve /srv/hav --addr 0.0.0.0:8080 --token <push-token>

serves every hub under /srv/hav/<name> at http://<addr>/<name>. On the client side nothing is new: remote, push, pull, and clone accept these URLs wherever they accept paths.

The harbor is a dumb transport, the HTTP equivalent of a path remote. Reads are anonymous: anyone can pull or clone with no credential at all, and every pull carries the hub's full travel-safe row set, sealed ciphertext included. Pushes require the one bearer token the server was started with (--token on remote/clone, or HAV_TOKEN). Accounts, per-user access control, and audit belong to a hosting product built on top rather than to the VCS.

Terminate TLS in front of it

The harbor speaks plain HTTP and puts the push token in the Authorization header. Binding the 0.0.0.0:8080 example straight to a public interface exposes that token to anyone on the wire. Bind to loopback or a private interface and put a TLS-terminating reverse proxy in front. TLS is the proxy's job.

All crypto stays client-side

The wire protocol is deliberately primitive. The client fetches the hub's whole travel-safe row set, runs the ordinary path-remote logic against it locally (merging, sealing, and fast-forward checks, all with the client's keys), and posts the successor row set back, fenced by a sequence number. Concurrent pushes serialize exactly like path pushers queueing on a write lock, and a lost race refetches and reruns. (Whole-history transfer both ways is the accepted v1 cost.)

Private work needs no server cooperation to stay private: sealed rows are ciphertext under the crew key, so handing them to a non-member hands over nothing. The key is the gate, exactly as it already is for a path clone on a shared disk.

The harbor validates ciphertext-blind

Every accepted push is checked inside one transaction, without reading any plaintext:

  • Format. The bundle's schema version must match the hub's, checked read-only before anything is applied — the harbor never migrates or modifies a client upload.
  • Monotonicity. History the hub already holds must arrive byte-identical: knots never vanish, rewind, or re-seal (a reveal may only clear the seal, which is monotone), and buoys and crews are never deleted.
  • Integrity. Every new object must hash to its claimed id.
  • Structure. Public buoys must move fast-forward, point at tied history, and arrive with their full closure, walked and kind-checked server-side. No plaintext untied knot lands, since WIP never travels. Crew generations only move forward, every generation carries its chain row, and a crew's identity is write-once, so a bundle swapping a different crew in under a held slug is refused as a deletion.

The server does not police plaintext at any path. With variant sets there is no ownership, and a public variant is legitimate everywhere, so disclosure control is the crew key plus the client-side publish gate.

The consequence: no credential can delete or rewrite history the hub already holds. A hostile token holder can push garbage forward. The worst case is a rotation members can't open, which wedges that crew's config on the hub, though members keep their local copies and keys and recovery is a fresh crew. They cannot destroy or alter anything already there.

Trust, stated loudly

The token authenticates the connection rather than the content, and the harbor cannot verify that a key wrap opens, which would need a key it must never hold. There is one write credential, everyone who pushes shares it, and the transition checks above are the only thing standing between a token holder and the hub. Who may push, who pushed what, and who fetched what are questions for the hosting product in front of the harbor. TLS is the reverse proxy's job.

Operational notes

  • One hub, one write path: a harbor-served root should be reachable only through the harbor, because path writes bypass its sequencing entirely.
  • Ops: URL pushes record nothing anywhere, like path pushes, since the hub mutates atomically. Pulls and clones record their ordinary single local op.
  • Tokens: server side --token or HAV_HARBOR_TOKEN; client side --token on remote/clone (stored outside op views, so undo never touches credentials) or HAV_TOKEN. Reads need none.

On this page