Skip to content

[AIT-1204] fix(liveobjects): reject tombstoning of the root object (RTLO4e10, RTO10c1b1) - #2270

Merged
sacOO7 merged 2 commits into
uts-liveobjectsfrom
fix/rtlo4e10-root-tombstone-guard
Jul 23, 2026
Merged

[AIT-1204] fix(liveobjects): reject tombstoning of the root object (RTLO4e10, RTO10c1b1)#2270
sacOO7 merged 2 commits into
uts-liveobjectsfrom
fix/rtlo4e10-root-tombstone-guard

Conversation

@sacOO7

@sacOO7 sacOO7 commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Fixes https://ably.atlassian.net/browse/AIT-1204

Problem

Raised by the review discussion on #2219 (r3558197916): deleteExtraObjectIds protects the root object during sync cleanup (RTO5c2a), but the GC sweep in _onGCInterval deletes any tombstoned object past the grace period — including root.

Tracing it showed the gap starts earlier: nothing prevented root from being tombstoned at all. If a faulty OBJECT_DELETE (or a tombstoned object state during sync) ever targeted root:

  1. Tombstone alone is already fatal — root's data is cleared and all its subscriptions are deregistered; tombstone is a terminal state (overrideWithObjectState skips tombstoned objects), so no re-sync or re-attach ever recovers it.
  2. GC eviction then makes it unrecoverablegetRoot() returns undefined, resetToInitialPool() throws on the next sync, and root can never be re-created as a zero-value object (ObjectId.fromString('root') throws, which would also abort the whole ProtocolMessage batch).

Per RTLO4e the realtime system only tombstones orphaned or uninitialized objects — root is by definition neither — so such a message indicates a fault and is now rejected client-side with a warning, consistent with the existing warn-and-skip handling for invalid inbound operations (RTLO4a3, RTLC7d3/RTLM15d4).

Changes

Source

File Change Spec
liveobject.ts tombstone() rejects the root object: warning + noop update, no state change. Single choke point — covers the OBJECT_DELETE operation path (RTLO5) and both sync paths (RTLC6f/RTLM6f). Return type widened to TUpdate | LiveObjectUpdateNoop. RTLO4e10
objectspool.ts _onGCInterval() never evicts root from the pool (mirrors the RTO5c2a guard). Root's tombstoned map entries still get entry-level GC. RTO10c1b1
livemap.ts / livecounter.ts overrideWithObjectState() returns the tombstone branch early (it can now yield a noop update); the non-tombstone flow is unchanged, just de-nested.

Derived UTS tests

  • live_map.test.tsRTLO4e10/object-delete-root-noop-0 and RTLO4e10/replace-data-tombstone-root-noop-0: root not tombstoned, data untouched, noop update produced.
  • realtime_object.test.tsRTO10c1b1/gc-root-never-removed-0: a rogue backdated OBJECT_DELETE on root, then a full GC sweep (observed via a control counter's eviction), then a MAP_SET that still applies — proving root survived the sweep and stayed live. Header deviation note extended to cover the GC rendering.

Spec twin

ably/specification#509 adds the RTLO4e10/RTO10c1b1 clauses and the UTS test specs, and repoints the three existing tombstone-mechanics test specs (RTLO5, RTLM6f, RTLO4e9) from objectId: "root" to a non-root map — the derived tests in this repo already used non-root ids, so no changes to those tests were needed here.

The same guards are implemented for ably-java (PR to follow).

Testing

  • New and touched UTS unit files green: live_map.test.ts (41 passing), realtime_object.test.ts (incl. new GC test); full UTS objects unit sweep 316 passing.
  • Legacy test/realtime/liveobjects.test.js against sandbox: 510 passing, 0 failing (no legacy test tombstones root — verified by sweep; existing tombstone: true usages are entry-level only).
  • tsc --noEmit and prettier clean.

Summary by CodeRabbit

  • Bug Fixes
    • Prevented accidental deletion or tombstoning of the root object.
    • Root-targeted delete and incoming state-override operations now safely produce no-op updates.
    • Garbage collection now preserves the root object while still removing tombstoned, grace-period-expired objects.
    • Root data remains intact, and normal updates continue to work after cleanup.
  • Tests
    • Added unit coverage for root delete/state-override handling and GC ensuring the root object is never removed.

A faulty OBJECT_DELETE operation (or a tombstoned object state) targeting
the root object would tombstone it: data cleared, all subscriptions
deregistered, and - tombstone being a terminal state - no re-sync could
ever resurrect it. After the GC grace period the sweep would then evict
root from the ObjectsPool entirely, violating RTO3b; root cannot be
re-created as a zero-value object ('root' is not a parseable object id),
so every subsequent objects operation on the channel fails unrecoverably.

Per RTLO4e the realtime system only tombstones orphaned or uninitialized
objects - root is by definition neither - so such a message indicates a
fault and is now rejected client-side, consistent with the existing
warn-and-skip handling for other invalid inbound operations.

- LiveObject.tombstone() rejects the root object with a warning and a
  noop update (RTLO4e10); single choke point covering both the RTLO5
  operation path and the RTLC6f/RTLM6f sync paths
- ObjectsPool._onGCInterval() never evicts root (RTO10c1b1, mirroring
  the existing RTO5c2a guard in deleteExtraObjectIds); root's tombstoned
  map entries still get RTLM19 entry-level GC
- overrideWithObjectState() in LiveMap/LiveCounter returns the tombstone
  branch early since tombstone() can now return a noop update

Derived UTS tests: RTLO4e10 operation/sync path tests in live_map.test.ts,
channel-level RTO10c1b1 GC test in realtime_object.test.ts.

Spec twin: ably/specification#509
Raised in #2219 review discussion r3558197916
@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Root tombstone and delete operations now return no-op updates instead of removing the root object. Garbage collection also excludes the root, while map and counter state overrides use early-return tombstone handling. Tests cover root targeting and post-GC root updates.

Changes

Root object protection

Layer / File(s) Summary
Tombstone and override no-op handling
src/plugins/liveobjects/liveobject.ts, src/plugins/liveobjects/livecounter.ts, src/plugins/liveobjects/livemap.ts, test/uts/objects/unit/live_map.test.ts
Root tombstone and delete operations return { noop: true }; map and counter overrides return immediately for tombstoned state, with tests covering root targeting.
Root exclusion from garbage collection
src/plugins/liveobjects/objectspool.ts, test/uts/objects/unit/realtime_object.test.ts
GC requires objects to be non-root and past the grace period, with coverage confirming the root remains usable after GC.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ObjectState
  participant LiveMap
  participant LiveObject
  ObjectState->>LiveMap: overrideWithObjectState(tombstone)
  LiveMap->>LiveObject: tombstone(objectMessage)
  LiveObject-->>LiveMap: noop update
Loading

Possibly related PRs

  • ably/ably-js#2257: Covers related root deletion, garbage-collection exclusion, and companion no-op behavior tests.

Suggested reviewers: ttypic, vesker

Poem

I guarded the root with a hop and a cheer,
No tombstone shall topple it here.
The map keeps its state,
GC shuts the gate,
And updates still bloom year to year.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: preventing the root live object from being tombstoned.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/rtlo4e10-root-tombstone-guard

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions
github-actions Bot temporarily deployed to staging/pull/2270/features July 22, 2026 14:47 Inactive
@github-actions
github-actions Bot temporarily deployed to staging/pull/2270/bundle-report July 22, 2026 14:47 Inactive
@github-actions
github-actions Bot temporarily deployed to staging/pull/2270/typedoc July 22, 2026 14:48 Inactive

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@test/uts/objects/unit/realtime_object.test.ts`:
- Around line 924-925: Update the GC completion poll in the realtime object test
to check whether root.get('score') is undefined directly, rather than calling
value() on the missing object. Preserve the existing pollUntil flow and
completion condition.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8e32b3f1-4075-4f4a-9e92-85feb69d7735

📥 Commits

Reviewing files that changed from the base of the PR and between c963991 and b1496ad.

📒 Files selected for processing (6)
  • src/plugins/liveobjects/livecounter.ts
  • src/plugins/liveobjects/livemap.ts
  • src/plugins/liveobjects/liveobject.ts
  • src/plugins/liveobjects/objectspool.ts
  • test/uts/objects/unit/live_map.test.ts
  • test/uts/objects/unit/realtime_object.test.ts

Comment thread test/uts/objects/unit/realtime_object.test.ts

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens the LiveObjects implementation against faulty inbound operations/state that attempt to tombstone (and later GC-evict) the special root object, ensuring root remains present and usable across sync and GC cycles.

Changes:

  • Reject attempts to tombstone the root object by returning a noop update and logging a warning.
  • Add an additional safeguard to GC so the root object is never removed from the ObjectsPool.
  • Add/extend UTS unit coverage for root-targeted deletes/state overrides and for GC behavior preserving root.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
test/uts/objects/unit/realtime_object.test.ts Adds a GC regression test proving root survives a GC sweep even when a rogue delete is received.
test/uts/objects/unit/live_map.test.ts Adds tests asserting root-targeted tombstone/delete paths are rejected and yield noop updates.
src/plugins/liveobjects/objectspool.ts Prevents GC from evicting the root object from the pool.
src/plugins/liveobjects/liveobject.ts Rejects tombstoning of the root object (warn + noop) and widens return types accordingly.
src/plugins/liveobjects/livemap.ts Refactors tombstone override path to return early (supporting noop updates).
src/plugins/liveobjects/livecounter.ts Refactors tombstone override path to return early (supporting noop updates).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/plugins/liveobjects/liveobject.ts Outdated
…ejection warning

A rejected root tombstone indicates a faulty inbound message, and the
site/serial is what identifies its origin; matches the adjacent RTLO4a3
warning style. Addresses review feedback on #2270.
@sacOO7 sacOO7 changed the title fix(liveobjects): reject tombstoning of the root object (RTLO4e10, RTO10c1b1) [AIT-1204] fix(liveobjects): reject tombstoning of the root object (RTLO4e10, RTO10c1b1) Jul 23, 2026

@ttypic ttypic left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@sacOO7
sacOO7 merged commit e7d47b8 into uts-liveobjects Jul 23, 2026
17 checks passed
@sacOO7
sacOO7 deleted the fix/rtlo4e10-root-tombstone-guard branch July 23, 2026 09:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants