Skip to content

Live subscription/replay reports a delete as an empty put #2738

Description

@kriszyp

A single-record audit replay reports a genuine delete as an empty put. The delete signal is lost, and what the client receives instead is { type: 'put', value: undefined } — a phantom write that never happened.

Mechanism on main

In the single-record (thisId specified) startTime audit-tail replay branch, resources/Table.ts:4600-4636, the loop walks a record's full audit history backward via auditRecord.previousVersion / previousNodeId. Deletes are part of that chain; nothing filters them out. Then, at resources/Table.ts:4632:

if (getFullRecord) auditRecord.type = 'put';

This is unconditional — it stomps the type of every entry in the walk. getFullRecord is !request.rawEvents, i.e. true by default.

A delete entry has no record payload to restore: delete: DELETE (resources/auditStore.ts:465-469) carries neither HAS_RECORD nor HAS_PARTIAL_RECORD, so auditRecord.getValue() (resources/auditStore.ts:750-772) returns undefined. The historyEntry then spreads the already-mutated auditRecord, so the consumer sees a put with no value.

Why this is a defect and not the intended contract

The two sibling replay branches in the same file make the identical auditRecord.getValue(primaryStore, getFullRecord, …) call and both emit type: auditRecord.type verbatim, preserving delete/patch/invalidate:

  • collection-level startTime branch — resources/Table.ts:4464
  • previousCount branch — resources/Table.ts:4513

Only the per-record branch overwrites the type. The inconsistency between three otherwise-parallel branches is the argument.

Not a duplicate

(qa-explorer campaign, QA-682. The finding was recorded UNVERIFIED because the ?startTime= replay path was never engaged live; this filing rests on a source read of origin/main 2026-09-04, which confirms the claim and locates it at :4632 — drifted from the original :3869. A replay-reaching regression test is worth adding with the fix.)

Activity

  1. added this to the v5.3 milestone on Sep 22, 2026
  2. added theissue type on Sep 22, 2026
  3. kriszyp commented on Oct 10, 2026

    @kriszyp
    MemberAuthor

    Duplicate of #2735: same line: the single-record replay branch sets auditRecord.type = 'put' unconditionally, so deletes replay as puts.

    🤖 Claude (Anthropic, Opus 5.5), posted via @kriszyp during an issue grouping pass.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Fields

    Priority

    P2

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions