feat(collab): separate agent edits from human edits in page history

Page-history snapshots are debounced/coalesced (one per 1–5 min window,
jobId=page.id). A human edit followed by an agent edit in the same window
collapsed into a single snapshot, losing both the pre-agent human state and
a deterministic record of the agent's result.

Two provenance-aware boundaries now bracket an agent intervention:
- Before: on a user->agent transition, onStoreDocument synchronously pins the
  current (pre-agent) human content as its own history version tagged 'user',
  inside the page-write transaction, before the agent overwrites it.
- After: agent stores enqueue an immediate (delay 0), source-keyed history job
  (jobId=`${pageId}:agent`) so the agent's result snapshots deterministically
  as 'agent' and a later human edit (jobId=page.id) cannot coalesce/retag it.

Also add an `id desc` tie-break to findPageLastHistory so "last history" stays
deterministic when two snapshots share a created_at, consistent with
findPageHistoryByPageId.

Known trade-offs (Variant 1): the delay-0 worker re-reads the row, leaving a
millisecond mis-tag window; multiple agent edits in one turn may yield multiple
versions. The reverse agent->human boundary is intentionally out of scope.
This commit is contained in:
vvzvlad
2026-06-17 06:40:28 +03:00
parent b0997cb749
commit 0a9788e89a
2 changed files with 57 additions and 6 deletions

View File

@@ -120,7 +120,12 @@ export class PageHistoryRepo {
.$if(opts?.includeContent, (qb) => qb.select('content'))
.where('pageId', '=', pageId)
.limit(1)
// Secondary `id` tie-break: two snapshots for the same page can share a
// createdAt (e.g. the synchronous pre-agent boundary row and the
// immediate agent snapshot), so order by id to keep "last history"
// deterministic and consistent with findPageHistoryByPageId (id desc).
.orderBy('createdAt', 'desc')
.orderBy('id', 'desc')
.executeTakeFirst();
}