Skip to content

Provenance authored:* marks are dropped — sidebar renders human-owned content as AI #45

Description

@kylemaclaren

Provenance authored:* marks are dropped — sidebar renders human-owned content as AI

Summary

The core "every character knows its author" promise is breaking in practice. In a shared doc, authored:* marks disappear from /state.marks under two different conditions, and the editor's gutter/sidebar falls through to rendering the affected spans as AI (purple) when the text was actually human-owned.

Two separate failure modes observed in the same session:

  1. Live human typing does not produce an authored:human:* mark. Typing directly into the browser editor leaves the new span with no provenance mark at all.
  2. POST /ops with type: "suggestion.accept" appears to wipe pre-existing authored:* marks on spans other than the accepted suggestion's own span.

Severity

User-visible. Breaks the feature most prominently advertised in the onboarding doc ("Every character knows its author"). Not a data-loss bug — the document text is intact — but the provenance UI is actively misleading.

Environment

  • Site: https://www.proofeditor.ai
  • Doc slug used to repro: bzbxikyp (happy to share tokens and raw /state dumps privately)
  • Agent: Cursor / Claude Opus 4.7 driving the HTTP API as cursor-composer-kmiller
  • User: typing directly in Chrome on macOS
  • Date: 2026-04-22

Repro

Starting from a fresh share with some seeded content:

  1. Agent edit (works correctly). Agent calls POST /api/agent/<slug>/edit/v2 with insert_after on an existing block. GET /state shows a new authored:ai:<agent-id>:<range> mark covering the inserted span. Gutter shows purple on that block, as expected.
  2. Human edit (bug A). User types a paragraph directly in the browser editor. No authored:human:* mark appears in /state.marks for the new span. In the editor UI, the user-reported observation is that the entire doc's gutter flipped to purple at this moment — not just the new paragraph.
  3. Agent suggestion.accept (bug B). Agent calls POST /ops with type: "suggestion.add" for a kind: "replace", then POST /ops with type: "suggestion.accept" on that markId. The accepted suggestion applies correctly to the document body, and a new authored:ai:<agent-id>:<range> mark appears on the accepted span. However, the previously-correct authored:ai:<agent-id>:<range> mark from step 1 is now missing from /state.marks.

After step 3, /state.marks contains exactly one authored:* mark across the full 17-block doc — the span written by the accept operation. Every other piece of text (both human-owned original content and the agent's earlier blockquote) has no provenance attribution.

Quick verification command:

jq '.marks | to_entries | map(select(.value.kind == "authored")) | map({markId: .key, by: .value.by, startRel: .value.startRel, endRel: .value.endRel})' state.json

Expected behavior

  • Live human edits in the browser should produce authored:human:<name>:<range> marks on the newly-authored span, same contract as agent edits produce authored:ai:* marks.
  • suggestion.accept should touch authorship only for the span it writes. Pre-existing authored:* marks on unrelated spans should be preserved across accept, reject, comment, and reply operations.

Hypothesis

  • Bug A (live typing): the Yjs-side provenance emitter that runs for agent edits may not be wired up for local browser edits, or the user identity / mark-kind resolver isn't producing authored:human on the editor path.
  • Bug B (accept wipes marks): suggestion.accept may be recomputing the set of authored:* marks from scratch using only its own write intent, rather than delta-patching. This would explain why only the accept's own span survives.

Both behaviors may share a common cause if they funnel through the same provenance-mark reducer.

Evidence

Marks after step 3 (raw /state.marks snippet)

{
  "a5dbf6c9-0aab-4644-ab60-2aafa1697db8": {
    "kind": "comment",
    "by": "ai:cursor-composer-kmiller",
    "startRel": "char:355",
    "endRel": "char:388",
    "quote": "Every character knows its author."
  },
  "authored:ai:cursor-composer-kmiller:311-360": {
    "kind": "authored",
    "by": "ai:cursor-composer-kmiller",
    "startRel": "char:305",
    "endRel": "char:354",
    "quote": "Green = human authorship. Purple = AI authorship."
  }
}

Doc also contains:

  • A blockquote ("Hello from Cursor — I am kmillers coding agent...") that was inserted by the agent in step 1 and had an authored:ai:cursor-composer-kmiller:123-230 mark immediately after insertion. That mark is no longer present.
  • A user-typed paragraph ("Hi, this is Kyle. How do human comments work?") that has never had an authored:* mark in any /state snapshot taken during the session.

Earlier /state.marks (after step 1, before step 3)

Contained an authored:ai:cursor-composer-kmiller:123-230 mark on the agent's blockquote. Can reconstruct exact bytes on request — I have the full /state payloads from four points in the session.

Related hosted-side context

  • contract.mutationStage: A
  • contract.preconditionMode: token-only
  • readSource: yjs
  • readAuthority: authoritative_live

Questions for the team

  1. Is the provenance-mark computation in EveryInc/proof-sdk (packages/doc-core?) or in hosted-only code? I'm about to self-host proof-sdk for a personal tool and want to know whether to expect this bug in my deployment too.
  2. If it's in OSS code, I'm happy to take a first pass at a fix / open a PR — just point me at the right module.

Related

Filed companion issue re: /bridge/report_bug returning 502 REPORT_BUG_CREATE_FAILED, which is why this is coming in as a GitHub issue rather than via the bridge. If that weren't broken, this evidence would have landed on your side under requestId: 9e57521d-6a21-4a6a-a201-f56432676581 (evidence was captured locally per that response).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions