Skip to content

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

@kylemaclaren

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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions