Skip to content

Latest commit

 

History

History
130 lines (85 loc) · 7.44 KB

File metadata and controls

130 lines (85 loc) · 7.44 KB

qi usage guide

This guide is for users deciding whether the qi playground fits their workflow and for operators running direct peer synchronization. See the README for prerequisites and the shortest setup.

When qi fits

qi is suitable for experimenting with replication of one top-level JSON object between directly reachable peers. Each top-level key is a CRDT entry. Its value may be any JSON value, but changing a nested array or object replaces that entire value; qi is not a recursive JSON CRDT.

Use another system if you need peer discovery, relay or NAT traversal, untrusted-peer authorization, recursive merging of nested JSON, or large-file/media transfer. qi normally exchanges CRDT deltas. After an explicit generation rotation, it sends the current checkpoint snapshot to force older-generation peers to bootstrap.

Identity model

A replica ID (--user) identifies causal authorship and is immutable for one running Store. Normally keep it stable across CLI restarts of the same logical editor, and never reuse one ID for independently active copies. The default is username@hostname.

Restoring a checkpoint under a different --user is supported: existing causal history remains intact, but future writes are authored by the new replica ID. The checkpoint does not store or enforce the runtime-local author.

A libp2p peer ID identifies a network endpoint. qi stores its private key at <data-dir>/libp2p.key, which keeps that peer ID stable across restarts. Do not derive replica IDs from peer IDs: causal identity and network identity have different lifecycles.

The left side of --peer label=/multiaddr/p2p/peerID is an operator label used for configuration and logs. It is not sent as a verified remote replica identity and is not authentication. The /p2p/... component selects the authenticated libp2p endpoint; only connect the demo CLI to peers you trust.

Clean two-peer localhost workflow

1. Start both identities and record their addresses

In terminal 1:

go run ./cmd/qi run \
  --user alice@local \
  --data-dir .qi/alice \
  --file alice.json \
  --listen /ip4/127.0.0.1/tcp/4101

In terminal 2:

go run ./cmd/qi run \
  --user bob@local \
  --data-dir .qi/bob \
  --file bob.json \
  --listen /ip4/127.0.0.1/tcp/4102

Copy each process's printed shareable multiaddrs, then stop both with Ctrl-C. Retain the data directories so their libp2p.key files—and therefore peer IDs—remain stable.

2. Restart with reciprocal mappings

Restart Alice, substituting Bob's complete printed peer ID:

go run ./cmd/qi run \
  --user alice@local \
  --data-dir .qi/alice \
  --file alice.json \
  --listen /ip4/127.0.0.1/tcp/4101 \
  --peer bob@local=/ip4/127.0.0.1/tcp/4102/p2p/<bob-peer-id>

Restart Bob, substituting Alice's peer ID:

go run ./cmd/qi run \
  --user bob@local \
  --data-dir .qi/bob \
  --file bob.json \
  --listen /ip4/127.0.0.1/tcp/4102 \
  --peer alice@local=/ip4/127.0.0.1/tcp/4101/p2p/<alice-peer-id>

Each process periodically pulls from configured peers. Reciprocal mappings are therefore required for both replicas to initiate synchronization and converge after edits on either side.

3. Exercise synchronization

While both processes run, save a top-level JSON object such as this to alice.json:

{"message":"hello","settings":{"theme":"dark"}}

After the polling interval, bob.json should converge. Try editing on Bob, deleting a top-level key, and changing settings; the complete nested settings value is replaced.

Visible JSON and snapshots

qi maintains two representations:

  • --file is the human-editable projection containing only visible JSON values.
  • <data-dir>/document.snapshot.json is a qi generation checkpoint containing document identity and a durable yin snapshot, including causal metadata and tombstones needed for correct replication.

Startup precedence is:

  1. Load <data-dir>/document.snapshot.json when it exists.
  2. Otherwise seed from --file when it exists.
  3. Otherwise start with {}.

Seeding from --file or {} does not itself publish a checkpoint. On a fresh data directory, document.snapshot.json first appears after a later state-changing local edit or pulled remote change is accepted. Until then, the visible file remains the seed on each restart.

Once a checkpoint exists, it is authoritative. On every start qi loads that checkpoint before serving or polling and rewrites --file from its recovered visible projection. An edit made only to --file while qi is stopped is therefore overwritten at the next start. This prevents a metadata-free projection from silently replacing durable causal state and also repairs a missing, stale, or partially written projection after an interrupted run.

During a run, qi accepts a state-changing local edit or peer update only after publishing the complete checkpoint, then writes the visible projection. A pre-publication failure leaves accepted state unchanged. A durability-uncertainty error means the published state was adopted and must not be retried blindly. Restart recovery always uses the checkpoint rather than replaying the projection. See the generation checkpoint architecture note for the precise outcomes and rotation contract.

To deliberately reseed, stop qi, delete only document.snapshot.json, edit the visible file, and restart. This starts the CLI's default initial generation and is not a coordinated rotation; do not use it while old peers can reconnect. The new seed is not checkpointed until a later accepted state change. Deleting the entire data directory also reseeds, but deletes libp2p.key; the peer ID changes and every remote --peer address must be updated.

Polling and invalid files

--interval controls both visible-file polling and peer pulls and defaults to 1s. Additions, updates, and removals of top-level keys are imported. qi may rewrite the file to canonical JSON.

After successful startup, invalid, partially written, or non-object JSON observed by polling is logged and ignored without changing CRDT state or publishing a checkpoint. A later poll retries after the file is valid. By contrast, an invalid initial seed prevents startup when no checkpoint exists; when a checkpoint does exist, startup replaces the projection from it. Valid polled input is not reported as applied until checkpoint acceptance succeeds. Prefer editor save modes that atomically replace files to reduce transient parse warnings.

Direct LAN operation

Listen on an externally reachable interface and use the remote host's LAN address, for example:

--listen /ip4/0.0.0.0/tcp/4101 \
--peer bob@lan=/ip4/192.168.2.11/tcp/4102/p2p/<bob-peer-id>

The remote host needs the reciprocal mapping. Firewalls must permit the selected TCP ports. qi currently provides no discovery, relay, or NAT traversal, so both hosts must be directly reachable.

Recovery checks

Before relying on a recovered replica:

  1. Confirm the expected --user and data directory.
  2. Confirm the startup log reports the expected peer ID.
  3. Preserve document.snapshot.json unless intentionally reseeding, and confirm the visible file was regenerated from it at startup.
  4. Verify remote --peer entries still contain the current peer ID and reachable address.
  5. Make a test top-level edit, wait for its acceptance message, and confirm convergence in both directions.
  6. Restart one peer and confirm its projection returns from the checkpoint before making further edits.