Skip to content

[Feature] Path based LiveObjects Implementation#1214

Draft
sacOO7 wants to merge 62 commits into
mainfrom
feature/path-based-liveobjects-implementation
Draft

[Feature] Path based LiveObjects Implementation#1214
sacOO7 wants to merge 62 commits into
mainfrom
feature/path-based-liveobjects-implementation

Conversation

@sacOO7

@sacOO7 sacOO7 commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator

sacOO7 added 6 commits June 8, 2026 18:20
- 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
@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6578ccaf-e608-441c-bc91-726e4bd5362f

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/path-based-liveobjects-implementation

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/1214/features June 11, 2026 12:42 Inactive
@github-actions github-actions Bot temporarily deployed to staging/pull/1214/javadoc June 11, 2026 12:43 Inactive
sacOO7 and others added 3 commits June 15, 2026 19:34
…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
@github-actions github-actions Bot temporarily deployed to staging/pull/1214/features June 16, 2026 12:01 Inactive
@github-actions github-actions Bot temporarily deployed to staging/pull/1214/javadoc June 16, 2026 12:02 Inactive
sacOO7 and others added 16 commits June 16, 2026 23:11
…mplementation' into chore/liveobjects-add-basic-implementation
- 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>
sacOO7 and others added 13 commits June 25, 2026 22:27
…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.
…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
@github-actions github-actions Bot temporarily deployed to staging/pull/1214/features June 30, 2026 10:30 Inactive
@github-actions github-actions Bot temporarily deployed to staging/pull/1214/javadoc June 30, 2026 10:31 Inactive
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.
@github-actions github-actions Bot temporarily deployed to staging/pull/1214/features June 30, 2026 10:43 Inactive
@github-actions github-actions Bot temporarily deployed to staging/pull/1214/javadoc June 30, 2026 10:44 Inactive
sacOO7 added 6 commits June 30, 2026 16:33
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
@github-actions github-actions Bot temporarily deployed to staging/pull/1214/features July 9, 2026 11:38 Inactive
@github-actions github-actions Bot temporarily deployed to staging/pull/1214/javadoc July 9, 2026 11:40 Inactive
…ntation

[AIT-934] feat(liveobjects): `LiveObjects` implementation to new path-based API (Kotlin)
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.

1 participant