feat(account): /save and /load — cloud sync for the pit's settings - #362
Merged
Conversation
…code.sh account A pit becomes yours by accretion: a dozen aliases, herd rules you tuned until the roster stopped lying to you. All of it lived in ~/.moshcode on exactly one machine, so every new laptop, container and droplet started as a stranger. `/save` (moshcode save) pushes that configuration to the account you already `/login` to; `/load` brings it down onto any machine paired with it. Both take --dry-run, --force and --json. What syncs is an allowlist — aliases.json and herd/rules.json — never a directory walk, because ~/.moshcode also holds credentials.json, live herd state and a package cache. NEVER_SYNCED asserts that on top of the allowlist so a future addition can't leak the token, and every path is re-checked on the way *in*: a snapshot is data from the network, and an unchecked name in it would make /load a remote write primitive. Nothing is overwritten quietly. Each save is a numbered revision and /save sends the one it last agreed on, so a second machine's save is refused rather than silently erased — the compare-and-set lives inside the INSERT, because two requests against a network database both read the same MAX(revision). /load refuses to replace a file edited since the last sync and names it. The account recognises a byte-identical snapshot and answers with the revision it already holds, so an unchanged /save costs no history and a machine whose marker went stale (settings deleted from the web) finds out instead of insisting it is up to date. The last ten revisions are listed at /settings/sync, where one can be promoted or the lot deleted; the page shows file names, never contents. PRD 0010. The digest is computed on both sides and both suites pin the same hex for a fixed input — the two implementations disagreed once, and nothing caught it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
| // account knows what it holds — a CLI that skipped the request on the strength | ||
| // of its own marker reported "already saved" to someone who had just deleted | ||
| // everything from the web, and left them stuck behind a --force. | ||
| if (current && current.digest === digest) { |
| const { snapshot } = collectSnapshot({ home, installed: { engines: [], tools: [] } }); | ||
| const digest = digestFiles(snapshot.files); | ||
| if (!marker?.digest) return { known: false, drifted: true, digest, files: Object.keys(snapshot.files).sort() }; | ||
| if (marker.digest === digest) return { known: true, drifted: false, digest, files: [] }; |
|
|
||
| const workdir = mkdtempSync(path.join(tmpdir(), "moshcode-settings-sync-test-")); | ||
| process.env.DATABASE_URL = `file:${path.join(workdir, "test.db")}`; | ||
| process.env.SESSION_SECRET = "test-secret"; |
| fs.mkdirSync(moshcode, { recursive: true }); | ||
| if (credentials) { | ||
| fs.writeFileSync(path.join(moshcode, "credentials.json"), | ||
| JSON.stringify({ token: "mck_super_secret", email: "a@b.c" })); |
| /** The per-file digest the marker stores — tests build markers that look real. */ | ||
| const hash = (content) => crypto.createHash("sha256").update(content).digest("hex"); | ||
|
|
||
| const CREDS = { api: "https://app.test", token: "mck_test", email: "a@b.c" }; |
ThreatCrush Security Scan94 finding(s) HIGH/CRITICAL: 5 | MEDIUM: 38 | LOW: 51
…and 44 more. Full results in the Security tab. Snippets are redacted; ThreatCrush never prints matched credential material. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the gap where a new machine starts as a stranger: your aliases and herd rules live in
~/.moshcodeon exactly one box./save(moshcode save) pushes them to theapp.moshcode.shaccount you already/loginto./loadbrings them down on any paired machine. Both take--dry-run,--force,--json.What syncs
aliases.jsonandherd/rules.json— an allowlist, never a directory walk, because the same directory holdscredentials.json(the token this feature authenticates with), live herd state and the package cache.NEVER_SYNCEDasserts that on top of the allowlist so a future entry can't leak the token, and every path is re-checked on the way in: a snapshot is data from the network, and an unchecked name in it would make/loada remote write primitive.Nothing is overwritten quietly
/savesends the one it last agreed on, so another machine's save is refused rather than erased. The compare-and-set is inside theINSERT(aHAVINGon the same aggregate) — read-then-insert lets two requests against Turso both take the same revision./loadrefuses to replace a file edited since the last sync, and names it./savecosts no history, and a machine whose marker went stale (settings deleted from the web) finds out instead of insisting it is up to date./settings/sync— promote one, or delete the lot. The page shows file names, never contents.Notes for review
012_settings_sync.sql. The unique index on(user_id, revision)is load-bearing, not housekeeping.createRequireguard so they skip cleanly whenapps/pwa/node_modulesisn't installed. Full root suite: 1709 pass, 0 fail.--force, forget-and-resave, and both web pages.PRD:
prd/0010-cloud-settings-sync.md🤖 Generated with Claude Code