Skip to content

feat: attributed-content endpoint for per-span doc authorship - #30

Draft
petergaultney wants to merge 1 commit into
No-Instructions:mainfrom
TrilliantHealth:thds/upstream-pr-attributed
Draft

feat: attributed-content endpoint for per-span doc authorship#30
petergaultney wants to merge 1 commit into
No-Instructions:mainfrom
TrilliantHealth:thds/upstream-pr-attributed

Conversation

@petergaultney

Copy link
Copy Markdown

Summary

Adds a GET /d/:doc_id/attributed-content endpoint that returns the current visible text content of a document, split into spans by authoring client, with clients resolved to users via the PermanentUserData "users" map.

This enables downstream consumers (e.g. a git-sync server) to produce per-author commits from collaborative documents.

Auth

The endpoint requires server auth (Y_SWEET_AUTH Bearer token) - the same auth used by /check_store and other admin endpoints. This is intentionally more restrictive than doc-token auth since the endpoint exposes content and authorship information that is appropriate for server-to-server use, not end-user access.

API

GET /d/:doc_id/attributed-content?root=contents
Authorization: Bearer <Y_SWEET_AUTH>

Response:

{
  "doc_id": "abc123",
  "root": "contents",
  "spans": [
    {"text": "hello", "client_id": 42, "user": "alice"},
    {"text": " world", "client_id": 99, "user": "bob"}
  ]
}

The root query param defaults to "contents" (the standard text root for Relay documents). Returns 404 if the doc has no text root by that name.

Implementation

The attribution walk runs on a cloned doc (not the live one) to avoid mutating shared state. It uses diff_range between the current snapshot and an empty snapshot, which surfaces every visible item with its originating client ID. Adjacent characters from the same client are merged into a single span.

A catch_unwind guard handles a known yrs 0.26 panic in find_pivot when splitting at the current snapshot while a client's stream is a single one-unit block - in that case the full content is returned as one unattributed span.

That panic is a divide-by-zero in find_index (block_store.rs), which I filed upstream as y-crdt/y-crdt#645; it was closed in favor of #647, which fixes part of the same problem. Neither is in a crates.io release, so on our own deployment we pin a fork carrying the fix on top of the dtkav/y-crdt revision this repo already uses: petergaultney/y-crdt rev 4996719 (branch thds/0.26.0-patched).

The catch_unwind in this PR is deliberately the whole remedy here - it degrades to one unattributed span rather than requiring a patched yrs, so this PR needs no dependency change. If you would rather take the yrs pin instead and drop the guard, that works too, but it means carrying a fork of yrs.

Relationship to the other open PRs

Independent of the logging/behavior split (#26, #23, #24) - no overlapping hunks, any merge order works.

Companion PR: No-Instructions/relay-git-sync#6 consumes this endpoint to produce per-author commits. That PR degrades gracefully when the endpoint is absent or returns 401/404, so it does not have to merge after this one.

Test plan

  • Verify endpoint requires server auth (401 without Bearer token)
  • Verify two-author attribution after sync
  • Verify deletions leave surviving spans attributed
  • Verify missing text root returns 404
  • Verify unmapped clients have null user
  • 242 tests pass

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant