Sync primitives

What this layer does

Phase 1 builds the smallest possible client surface for moving bytes between Joe's machine and DO Spaces through the Worker. Joe never holds DO credentials. The Worker is the only ingress. The client is a single no-deps Node script plus a versioned manifest. Everything else in snappy-os layers on top of these four primitives: push, pull, status, doctor.

Files involved

DO_SPACES_KEY / DO_SPACES_SECRET from env (Worker side) or stdin (Robert-side admin). Joe never calls this directly.

snapshot, restore, rollback, --version, alert.

SYNC_DENY lists. Every path under state/log/* must match one or the other. The sync-rules-coverage.ts lint enforces it.

helpers. Stored at state/log/sync-manifest.json.

x-amz-meta-sha256 round-trip probe. Either header (preferred) or sidecar (fallback writes <file>.sha256).

native session-lifecycle hooks (Gemini CLI, OpenClaw).

Manifest format

type SyncManifest = {
  version: 1;
  generated_at: string;
  files: Record<string, { sha256: string; size: number; mtime: string; }>;
};

Bump version only on incompatible change. Pull refuses if the remote manifest version exceeds what the local code understands.

Operational gotchas

60s mtime check that exits silently. /tmp/snappy-os-upload.lock is a flock(2) held during the actual POST /_push call. Stale debounce locks (mtime over 120s) get force-removed before exit.

Code windows on one machine share the same lock.

--scope sources to push. The default --auto scope is state.

10 MB get gzipped before upload (Phase 12).

logs the would-upload list to state/log/sync-events.ndjson without contacting the Worker.

How to verify it's working

after a successful push.

manifest (counts by added, modified, deleted).

call, with dur_ms, bytes, and manifest_after populated.