[AIT-1245] objects & uts: RTO27 channel-state data lifecycle, objects/unit test-spec corrections, and UTS authoring conventions - #512
Conversation
Corrections and clarifications to the objects UTS unit test specs, surfaced while translating them to ably-java and cross-checked against ably-js and ably-cocoa source. - RTO17-RTO18 (realtime_object.md): remove the "re-attach after detach" sync-event scenario. It is redundant with "re-sync on new ATTACHED" (identical onAttached -> new-sync path) and not portably expressible - an unsolicited server DETACHED transitions the channel to SUSPENDED (not DETACHED) in some SDKs. ably-js already folds it into "re-sync on new ATTACHED". - RTO18d (realtime_object.md, objects-features.md): remove the duplicate-listener UTS test and add an editor's note. Whether re-registering the same listener fires once or twice is a platform-idiomatic design choice (verified in source: ably-js and ably-cocoa append -> twice; ably-java de-duplicates by listener instance -> once), rooted in the general EventEmitter contract (RTE4). Not universally testable; each SDK pins its own. - Op-path LiveObjectUpdate portability (internal_live_counter.md, internal_live_map.md): note that the update returned by applyOperation may be observed via the return value OR the emitted update event / resulting state, so event-based / typed SDKs can satisfy the same requirement. - Read-after-send quiescence (path_object.md): add a poll_until barrier before the positive reads in RTPO13c5 / RTPO14 (inbound ops apply asynchronously). - objects_pool.md: add a NOTE documenting the SDK-internal "DETACHED/FAILED clears objects data; SUSPENDED retains" behaviour and why it is deliberately not a UTS requirement (no normative point; unobservable via the public API), guarded by SDK-local tests. - internal_live_counter_api.md: assert the null-means-omitted increment contract directly where null is not distinguishable from an omitted argument.
paddybyers
left a comment
There was a problem hiding this comment.
Pls review the comments.
(Also fyi @mschristensen @VeskeR )
|
This PR is in progress locally, will update and mark it as ready for review |
…c point + UTS test Addresses the PR review on the objects UTS cross-SDK corrections. - RTO18d (realtime_object.md, objects-features.md): restore the duplicate-listener UTS test (asserting the RTE4 "fires twice" contract) instead of removing it, and drop the editor's note from the spec. The UTS tests the spec's assertion; SDKs that de-duplicate listeners by instance (e.g. ably-java) may assert fires-once via an optional note on the test — the deviation stays with the SDK, not the UTS. - RTO27 (objects-features.md, NEW): add an explicit normative point for the objects-data lifecycle on channel state transitions — on DETACHED/FAILED clear every pooled object's data without emitting update events (and clear the SyncObjectsPool); on SUSPENDED retain it. Placed in the channel-lifecycle cluster (after RTO5) and cross-referenced from RTO20e1. Closes a previously-unspecified gap both reference SDKs already implement (ably-js RealtimeObject.actOnChannelState, ably-java DefaultRealtimeObject.handleStateChange). - RTO27 UTS test (realtime_object.md): add a white-box test that drives the internal channel-state handler directly and inspects the ObjectsPool (mirroring the internal_live_counter / internal_live_map tests), so the clear (not observable via the public API) and SUSPENDED (a connection-level state) are both testable at the unit tier. Reframe the objects_pool.md / realtime_object.md notes to reference RTO27. - path_object.md: reword the RTPO13c5 / RTPO14 read-after-send quiescence barrier to clarify it guards an INBOUND server OBJECT message (no apply-on-ack point to await), not a local publish.
…ming conventions Follow-up to the RTO27 work and the PR review. - objects_pool.md: remove the DETACHED/FAILED detach-clear NOTE. It was a stopgap for a missing spec point; now that RTO27 is normative and has its own white-box test in realtime_object.md, the note is redundant (and was a RealtimeObject concern, not a bare ObjectsPool one). - realtime_object.md: rename the RTO27 test's abstract white-box symbols to the UTS camelCase convention (objects_pool -> objectsPool, handle_channel_state -> processChannelState); drop the dangling "See objects_pool.md" pointer and the redundant RTO27 cross-reference from the no-re-attach-after-detach note, trimming that note to its two load-bearing reasons (redundant + not portably expressible); extend the header spec points to RTO22-RTO27 to match the file's contents. - docs/writing-test-specs.md, README.md: add an "Identifier Naming" convention section (spec surface mirrors the specification's names — PascalCase types, camelCase fields/methods; behaviours the spec describes but doesn't name get a camelCase coinage; snake_case is reserved for test-harness constructs; wire/enum values keep protocol/spec casing). Validated against objects/realtime/rest UTS specs and the features.md/protocol.md parent specs.
…ation recording conventions
- internal_live_counter.md, internal_live_map.md: remove the "SDK portability note" blocks.
ably-java's op path now returns the LiveObjectUpdate (RTLC9g/RTLM7f), so the boolean-return
divergence they described no longer exists; the UTS simply asserts the return contract, and any
SDK that diverges records it in its own deviations.md rather than in the shared spec. These notes
were added earlier in this PR, so they net out of the final diff.
- docs/writing-derived-tests.md (§ Recording deviations, § Test patterns, decision tree): make the
deviation-handling guidance explicit and internally consistent so it can be followed without
ambiguity:
- the SDK-deviation choice is disposition-based — env-gated skip for a bug you expect to fix,
adapted assertion (preferred) for a permanent/intentional divergence with stable behaviour;
§2c and the decision tree now point at both patterns instead of "adapt" unconditionally.
- new "Idiomatic translation vs genuine deviations" subsection: idiomatic public-API naming is
not a deviation (just translate it); public-behaviour differences are deviations at any tier;
internal-API shape differences are unit-tier-only adaptations that must preserve coverage.
- keep all four section headings present in every deviations.md (mark empty ones "(none)"); add
Status/Resolution entry fields and the shape-deviation vocabulary convention.
- fixes: "four advantages" count, and "Test impact" wording no longer assumes adaptation.
…d wording - realtime_object.md (RTO27): assert the nested pooled map map:profile@1000 independently — cleared on DETACHED/FAILED, retained on SUSPENDED — checked via the pool (not via root navigation) so a nested-object regression can't be hidden by the root clear. Addresses the CodeRabbit review on ably-js#2278; mirrored in the ably-java and ably-js derived tests. - writing-derived-tests.md: drop the redundant "Also record…" lead-in so the deviation entry fields read as a single 1–7 list, and qualify the two non-core fields — Status "(for an SDK deviation)" and Resolution "(once resolved)" — so they don't read as mandatory on every entry (a UTS Spec Error / Mock Infra entry has no such disposition; Resolution is omitted until there is one).
… with @SPEC RTO27 - realtime_object.test.ts (RTO27): assert the nested pooled map map:profile@1000 independently — cleared on DETACHED/FAILED, retained on SUSPENDED — via the pool (size 3 -> 0 / stays 3), so a nested-object regression can't be hidden by the root clear. Addresses the CodeRabbit review; mirrors the spec change in ably/specification#512 and the ably-java derived test. - realtimeobject.ts: annotate actOnChannelState with @SPEC RTO27 (and inline RTO27a/a1/a2/b) — comment-only; the behaviour was previously unannotated.
There was a problem hiding this comment.
Pull request overview
This PR updates the LiveObjects specification and its Universal Test Suite (UTS) documentation/tests by introducing a new normative spec point (RTO27) for object-data lifecycle across channel state transitions, correcting several objects/unit UTS specs for portability/spec-faithfulness, and formalising UTS authoring conventions (identifier naming + recording SDK deviations).
Changes:
- Add normative spec point
RTO27describing objects-data handling onDETACHED/FAILED/SUSPENDEDtransitions, plus a matching white-box unit UTS test. - Refine/correct
objects/unitUTS specs (listener duplication optionality note, remove non-portable “re-attach after detach” scenario, add quiescence barriers, clarify null/omitted increment semantics). - Document UTS authoring conventions for identifier casing and deviation-handling patterns.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| uts/README.md | Adds a concise identifier-naming convention summary with a link to the full rules. |
| uts/objects/unit/realtime_object.md | Updates spec-point coverage, adjusts sync scenarios, and adds the new RTO27 white-box unit test. |
| uts/objects/unit/path_object.md | Adds quiescence barriers before positive reads after inbound OBJECT messages. |
| uts/objects/unit/internal_live_counter_api.md | Clarifies how to handle null-vs-omitted increment semantics in derived tests. |
| uts/docs/writing-test-specs.md | Adds a detailed “Identifier Naming” convention section for UTS pseudocode. |
| uts/docs/writing-derived-tests.md | Refines guidance on deviation patterns and adds an “Idiomatic translation vs genuine deviations” section. |
| specifications/objects-features.md | Introduces new normative spec point RTO27 for object-data lifecycle on channel state transitions. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…ment note Addresses the two review comments on PR #512: - RTO27b generalized from SUSPENDED-only to a catch-all: SUSPENDED plus every other non-{DETACHED,FAILED} state (INITIALIZED/ATTACHING/DETACHING) retains the stored objects data unchanged, so RTO27a+RTO27b are now total over all non-ATTACHED transitions (no behaviour change in ably-js/ably-java, which already fall through for those states). - Add reciprocal cross-references between RTO4 (ATTACHED) and RTO27 (non-ATTACHED) so the two halves of the channel-state lifecycle are mutually discoverable. - internal_live_counter_api.md: reword the null-row note around the omitted-argument call increment() rather than increment(null), keeping the null-means-omitted aside scoped to languages that treat null and the absent value as equivalently nullish, and explicitly not endorsing null as a valid amount.
…x, path_object barriers Derived-test updates matching the objects/unit UTS spec changes in ably/specification#512. Test specs only — no SDK changes. - RTO27 (realtime_object): new tests for the objects-data lifecycle on channel state transitions — DETACHED/FAILED clear every pooled object's data to its zero value without emitting update events (objects remain in the pool); SUSPENDED retains the data. Driven via the internal actOnChannelState handler and asserted against _objectsPool (not black-box observable). - RTO17/RTO18 (realtime_object): rename "ATTACHED without HAS_OBJECTS emits SYNCED only" to "... emits SYNCING then SYNCED", matching RTO4c (sync state always transitions SYNCING -> SYNCED, even without the HAS_OBJECTS flag). - RTPO13c5 / RTPO14 (path_object): replace the flushAsync() barrier with the spec's poll_until(condition) — poll until the inbound MAP_SET ("back_ref") has applied before the positive read. Verified: realtime_object 41 passing, path_object 30 passing.
… with @SPEC RTO27 - realtime_object.test.ts (RTO27): assert the nested pooled map map:profile@1000 independently — cleared on DETACHED/FAILED, retained on SUSPENDED — via the pool (size 3 -> 0 / stays 3), so a nested-object regression can't be hidden by the root clear. Addresses the CodeRabbit review; mirrors the spec change in ably/specification#512 and the ably-java derived test. - realtimeobject.ts: annotate actOnChannelState with @SPEC RTO27 (and inline RTO27a/a1/a2/b) — comment-only; the behaviour was previously unannotated.
Summary
This started as small corrections to the objects
objects/unitUTS specs — surfaced while translating them to ably-java and cross-checking against ably-js and ably-cocoa — and, through review, grew into three related pieces of work:RTO27— specifying the objects-data lifecycle across channel state transitions, plus its UTS test.objects/unitUTS test specs — so several tests are portable and spec-faithful.Net effect vs
main: 7 files. No change to existing normative behaviour beyond specifyingRTO27(which documents behaviour both reference SDKs already implement).1. New normative point:
RTO27— objects-data lifecycle on channel state transitionsspecifications/objects-features.mdgainsRTO27: on a channel transition toDETACHED/FAILEDthe client clears every pooled object's data without emitting update events (and clears theSyncObjectsPool); onSUSPENDEDit retains the data. This closes a previously-unspecified gap that both reference SDKs already implement (ably-jsRealtimeObject.actOnChannelState, ably-javaDefaultRealtimeObject.handleStateChange). It sits in the channel-lifecycle cluster (after RTO5) and cross-references RTO20e1.realtime_object.mdgains the matching white-box UTS test (RTO27/channel-state-data-lifecycle-0): it drives the internal channel-state handler directly and inspects theObjectsPool, because the behaviour is not reachable black-box (access onDETACHEDthrows per RTO25b, andSUSPENDEDis a connection-level state).2.
objects/unittest-spec correctionsrealtime_object.md). Asserts the RTE4 reference ("the same listener registered twice fires twice") and adds an OPTIONAL note sanctioning SDKs that de-duplicate listeners by instance (e.g. ably-java) to assertcall_count == 1or skip. Resolves review comment Move the features specification to this repository #1 — the shared test is kept, not deleted; SDK divergence is handled per-SDK.realtime_object.md), with a short guard note: it is redundant with "re-sync on new ATTACHED" (sameonAttached → new-syncpath, RTO4c) and not portably expressible (an unsolicited serverDETACHEDbecomesSUSPENDEDin some SDKs).path_object.md, RTPO13c5 / RTPO14): clarified these guard an inbound server OBJECT message (no apply-on-ack point to await), not a local publish. Resolves review comment Build textile to html #4.internal_live_counter_api.md): assert the equivalence directly wherenullis indistinguishable from an omitted argument.objectsPool,processChannelState), per the identifier-naming convention below.3. UTS authoring conventions
Formalised two previously-implicit conventions so derived-test work is consistent:
writing-test-specs.md§ Identifier Naming, summarised inREADME.md): spec-surface identifiers mirror the specification's own names (PascalCasetypes,camelCasefields/methods); behaviours the spec describes but doesn't name get acamelCasecoinage;snake_caseis reserved for test-harness constructs; wire/enum values keep protocol casing; and file names are unrelated to symbol casing.writing-derived-tests.md): the SDK-deviation choice is disposition-based (env-gated skip for a bug you expect to fix; adapted assertion — preferred — for a permanent/intentional divergence); a new "Idiomatic translation vs genuine deviations" subsection (idiomatic naming is not a deviation; public-behaviour differences are, at any tier; internal-API-shape differences are unit-tier-only adaptations that must preserve coverage); all four deviation-section headings kept present in everydeviations.md;Status/Resolutionentry fields; and theS-1…S-nshape-deviation vocabulary convention.Cross-SDK validation
eventemitter.ts,realtimeobject.ts), ably-cocoa (ARTEventEmitter.m), ably-java (EventEmitter.java,DefaultRealtimeObject.kt).objects/unitsuite was regenerated and runs green;RTO27has a matching test; the op path now returns theLiveObjectUpdate(conforms to RTLC9g/RTLM7f).RTO27behaviour matches both reference SDKs.Non-goals
RTO27.EventEmitterlistener de-duplication) are recorded in each SDK's owndeviations.md, not in the UTS.