Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
88 lines
3.9 KiB
Markdown
88 lines
3.9 KiB
Markdown
# Sync providers
|
|
|
|
Netcatty cloud sync providers are dynamic and namespaced. Built-in providers
|
|
(`github`, `google`, `onedrive`, `webdav`, `s3`) stay compatible; plugins
|
|
register additional IDs under their plugin namespace with `kind: "sync"` and
|
|
permission `provider.sync`.
|
|
|
|
## Boundary
|
|
|
|
Plugins implement **encrypted object storage only**:
|
|
|
|
- `connect` / `disconnect` / `getAccount`
|
|
- `getCapabilities` (`revisions`, `conditionalWrites`, `atomicReplacement`, size limits)
|
|
- `readObject` / `writeObject` / `deleteObject`
|
|
|
|
Netcatty owns encryption, the master key, CRDT merge, migrations, protection
|
|
snapshots, conflict handling, and read-merge-write-verify. Plugin providers
|
|
never receive the vault master key or plaintext sync payloads.
|
|
|
|
## Secrets
|
|
|
|
Only non-secret configuration marked for sync enters cloud payloads. Plugin
|
|
connect secrets (`password`, `token`, `secret`, `apiKey`, `accessToken`) are
|
|
stripped from configuration, stored in the OS-backed plugin secret store, and
|
|
passed to `SyncConnectPayload.credential` as opaque `{ kind: "secret", id, key }`
|
|
references. Additional extracted secrets are stored under `sync-credential:<field>`
|
|
keys so plugins can `secrets.get` / `credentials.createLease` them.
|
|
|
|
Durable reconnects persist an opaque SecretRef (`{ kind, id, key }`), not
|
|
plaintext. The host injects Authorization only after consuming an
|
|
operation-bound lease whose `operationId` matches `network:<origin>`.
|
|
|
|
**Using a SecretRef from a sandbox plugin:** create an operation-bound lease via
|
|
`credentials.createLease` with `operationId` set to `network:<origin>` (same
|
|
origin the request will call), then call `network.request` with:
|
|
|
|
```json
|
|
{
|
|
"url": "https://example.com/…",
|
|
"credentialLease": { "kind": "secret-lease", "id": "…", "operationId": "network:https://example.com", "expiresAt": 0 },
|
|
"authorization": { "scheme": "Bearer" }
|
|
}
|
|
```
|
|
|
|
The host consumes the lease (bound to the request origin, not a plugin-echoed
|
|
id) and injects `Authorization` (Bearer or Basic). Plaintext never returns to
|
|
the plugin. Companion `credentialLeases` remains available for node-only
|
|
companions.
|
|
|
|
WebDAV continues to exercise the shared EncryptedObjectStorage path for
|
|
configuration, proxy behavior, upload/download, and recovery. Write verification
|
|
on WebDAV is performed by the adapter's pad+verify upload (not a second host
|
|
byte re-read). Credentials remain field-encrypted at rest via the secure field
|
|
adapter.
|
|
|
|
## Streams and SyncLimits
|
|
|
|
Public `SyncLimits` (see plugin contract) define:
|
|
|
|
- `maxObjectBytes` — hard ciphertext cap
|
|
- `inlineObjectBytes` — maximum size that may travel inline on the control plane
|
|
- key / revision length bounds
|
|
|
|
Above `inlineObjectBytes`, main↔plugin uses credit-window streams
|
|
(`STREAM_WINDOW_BYTES` = 256 KiB). Renderer↔main uses structured-clone
|
|
`Uint8Array` for inline payloads and pull/chunked IPC (`sync-write-begin` /
|
|
`sync-write-chunk` / `sync-write-commit`, `sync-read-chunk`) for larger objects.
|
|
Transfers are per-sender, TTL-bounded, capped, and cancelled via
|
|
`cancelPluginExtensionRequest(requestId)` / `AbortSignal`.
|
|
|
|
## Sidecars (non-cascade)
|
|
|
|
Missing or disabled plugins must not delete synced settings or connection
|
|
baselines. Host-owned `plugin_sync_sidecars` carry `sync:true` non-secret
|
|
settings and account/CRDT baselines through collect/apply with last-known and
|
|
prefer-cloud merge semantics. Device-local baselines survive remote settings
|
|
wipes; empty-vault upload guards ignore last-known-only evidence.
|
|
|
|
## WebDAV
|
|
|
|
The production WebDAV adapter is wrapped as EncryptedObjectStorage so it shares
|
|
the same encrypt→write / read→decrypt surface as plugin providers. WebDAV's
|
|
native pad+verify upload already satisfies write verification and may leave
|
|
trailing padding on the remote object; the shared bridge therefore skips a
|
|
full byte re-read on that path (`assumeVerifiedWrites`) — without that flag the
|
|
host compare would false-fail on padded bodies. Plugin providers keep
|
|
host-owned byte compare after write.
|