[Init] Initial commit - NetMesh terminal manager
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
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
This commit is contained in:
73
infrastructure/services/syncAnchorDecision.js
Normal file
73
infrastructure/services/syncAnchorDecision.js
Normal file
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* syncAnchorDecision — pure "has the remote changed since we last saw it?"
|
||||
* logic extracted from CloudSyncManager so it can be exercised by
|
||||
* `node --test` without standing up the full manager harness.
|
||||
*
|
||||
* Called from CloudSyncManager.inspectProviderRemoteState after the
|
||||
* remote has been downloaded and its signature computed. Given the
|
||||
* previous anchor and the current state, decides whether the remote
|
||||
* looks different enough to warrant re-merging.
|
||||
*
|
||||
* Four decisions matter for data integrity:
|
||||
*
|
||||
* 1. Anchor missing + remote empty → not changed (first sync, nothing
|
||||
* to merge from). Callers MUST still guard against pushing an empty
|
||||
* local vault (see useAutoSync `hasMeaningfulSyncData`) — that guard
|
||||
* is orthogonal to this decision.
|
||||
* 2. Anchor missing + remote non-empty → changed (first sync, remote
|
||||
* has data we've never observed → three-way merge with empty base).
|
||||
* 3. Anchor present + resourceId drift → changed (provider created a
|
||||
* fresh file; reuse of the old anchor would be meaningless).
|
||||
* 4. Anchor present + signature mismatch → changed (same resource, new
|
||||
* ciphertext — standard drift).
|
||||
*
|
||||
* Any other state is "unchanged", and callers short-circuit the merge.
|
||||
*
|
||||
* @param {{
|
||||
* currentSignature: string | null,
|
||||
* currentResourceId: string | null,
|
||||
* anchor: { signature?: string | null, resourceId?: string | null } | null,
|
||||
* hasRemoteFile: boolean,
|
||||
* }} input
|
||||
* @returns {{ remoteChanged: boolean, reason: string }}
|
||||
*/
|
||||
export function decideRemoteChanged(input) {
|
||||
const { currentSignature, currentResourceId, anchor, hasRemoteFile } = input;
|
||||
|
||||
if (!anchor) {
|
||||
// No anchor means we've never observed this provider.
|
||||
if (!hasRemoteFile) {
|
||||
// Remote has no file at all → nothing to merge.
|
||||
return { remoteChanged: false, reason: 'no-anchor-no-remote' };
|
||||
}
|
||||
if (currentSignature === null) {
|
||||
// hasRemoteFile=true but the signature computed to null — the
|
||||
// file exists but we can't hash its meta (malformed shape, newer
|
||||
// schema, partial download). Treat as CHANGED so the caller
|
||||
// routes through the three-way merge / decrypt path rather than
|
||||
// silently short-circuiting and letting the next upload overwrite
|
||||
// an unreadable-but-extant remote file. If the payload is
|
||||
// decryptable the merge will succeed; if it isn't, the decrypt
|
||||
// error surfaces to the user, which is strictly safer than a
|
||||
// silent stomp.
|
||||
return { remoteChanged: true, reason: 'unreadable-remote' };
|
||||
}
|
||||
return { remoteChanged: true, reason: 'no-anchor-remote-has-data' };
|
||||
}
|
||||
|
||||
// Resource identity drift: provider returned a different resource
|
||||
// (e.g. a freshly-created gist, or the user reconnected and the
|
||||
// adapter picked a new file). The previous anchor's signature is
|
||||
// meaningless once the resource id changes.
|
||||
const anchorResourceId = anchor.resourceId ?? null;
|
||||
if (anchorResourceId !== currentResourceId) {
|
||||
return { remoteChanged: true, reason: 'resource-id-changed' };
|
||||
}
|
||||
|
||||
// Same resource, different signature → new ciphertext/meta.
|
||||
if ((anchor.signature ?? null) !== currentSignature) {
|
||||
return { remoteChanged: true, reason: 'signature-mismatch' };
|
||||
}
|
||||
|
||||
return { remoteChanged: false, reason: 'anchor-matches' };
|
||||
}
|
||||
Reference in New Issue
Block a user