Design: the backend for grandma knit (sharing project memory between teammates) #14
anshulforyou
started this conversation in
Ideas
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
knit is the sharing phase, and the framing we settled on is git for context. Two people working the same project each build their own project-scoped memory locally. One shares, the other pulls it through grandma, and grandma shows a per-fact diff so each side keeps what it wants. Peer sync with a review step, not a one-way push. This is the whole feature: the commands, the transport, the merge, provenance, and the strip pass.
Here is a suggested design. It is researched and meant to be built from, but push back where you disagree.
Transport: a short-code handoff over a secret gist. The reasoning matters, because two obvious options both fail on UX. A plain file export is manual (you still have to get the file to your teammate). A shared git remote is a lot of setup (make a repo, wire it, pick a handle). grandma's hard promise is no server and no accounts, so a grandma-hosted sync is out. A gist under the user's own GitHub threads that needle: knit share posts the bundle and prints a short code, the teammate runs knit pull
, and grandma runs nothing and holds no account, it rides the gh (or a token) the user already has.The catch with a secret gist is that it is only unlisted, not access-controlled, so anyone with the id could read it. So the bundle is encrypted with a random key and the short code carries that key (code = gist id plus key). The gist holds ciphertext, and only the person you hand the code to can decrypt it. That turns the code into a real capability token and makes the transport genuinely private. When gh and a token are both absent, share degrades to writing the same encrypted bundle as a local file you pass by hand, so the feature still works offline.
Commands:
. Fetch the gist, decrypt with the key in the code, reconcile against local memory, and land the result as an uncommitted proposal: clean adds appended, conflicts written to a review file. No auto-commit.. Delete the gist when you are done.Merge: do not hand facts to git's line merge, it emits raw conflict markers and keeps near-duplicate lines when both sides added the same fact in different words. You also do not need a text CRDT, those exist to order concurrent edits inside one evolving document and pay for it in tombstones, which would wreck the plain-readable-markdown promise. grandma memory is an unordered set of facts, order irrelevant, newest wins, which is exactly a last-writer-wins register per fact plus a grow-only set for adds. Give each fact a stable key (a content hash of the normalized text) and an origin. On pull, reconcile per key: present on one side is a clean add, an identical fact is skipped, and the same subject with different text is the only case that goes to the human, shown as a diff3 three-part view so the reviewer sees the common base, not just two endpoints. Never destroy anything, an unsettled fact becomes a pending item so the pull always completes. This model is lifted from git-bug's operation log.
Provenance: origin lives in a sidecar ledger, one per scope, git-ignored, keyed by fact-hash. Not inline, since a from-teammate tag on each line pollutes the readable markdown and bakes a teammate's identity into committed project memory, a leak vector. Not frontmatter, since that is per-file and provenance is per-fact. The ledger lets grandma show trust in review (this came from your teammate) and, crucially, skip any fact whose origin is a teammate when you next share, so an absorbed note is never re-broadcast back out as yours.
Stripping, and this is security-critical. share removes personal scope before anything is encrypted or posted. Define the shareable subset by a boundary set before send, not by scrubbing a full dump: share touches exactly one project sweater and only its shareable files, never global, never another sweater, and excludes raw session logs by default. Then a line-level pass drops anything tagged personal, anything on the denylist, and any secret-shaped token, reusing the regexes the core-purity test already uses. Gate it like isolation is gated: test/cmd_knit.sh plants a personal fact, a secret, and a cross-scope line in a kebab-case fixture and asserts the shared bundle contains none of them and is not plaintext, failing before the strip-and-encrypt step exists and passing after, plus a matching invariant in grandma-test.sh. The gist call itself goes through a seam a fake shim can replace, so the tests never touch the network.
Open questions, yours to settle:
Prior art worth reading before you start: git-bug's data model, git's diff3 merge, jujutsu's record-the-conflict-and-move-on approach, how password managers split a shared vault from an export, and how encrypted pastebins put the decryption key in the URL fragment so the server never sees it. Not built yet.
Beta Was this translation helpful? Give feedback.
All reactions