first commit
This commit is contained in:
74
docs/security-stack/security/vault/src/sync/sync.md
Normal file
74
docs/security-stack/security/vault/src/sync/sync.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# vault sync
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
`sync.ts` exposes the `VaultSync` orchestrator that delegates `pull` / `push` / `sync` operations to a `VaultSyncLike` adapter (production wires this to `@suaveplan/vault-sync`'s `SyncEngine`). The orchestrator's responsibilities are: (1) project the in-memory item rows into the wire-shaped `SyncSnapshot`, (2) hydrate the delegate with the latest local snapshot before every operation, (3) translate the delegate's `SyncDelegationResult` into the public `SyncResult` envelope, and (4) wrap any delegate failure in a `VAULT.SYNC.FAILED` `VaultError` so consumers see a stable error code regardless of the underlying transport.
|
||||
|
||||
This keeps the sync engine generic — it has no knowledge of the vault's encryption layer, item schema, or container format. The orchestrator handles all the impedance matching at the boundary.
|
||||
|
||||
## Features
|
||||
|
||||
- `pull() → SyncResult` — pull remote changes via the delegate.
|
||||
- `push() → SyncResult` — push local changes via the delegate.
|
||||
- `sync() → SyncResult` — bidirectional sync via the delegate.
|
||||
- `currentSnapshot()` — read-through to the delegate's view.
|
||||
- Snapshot is built on-demand from `rows()` — the orchestrator never caches a stale snapshot.
|
||||
- All operations emit a histogram sample and an `op`-tagged outcome counter; failures map to `VAULT.SYNC.FAILED` with the underlying error chained as `cause`.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { VaultSync } from "@suaveplan/vault/sync/sync";
|
||||
|
||||
const sync = new VaultSync({
|
||||
delegate: vaultSyncEngine,
|
||||
rows: () => items.allRows(),
|
||||
clock: Date.now,
|
||||
});
|
||||
|
||||
const result = await sync.sync();
|
||||
console.log(`merged ${result.merged.length} items`);
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { VaultSync } from "@suaveplan/vault/sync/sync";
|
||||
import { VaultError, VAULT_CODES } from "@suaveplan/vault/errors";
|
||||
|
||||
async function syncWithBackoff(sync: VaultSync, attempts = 3) {
|
||||
for (let i = 0; i < attempts; i++) {
|
||||
try {
|
||||
return await sync.sync();
|
||||
} catch (err) {
|
||||
if (
|
||||
!VaultError.is(err) ||
|
||||
err.code !== VAULT_CODES["SYNC.FAILED"] ||
|
||||
i === attempts - 1
|
||||
) throw err;
|
||||
await new Promise((r) => setTimeout(r, 100 * 2 ** i));
|
||||
}
|
||||
}
|
||||
throw new Error("unreachable");
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `class VaultSync`.
|
||||
- `constructor(options: SyncOptions)`.
|
||||
- `pull(): Promise<SyncResult>` — throws `SYNC.FAILED`.
|
||||
- `push(): Promise<SyncResult>` — throws `SYNC.FAILED`.
|
||||
- `sync(): Promise<SyncResult>` — throws `SYNC.FAILED`.
|
||||
- `currentSnapshot(): SyncSnapshot`.
|
||||
- `interface SyncOptions` — `{ delegate, rows, clock }`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- `buildSnapshot` projects only the wire-essential fields (`id`, `updatedAt`, `version`, `deleted`, `fieldsJson`, `concealedFieldsJson`, `tagsJson`). It deliberately does not decrypt concealed fields — sync sees ciphertext only, end-to-end. The remote service stores ciphertext, the merge layer compares it as opaque bytes, and concealed-field plaintext never leaves the vault layer.
|
||||
- The orchestrator hydrates the delegate before every operation. This keeps the snapshot correct even if the caller mutates items between successive `pull` / `push` calls.
|
||||
- `VAULT.SYNC.FAILED` is the canonical wrapping for any error raised from the delegate. Callers can inspect `err.cause` for the underlying reason (transport timeout, merge conflict, payload-too-large, etc.).
|
||||
- The OTel `outcome` counter uses a fixed allowlist (`success | failed | rejected`); the orchestrator emits `success` and `failed`. The `rejected` slot is reserved for the lifecycle reject path (e.g. `vault.sync.pull` called while locked) which is enforced one layer up in `Vault`.
|
||||
Reference in New Issue
Block a user