Skip to content

docs(exploration): Yjs document history and evidence-grade attribution (0376, 0377) - #597

Merged
crs48 merged 2 commits into
mainfrom
claude/yjs-document-history-aa881d
Jul 19, 2026
Merged

docs(exploration): Yjs document history and evidence-grade attribution (0376, 0377)#597
crs48 merged 2 commits into
mainfrom
claude/yjs-document-history-aa881d

Conversation

@crs48

@crs48 crs48 commented Jul 19, 2026

Copy link
Copy Markdown
Owner

Two linked explorations on document history and attribution. Docs only — no code, no changeset needed.

0376 — Two Histories, One Timeline

The History tab (time-machine, wildcard context tool) scrubs the node change log only. On a page it replays the title, tags and icon, and says nothing about the prose.

The plumbing is ~80% built and 0% surfaced:

  • DocumentHistoryEngine captures Yjs snapshots on every debounced persist, persisted to a real yjs_snapshots table.
  • mergeTimelines() already computes the unified timeline — no caller outside its own test.
  • useTimeMachine fetches the snapshots only to .length them; TimeMachinePanel prints that count as a caption.

Findings:

  • ⚠️ Restore silently half-restores. restoreToFrontier documents that document content is not restored, and tm.restore() inherits it — the confirm dialog promises a full restore. The frontier already carries yjsSnapshotRef, so the fix is small.
  • No Yjs history is dropped — the gap is the time index, not the data. gc: false everywhere, PruningEngine never touches Yjs, no compaction exists. But CRDT items are keyed (client, clock) with no wall time, so a complete item log still can't answer "what did this say Tuesday at 3pm".
  • Every snapshot stores a full document copy on top of that retained blob; eviction destroys index entries, not content.
  • ⚠️ Do not "fix" the 5 MiB large-blob warning by enabling GC — retention is what makes scrubbing possible.

Recommends one timeline, two lanes, coarse by default — and rejects a naive merge, because it would mix signed and unsigned entries.

0377 — Evidence-Grade Attribution

The follow-on: how to get detailed attribution in the CRDT using the existing data model.

Almost none of it needs building. packages/sync/src/yjs-change.ts fully implements Yjs updates as entries in the same signed per-node hash chain — YJS_CHANGE_TYPE = 'yjs-update', createYjsChange, type guards, a ~350-line test suite, barrel exports. Zero production callers. Same for ClientIdAttestation, validateClientIdOwnership, createPersistedDocState, YjsBatcher. Change<T>'s own header names Yjs as a first-class payload type.

Meanwhile every Yjs update is already signed in transit with author + timestamp + clientID, and all of it is discarded at setDocumentContent.

Findings:

  • ⚠️ clientID is spoofable and plan step 07's attestation is unwired — so any attribution UI shipped first would render a forgeable claim as fact. Step 07 is P0.
  • ⚠️ plan03_4_1YjsSecurity/README.md has five [x] boxes that track module existence, not wiring. Worth correcting regardless of whether this work proceeds.
  • yjs_state must stay authoritative. 0254 states the local log is a non-authoritative cache, and its compaction ships and runs every boot — inverting authority would let compaction delete document content. The correct shape is the parallel that already exists.
  • Cost is rows and bytes, not CPU (38 µs native sign). YjsBatcher's 2 s default gives ~7-15:1 envelope amplification and ~1,800 rows per writing hour against a whole-user budget of 5,000 changes/day; ~10 s with paragraph flush lands ~2-3:1.
  • BatchCommit is forbidden on the interactive lane (0357) — amortise by batching updates into fewer changes, not signatures.
  • 0330's D3 gate is half-open: compaction shipped, the growth model never happened.

Notes

🤖 Generated with Claude Code

xNet Test added 2 commits July 19, 2026 16:49
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Signed-off-by: xNet Test <test@xnet.dev>
…story

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Signed-off-by: xNet Test <test@xnet.dev>
@crs48 crs48 added the skip-changelog Exclude this PR from the changelog label Jul 19, 2026
@crs48
crs48 temporarily deployed to pr-597 July 19, 2026 23:50 — with GitHub Actions Inactive
github-actions Bot added a commit that referenced this pull request Jul 19, 2026
github-actions Bot added a commit that referenced this pull request Jul 19, 2026
@github-actions

Copy link
Copy Markdown
Contributor

@crs48
crs48 merged commit e581c39 into main Jul 19, 2026
10 of 11 checks passed
@crs48
crs48 deleted the claude/yjs-document-history-aa881d branch July 19, 2026 23:59
github-actions Bot added a commit that referenced this pull request Jul 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip-changelog Exclude this PR from the changelog

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant