[Feature] Path based LiveObjects Implementation#1214
Draft
sacOO7 wants to merge 62 commits into
Draft
Conversation
sacOO7
commented
Jun 11, 2026
Collaborator
- PathObject documentation -> https://ably.com/docs/liveobjects/concepts/path-object
- Public API doc -> PathObject based LiveObjects API
- PathObject spec -> https://sdk.ably.com/builds/ably/specification/main/objects-features/
- PathObjectSubscriptionOptions: validate depth fail-fast per RTPO19c1a,
throwing AblyException with ErrorInfo(400, 40003) when depth <= 0.
Depth is now a primitive int; the "no depth / infinite depth" state is
expressed via a new no-arg constructor (mirrors ably-js `{}` options),
so no null handling is needed
- LiveMapValue: defensively copy binary payloads on creation and access,
making the RTLMV3d immutability guarantee real for byte[] values
- ObjectData#getBytes: document that the returned array is the underlying
message payload and must be treated as read-only
- JsonObjectPathObject/JsonArrayPathObject: reword "primitive resolution"
javadoc for clarity
- LiveMapPathObject#at: fix javadoc equivalence example to compile
(get() returns base PathObject, so chain via asLiveMap())
…al-public-api [LiveObjects] Implement path-based LiveObjects public API
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
…ccessor Introduce the public, strongly-typed, path-based LiveObjects entry point on a realtime channel, accessed via `channel.object`. - RealtimeObject: exposes `get()` returning the root LiveMapPathObject, and extends ObjectStateChange to subscribe to objects sync-state events (on/off/offAll). - ObjectStateChange / ObjectStateEvent: the SYNCING/SYNCED sync-state subscription API surface. - ChannelBase.object: a public field providing `channel.object` access. When the LiveObjects plugin is not installed, the field is assigned RealtimeObject.Unavailable - a null-object guard whose methods fail fast with a clear plugin-missing error (statusCode 400, code 40019) instead of an NPE, keeping the `channel.object.<method>()` syntax consistent in both cases. The plugin-present branch is intentionally left as a TODO until the LiveObjects plugin exposes the new io.ably.lib.object.RealtimeObject type (getInstance currently returns the legacy io.ably.lib.objects.RealtimeObjects). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ture - Replaced ObjectsSubscription import with Subscription as per requirement
…tion-for-path-based-interfaces [AIT-928] feat(liveobjects): add path-based RealtimeObject and channel.object accessor
…mplementation' into chore/liveobjects-add-basic-implementation
… relevant error codes under `Errors.kt`
- Marked PathObject#getValue as nullable when value doesn't exist at given path
- Implemented JsonSerializer annotation for better json handling
Point the JSON and MsgPack serializers in io.ably.lib.object.serialization
at the new WireObjectMessage wire model instead of the legacy
io.ably.lib.objects.ObjectMessage, so the new `object` package has no
dependency on the legacy `objects` package.
- DefaultSerialization: implement the new ObjectSerializer interface and
(de)serialize WireObjectMessage arrays (reflectively loaded via
ObjectSerializer.Holder).
- Json/MsgpackSerialization: bind the Wire* types; replace legacy
objectError with the object package's objectStateError (same 500/92000).
- WireObjectMessage: restore the gson annotations required for wire-format
fidelity - @SerializedName("object") on objectState and
@JsonAdapter(WireObjectDataJsonSerializer) on WireObjectData.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fixes checkstyle AvoidStarImport violation on com.google.gson.*. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…aluate mode Make the skill's selection phase deterministic and add an explicit translate-vs-evaluate choice: - Add scripts/resolve_uts.py — a bundled resolver that validates the module directory, reads uts-package-mapping.json, and emits JSON with, per tier, the target dir, Kotlin package, and the candidate specs with derived class names. This replaces the per-run hand-work (regex validation, path joins, snake_case->PascalCase) that the model previously improvised, so Phase 1 is byte-for-byte deterministic. Exclusions are checked relative to the tier base (robust to the checkout location), and --create guards the target name. - Rewrite Phase 1 (Steps A-E) around the resolver: resolve, confirm/create mapping, choose tier, choose specs, choose translate-only vs evaluate. - Gate Step 6 (run/fix) behind evaluate mode per writing-derived-tests.md's Translation (always) vs Evaluation (only when an implementation exists) split; translate-only stops after compile + review. - Make the reference fetch mandatory (WebFetch added to allowed-tools). - Fix the file template to use the resolver's package/className (no hardcoded realtime, no double Test suffix) and the spec's full @uts id; correct stale uts/test/... proxy doc paths.
…rence The objects UTS specs are written in ably-js-style pseudocode, but ably-java is a typed SDK (RTTS1-10 partition). Add references/objects-mapping.md mapping each ably-js symbol to its ably-java equivalent: entry point, async (CompletableFuture/await), the typed PathObject/Instance hierarchies and as* casts, the LiveMapValue write union, creation value types, subscriptions, sync-state events, ValueType, message/operation getters, error codes, path dot-escaping, and the internal-graph caveat for unit specs. Wire it in deterministically: each module declares its translation reference via a `notes` field in uts-package-mapping.json; resolve_uts.py resolves it to `translationNotes` (absolute path, or null), and SKILL.md makes it required reading before Phase 2 when present.
…ap them Translate objects/helpers/standard_test_pool.md into ably-java test helpers in uts/.../unit/liveobjects/helpers.kt: the standard object pool, the protocol- and object-message builders (emitting the integer-coded wire JSON the SDK's Gson expects), setupSyncedChannel/NoAck over the existing MockWebSocket, and buildPublicObjectMessage — which reaches the internal PAOM3/PAOOP3 construction (WireObjectMessage -> DefaultObjectMessage) by reflection, so it runs today even though the rest of :liveobjects is unimplemented. Add testRuntimeOnly(:liveobjects) so that reflection resolves while keeping the compile classpath decoupled. Wire the mapping reference at it: objects-mapping.md gains a "Unit-test helpers" section mapping each spec helper to its Kotlin name, and §11/§13 are corrected to note public_object_message.md is translatable via buildPublicObjectMessage rather than internal-only.
…s.md Record the five objects/unit UTS specs (live_counter, live_map, object_id, objects_pool, parent_references) that cannot be translated into the :uts module. They assert on the internal CRDT graph (ObjectsPool, live nodes, applyOperation, object-id generation, parent references), which is (a) not yet implemented in :liveobjects and (b) Kotlin-`internal`, so unreachable from the :uts test module at compile time. Covers: status of all 15 objects/unit specs (10 translated, 5 blocked), why these target internals, the two blockers, and the realistic visibility options (java-test-fixtures bridge, tests in :liveobjects/src/test, or reflection) with their trade-offs and a recommendation.
Add the integration-tier translation of standard_test_pool.md's REST fixture
provisioning (`provision_objects_via_rest`) under
uts/.../integration/standard/liveobjects/helpers.kt: `provisionObjectsViaRest`
plus op builders (mapSet/mapRemove/mapCreate/counterCreate/counterInc) and value
builders (string/number/boolean/bytes/objectId).
The helper follows the LiveObjects V2 objects REST API (per the OpenAPI), not the
legacy pseudocode: POST /channels/{channel}/object (singular), body is a single
operation or a bare array (no "messages" envelope), each op identified by its
payload key with an objectId/path target. Compiles against :java only
(AblyRest + HttpUtils); used by objects/integration/RTPO15.
Document it in the uts-to-kotlin skill's objects-mapping.md as a new section
("14. Integration-test helpers"), promoted out of the unit-only internal-graph
section, with the TOC renumbered accordingly.
…ation/proxy tests
…efactor - Add ChannelHistoryTest (RTL10d) and TokenRequestTest (RSA9/RSA9a/RSA9g) direct-sandbox integration tests, parameterised over json/msgpack. - Add junit-jupiter-params dependency (@ParameterizedTest / @valuesource). - Consolidate the sandbox host into a single SandboxApp.sandboxHost constant (removed from ProxyManager; ProxySession defaults both hosts to it). - Sync uts/README.md, uts/index.html, and the uts-to-kotlin SKILL.md for the direct-sandbox tier: new ChannelHistoryTest walkthrough, integration-tests umbrella section, section renumber. - Rename unit liveobjects helpers.kt -> Helpers.kt (case-only).
Applied on top of the merge of feature/liveobjects-uts-tests: - Point the liveobjects integration/proxy tests (ObjectsFaultsTest, ObjectsLifecycleTest, ObjectsSyncTest) at SandboxApp.sandboxHost, matching the consolidated sandbox-host constant. - Update objects-mapping.md (§12 nested-cause, §14 sandbox host / V2 REST) and the unit liveobjects tests for the same refactor. - Rename integration liveobjects helpers.kt -> Helpers.kt (case + content). Compiles clean (:uts:compileTestKotlin). The liveobjects tests remain translate-only at runtime (pending LiveObjects plugin wiring in the test harness); the realtime tiers pass.
- objects-mapping.md: correct helper filename casing helpers.kt -> Helpers.kt (broken source-nav on case-sensitive filesystems); tag bare code fences as `text` to clear markdownlint MD040. - README.md: tag bare fences as `text`; correct the msgpack rationale — ably-java does implement msgpack (useBinaryProtocol = true by default), the real reason proxy tests force JSON is that the proxy only handles text WebSocket frames. - index.html: mirror the README msgpack correction. - SKILL.md: label the unit-tier test scaffold as such and point integration/proxy flows at the SandboxApp/ProxySession section. - resolve_uts.py: validate the module path via Path.parts instead of a forward-slash-only regex, so Windows paths are accepted. - ConnectionRecoveryTest: use CopyOnWriteArrayList for the query-param capture list (written on the SDK transport thread, read on the coroutine dispatcher) to remove a visibility race / flake risk. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Translation review (SKILL.md Step 7) relied on eyeballing the spec and the generated Kotlin side-by-side to confirm every test case, setup step, operation and assertion was carried over — exactly the mechanical comparison the model does inconsistently. Add a bundled extractor so the review reconciles a concrete ledger instead. scripts/audit_translation.py (regex-only, no semantic judgement, deterministic): - idCoverage: spec `**Test ID**` set vs Kotlin `@UTS` tags -> missing / orphan. - perTest[].sections: every non-comment pseudo-block line, grouped by section and tagged assert / await / step, so setup + operations + assertions are all enumerated; assertionShortfall flags likely-dropped assertions. - Robustness: never crashes — tolerant decoding plus a top-level guard always emit one JSON object. Exit 0 clean / 2 missing-or-orphan / 64 couldn't-run, via a fail() helper matching resolve_uts.py's error shape. SKILL.md Step 7 now runs the audit first and reconciles its output line by line (coverage + completeness), keeping setup-fidelity and deviation-honesty as the semantic checks; paths reference the resolver's specs[].file / targetDir.
[AIT-1081] Update liveobjects `uts-to-kotlin` skill to generate tests
read_text()/write_text() default to the locale encoding on Windows (often cp1252, not UTF-8) and apply newline translation, which would both mis-handle non-ASCII content and rewrite the git-tracked uts-package-mapping.json with CRLF. Pin the read to utf-8 and write via write_bytes (binary mode does zero newline translation on any OS or Python version, keeping the file LF — unlike write_text(newline=...), which needs Python 3.10+). Aligns with the utf-8 reads already in audit_translation.py.
…ture/uts-liveobjects-unit-tests
Running the Step 7 faithfulness audit (audit_translation.py) across the objects module surfaced three undocumented intentional omissions in the unit tests. Each is benign but was not recorded as a deviation; add entries so the audit reconciles clean: - RTPO10/10d/11/11d — `keys()`/`values()` "IS Array" is a static-type tautology (typed Iterable return), so the type-check line is omitted; count/membership are asserted (PathObjectTest). - RTO23f — `get()` result "IS PathObject" is guaranteed by the LiveMapPathObject return type; the runtime type-check is omitted (RealtimeObjectTest). - RTLC12b/c/d — relocated to RTO26 with no executable spec content, so the marker entry is intentionally not translated (LiveCounterApiTest). Integration and proxy objects tests audited clean; no deviations needed there.
…nted The liveobjects SDK has no implementation yet, so the translate-only liveobjects tests fail at runtime with "LiveObjects plugin hasn't been installed". Exclude them from runUtsUnitTests and runUtsIntegrationTests (unit + integration standard and proxy) so the CI gates pass; they still compile via compileTestKotlin. Marked with a TODO to remove the exclusions once the liveobjects implementation is in place.
Align the skill's Step 6 with the spec manual (ably/specification#498) so a diagnosed UTS spec error is handled spec-first instead of being quietly adapted to green: - Step 6 intro now names three acceptable end-states (spec-correct pass, SDK deviation stays green, UTS spec error fails fast) — removes the earlier "a red test is never acceptable" contradiction. - Decision tree NO-branch: fix the spec at source + record under deviations.md "UTS Spec Errors" + emit a fail-fast test. - Add the "spec-error fail-fast" Kotlin pattern (mirrors the manual's JS example) and retitle the section to "Test patterns for a diagnosed failure" since fail-fast is not a deviation. - Defer the deviations-file entry format to the manual's "Recording deviations" sections (removes the divergent inline field list).
…alue types
Fills in the Kotlin implementation behind the LiveObjects public interfaces
(lib/.../liveobjects), replacing the TODO stubs in DefaultRealtimeObject with
a working objects sync engine and the LiveCounter / LiveMap value types.
Sync engine (io.ably.lib.liveobjects):
- ObjectsManager - processes OBJECT / OBJECT_SYNC messages, buffers ops
during sync, creates zero-value objects (RTO5-RTO7).
- ObjectsPool - object store keyed by objectId with tombstone GC
(grace period from connectionDetails, RTO3).
- ObjectsSyncTracker/ObjectsState - track sync sequence + state transitions
and emit SYNCING/SYNCED events (RTO2, RTO5).
- ObjectId - SHA-256 based object identity from initialValue+nonce
(RTO6b1, RTO14).
- ServerTime - server-time offset with double-checked locking (RTO16).
- ObjectsOperationSource - LOCAL vs CHANNEL operation origin (RTO22).
- DefaultRealtimeObject - wires the above into the RealtimeObject entry point.
Value types (io.ably.lib.liveobjects.value):
- LiveCounter and LiveMap are each split into an Internal* value holder, a
*Manager (applies state/operations), and a *ChangeCoordinator (subscriber
notification), over a shared BaseRealtimeLiveObject. DefaultLiveCounter /
DefaultLiveMap move into livecounter/ and livemap/ subpackages.
Reflection / R8 consistency (see prior review):
- LiveCounter.java / LiveMap.java IMPLEMENTATION_CLASS constants updated to the
new value.livecounter / value.livemap package locations (previously pointed
at the old value.* path and would have thrown ClassNotFoundException).
- android/proguard.txt keep-rules migrated to io.ably.lib.liveobjects.*, aligned
with the actual class locations and reflective constructor signatures, plus
added keeps for WireObjectDataJsonSerializer and the Wire* message model that
Gson (de)serializes by field-name reflection on the JSON transport path.
…PI preconditions to callers Spec-point migration (path-based API): - Replace deprecated/renamed spec-point references throughout the liveobjects impl and the ObjectsMapEntry interface with their current equivalents: access/write preconditions -> RTO25/RTO26, map/counter create steps -> RTLMV4*/RTLCV4*/RTO14, "v6.0.0" renumbers (e.g. RTLM15d2->RTLM15d6, RTLM7c->RTLM7g, RTLC7d2->RTLC7d5), RTO5f2c->RTO5f3, RTO6b4->RTO6b2/RTO6b3, RTTS9d2->RTTS9d, and ME1->OME1. Move access/write API preconditions to callers (RTO25/RTO26): - Remove throwIfInvalidAccessApiConfiguration / throwIfInvalidWriteApiConfiguration from InternalLiveMap (get/entries/size/subscribe/set/remove) and InternalLiveCounter (value/subscribe/increment). The spec now checks these preconditions in the callers; PathObject and Instance already enforce them. - Make the InternalLiveMap/InternalLiveCounter value methods `internal` so they cannot be reached bypassing those wrappers; rename the ctor param realtimeObjects -> realtimeObject. Correctness: - calculateUpdateFromDataDiff returns the no-op update (ObjectUpdate(null)) for an empty map diff / zero counter delta, so notifyUpdated() short-circuits and no spurious change event is emitted when clearing an empty root object.
[AIT-1008] Translate LiveObjects `objects` UTS test specs to Kotlin tests
…ntation [AIT-934] feat(liveobjects): `LiveObjects` implementation to new path-based API (Kotlin)
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.