Skip to content

Optional Object Storage Service

The object-storage service retains immutable epoch-encrypted objects and advisory signed head publications. It is separate from the relay/AutoNAT and mailbox services, cannot decrypt vault content, and never selects a canonical head for clients.

Start from packaging/lattice-object-storage.toml, replace the example external addresses, and validate the result:

Terminal window
lattice object-storage config-check --config ./lattice-object-storage.toml
lattice object-storage peer-id

The server listens on TCP and QUIC port 4102 by default. Its identity, capability key, metadata, immutable object files, usage records, and process lock live under the dedicated lattice-object-storage data directory.

The private observability listener defaults to 127.0.0.1:9476:

  • /healthz: the metrics HTTP task is alive.
  • /readyz: both libp2p listeners reached readiness.
  • /metrics: OpenMetrics request, response, error, swarm, and bandwidth data.

Metrics contain no peer IDs, repository IDs, object IDs, capability IDs, or network addresses as labels. Production config validation requires a nonzero loopback metrics address.

Repositories use an epoch-derived blinded storage ID. Issue least-privilege reader or publisher capabilities for that ID:

Terminal window
lattice object-storage issue <repo-id> reader \
--config ./lattice-object-storage.toml
lattice object-storage issue <repo-id> publisher \
--config ./lattice-object-storage.toml

reader permits object reads and advisory-head listing. publisher permits immutable puts, signed publication, and owner-scoped unpin. full grants both sets. Capabilities default to 90 days, accept --valid-days 1..365, and can be bound to one libp2p identity with --peer-id.

Issuance prints the bearer token on stdout and its publication-owner ID on stderr. Preserve that owner ID when rotating a publisher or unpin token:

Terminal window
lattice object-storage issue <repo-id> publisher \
--publication-owner <32-hex-owner-id> \
--config ./lattice-object-storage.toml

The token is a bearer secret. Keep it out of logs, shell history, world-readable files, and service configuration.

Pin one advertised direct TCP or QUIC address and the service peer ID:

Terminal window
lattice vault storage set personal \
/dns4/storage.example.net/udp/4102/quic-v1/p2p/<storage-peer-id>

Install issued tokens through stdin so they are stored only as protected entries in credential-cache.kdbx:

Terminal window
lattice object-storage issue <repo-id> reader \
--config ./lattice-object-storage.toml \
| lattice vault storage capability personal
lattice object-storage issue <repo-id> publisher \
--publication-owner <32-hex-owner-id> \
--config ./lattice-object-storage.toml \
| lattice vault storage capability personal

The token repository ID must match the vault’s current epoch-derived storage ID. Peer-bound tokens must match the daemon identity. lattice vault storage status personal reports only the pinned endpoint and capability counts; clear removes protected tokens before disabling or changing the endpoint.

Both the mailbox and object-storage services must be configured for offline delivery. A device that publishes needs a mailbox deposit capability plus an object-storage publisher or full capability. A receiving device needs a mailbox poll/acknowledge capability plus an object-storage reader or full capability.

On each mailbox cycle, the daemon encrypts the current signed revision into an immutable object bundle, uploads its exact object closure, publishes the signed head, and only then includes the encrypted manifest object ID in the recipient’s signed mailbox hint. If publication fails, the daemon can still deposit a peer-address hint and reports the object-storage error in daemon status.

Prepared bundles use randomized encryption. The daemon therefore persists the exact prepared bytes under the vault’s private data directory before the first upload. Retries and restarts reuse those bytes and object IDs, so a periodic cycle cannot create a new publication for an unchanged revision. After a new revision is published, the daemon uses the publisher’s stable publication-owner identity to unpin the superseded publication. Keep that owner ID when rotating publisher capabilities. Across an epoch rotation, retain the old publisher capability until the daemon has had a successful cycle so it can unpin the old repository’s publication.

When a recipient sees an unknown revision, it first lists advisory heads from the pinned object service, finds the signed head bound to the mailbox manifest ID, downloads the exact signed closure, decrypts and validates every object, and passes the revision to the normal authoritative apply path. The mailbox message is acknowledged only after a successful apply. If no usable object bundle is available, peer addresses from the same hint remain the fallback.

Object storage never grants rollback authority. Already-known stale heads are acknowledged without applying them, and unseen stale or detached revisions are rejected by the same signed ancestry, membership cutover, and rollback-floor checks used for direct peer synchronization.

Terminal window
install -Dm0755 target/release/lattice ~/.local/bin/lattice
install -Dm0644 packaging/systemd/user/lattice-object-storage.service \
~/.config/systemd/user/lattice-object-storage.service
install -Dm0600 packaging/lattice-object-storage.toml \
~/.config/lattice-object-storage/config.toml
systemctl --user daemon-reload
systemctl --user enable --now lattice-object-storage.service

Verify the rootless service locally with curl --fail http://127.0.0.1:9476/readyz.

Objects are addressed by the hash of their exact encrypted encoding and are never overwritten. A publication pins one signed, AEAD-bound head pointer and its exact sorted object closure. Multiple concurrent publications remain visible until their publication owner unpins them; clients validate membership, decrypt objects, and resolve revision ancestry before applying anything.

Read, write, object-count, publication, pin, repository-byte, service-byte, and bandwidth limits are enforced per policy and capability. Unpinned objects are eligible for collection only after 30 days. Read usage survives restart in small per-token records without rewriting the repository metadata database.