[AIT-1204] Add RTLO4e10 and RTO10c1b1: the root object must never be tombstoned or GC-evicted#509
Merged
Merged
Conversation
Contributor
Author
|
@coderabbitai review |
There was a problem hiding this comment.
Pull request overview
This PR updates the LiveObjects specification and UTS coverage to harden the “root” object invariant: the root object must never be tombstoned and must never be GC-evicted from the ObjectsPool, even in the presence of faulty inbound messages.
Changes:
- Adds spec clause RTLO4e10 to reject any attempt to tombstone
root(log warning, return noop update, no state change). - Adds spec clause RTO10c1b1 to ensure the GC sweep never removes
rootfromObjectsPool. - Updates/extends UTS specs to (a) add root-rejection tests and (b) repoint existing tombstone-mechanics tests away from
rootto a tombstoneable non-root object.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| specifications/objects-features.md | Adds RTLO4e10 (reject tombstoning root) and RTO10c1b1 (GC must never remove root). |
| uts/objects/unit/internal_live_map.md | Adds RTLO4e10 unit tests and updates existing tombstone tests to use a non-root map id. |
| uts/objects/unit/realtime_object.md | Adds a channel-level test asserting a rogue OBJECT_DELETE targeting root does not break root functionality after the GC grace period. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
sacOO7
added a commit
that referenced
this pull request
Jul 23, 2026
…in isolation RTLO4e10 makes a tombstoned root unreachable in a conforming implementation, so the channel-level test verifies the composed behaviour and the GC-side exclusion remains defense-in-depth verified by its spec tag rather than by fabricating an impossible state. Addresses review feedback on #509.
…or GC-evicted RTO10c1b as written compelled a compliant SDK to violate RTO3b: an OBJECT_DELETE (or a tombstoned ObjectState) targeting the root object would tombstone it - clearing its data, deregistering all listeners, and (since tombstone is a terminal state) making the channel's objects permanently unrecoverable - after which the GC sweep would evict root from the ObjectsPool entirely. - RTLO4e10: LiveObject.tombstone must reject the root object with a warning and a noop LiveObjectUpdate. Placed in the shared tombstone method so it covers both the RTLO5 operation path and the RTLC6f/RTLM6f sync paths. - RTO10c1b1: the GC sweep must never remove the root object from the ObjectsPool (mirrors RTO5c2a), as a safeguard for the RTO3b invariant. UTS test specs: - internal_live_map.md: new RTLO4e10 tests for the operation and sync paths; the three existing tombstone-mechanics tests (RTLO5, RTLM6f, RTLO4e9) are repointed from objectId "root" to a tombstoneable non-root map, reconciling the spec with the ids the ably-js derived tests already used. - realtime_object.md: new channel-level RTO10c1b1 test verifying root survives a GC sweep after a rogue OBJECT_DELETE and stays functional. Raised in ably/ably-js#2219 review discussion r3558197916.
…in isolation RTLO4e10 makes a tombstoned root unreachable in a conforming implementation, so the channel-level test verifies the composed behaviour and the GC-side exclusion remains defense-in-depth verified by its spec tag rather than by fabricating an impossible state. Addresses review feedback on #509.
sacOO7
force-pushed
the
fix/rtlo4e10-root-tombstone-guard
branch
from
July 23, 2026 09:17
688a792 to
e1c9a95
Compare
ttypic
pushed a commit
to w3lld1/ably-js
that referenced
this pull request
Jul 24, 2026
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 ably#2219 review discussion r3558197916
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes https://ably.atlassian.net/browse/AIT-1204
Problem
Raised in the ably-js PR review: ably/ably-js#2219 (discussion) — the GC sweep deletes any tombstoned object past the grace period with no protection for the root object.
Investigating it surfaced a spec-level inconsistency:
ObjectsPool"must always contain" the rootInternalLiveMap.And the deeper gap: nothing prevented the root object from being tombstoned in the first place. Per RTLO4e the realtime system only tombstones orphaned/uninitialized objects — root is by definition neither — but no client-side guard existed. If a faulty
OBJECT_DELETE(or a tombstonedObjectState) ever targeted root:rootcannot be re-created as a zero-value object ("root"is not parseable as an object id per RTO6b1), so every subsequent objects operation on the channel fails unrecoverably.Changes
Feature spec (
objects-features.md)LiveObject.tombstonemust reject the root object: log a warning, return a noopLiveObjectUpdate, perform no state change. Placed in the shared tombstone method so one guard covers both the RTLO5 operation path and the RTLC6f/RTLM6f sync paths.ObjectsPool(mirrors RTO5c2a). Defense-in-depth for the RTO3b invariant — root can never become tombstoned per RTLO4e10, so this is a safeguard.UTS test specs
internal_live_map.mdRTLO4e10/object-delete-root-noop-0(operation path) andRTLO4e10/replace-data-tombstone-root-noop-0(sync path) — root is not tombstoned, data untouched, noop update returned.RTLO5,RTLM6f,RTLO4e9) are repointed fromobjectId: "root"to a tombstoneable non-root map (map:test@1000). Their stimuli would noop under the new guard; the assertions themselves are unchanged. This also reconciles the spec with the non-root ids the ably-js derived tests already used.realtime_object.mdRTO10c1b1/gc-root-never-removed-0: a rogueOBJECT_DELETEtargeting root leaves it untouched, and after the GC grace period elapses root remains in the pool and still applies subsequent operations.Severity note
Exposure requires the server to send something the protocol says it never sends (clients must never publish
OBJECT_DELETE, and the system never targets root), so this is defensive hardening — but the blast radius is terminal and unrecoverable, and RTO5c2a already set the precedent of defending root against exactly this class of anomaly.SDK implementations
fix/liveobjects-objects-audit-op-handling(PR to follow)