Skip to content

feat(seat): CP stdio seat arm chain on the folded 5.17.0 basis (hook-series chain + set-streaming arm + contract-tests/seat decoupling) - #412

Open
JackDanger wants to merge 19 commits into
mainfrom
cp-seat
Open

JackDanger wants to merge 19 commits into
mainfrom
cp-seat

Conversation

@JackDanger

@JackDanger JackDanger commented Oct 17, 2026 •

Copy link
Copy Markdown

What

The android family's CP seat face chain, folded onto origin/main (d7d156a, release 5.17.0) per the W10 family-face fold:

  • 3a73e59 — serve the SDK's own reason serialization on the response arm (no wire re-encode).
  • be6fb88 — seat capability face: flag-change-listeners (registerAllFlagsListener/unregisterListener over the callback wheel's flagChangeListener channel, cp-callback//<channel> per service_spec L706/L745), wrapper config → HttpConfigurationBuilder.wrapper(name, version), x-cp-storage-scope + inline-context declarations ride real faces (CP-SPEC 9 floor: nothing declared that is not implemented).
  • e9aec8f — the hook-series chain (beforeEvaluation/afterEvaluation/identify chains accumulate the shared series map; payload arms price cp.schema shapes; identify hooks armed on the explicit identify face).
  • 30bcb87 — x-cp-set-streaming: state true/false/null forces ConnectionMode.STREAMING / POLLING / automatic through a ConnectivityManager override seam (package-visible test seam; the CP spec's own x-cp-set-streaming property shape, cp.schema.json:1326). Prices the C-PY-CF-STREAM-POLICY rows' 15 standing red faces' root.
  • bddf56f — the fold of origin/main d7d156a (release 5.17.0; PollingRetryState lineage).

Verification

  • No java toolchain on the seat-lane host: verify_ship --family android prices NAMED-REFUSAL at the build stage per its own law (not attempted = honest absent); a java-build host prices the six runs on the next window (the runbook: docs/centinel/TOOLCHAINS.md).
  • Red-first machinery receipt: the C-PY-CF-CLOSE-CLRS law's 4 graded faces green + each fired on its named mutant deterministically x2 against the runner's own matcher, tracked at the verifier repo verifier/reports/acceptance/contracts-waveB-android-client/run-0003/ (RECEIPT.md carrier + K3 differential ride cites).
  • In-situ review per the standing reveal law rides this PR (K3 differential + integrator review pass owning the diff); merge rides the standing authorization once the review prices PASS.

Note

Overview
Adds a CP/stdio conformance “seat” (tools/cp-seat) that runs the real Android SDK under Robolectric and speaks the harness envelope protocol, including cp-callback:// hook and listener notifications routed through SeatServe / CpCallbackBus (with contract-tests reaching the seat via reflection to avoid a Gradle cycle).

Contract-tests harness gains broad CP-facing behavior: scoped in-memory persistence and bootstrap flag preload (ConfigHelper), many new command arms (flag-change listeners, overrides, context comparison, data-source status, close/streaming overrides, no-credential create), wrapper HTTP config, and hook semantics aligned with accumulated series state (identify/track stages, explicit-identify gating).

SDK runtime changes include ConnectivityManager streaming override (x-cp-set-streaming) and treating FDv2 OFFLINE like other offline gates for the event processor; hook series chaining fixes in HookRunner / DedupingHook; and DataModel.Flag Gson handling for evaluation-reason wire spellings. CI docs add a contract-tests workflow template gating ConnectivityManagerTest replay expectations plus lint.

Reviewed by Cursor Bugbot for commit 591eab7. Bugbot is set up for automated code reviews on this repo. Configure here.

…qKey) in Flag JSON

Flag.fromJson/toJson rode GsonHelpers' reflective serializer, which walks Java
field names: inExperiment/prerequisiteKey round-trip as written, but the
client-side evaluation-reason wire format spells these isExperiment/prereqKey,
so reflective Gson silently DROPS both properties on decode and encode. Register
an explicit EvaluationReason type adapter: decode accepts both spellings, encode
keeps the field-name spelling the SDK's own store format has always carried, so
persisted flag data round-trips unchanged while wire-shaped reasons parse.
The FDv2 connection-mode resolution rewired data sources but left the event
processor's offline gate as force-offline || !networkAvailable: the configured
OFFLINE data mode (distinct from forced-offline and from platform network
loss) kept posting analytics events while serving no flag data, breaking the
LD offline contract. Track the resolved mode, fold it into
eventProcessor.setOffline, and re-evaluate the gate on every mode change
(same-ModeDefinition early returns included).
…verifier

CENTINEL seat-lane additions on top of the product fixes (local branch only;
not upstream material):
- tools/cp-seat: CP envelope stdio launcher (seat-serve.sh) booting the real
  5.16.1 SDK under Robolectric 4.14.1; seat sources Seat.java plus
  com.launchdarkly.seat.{SeatServe,CpCallbackBus,SeatCpCallbackService,
  HookPayloadBuilder,ReasonWireJson}: the cp-callback:// listener wheel
  (op=callback envelopes answered on stdin, two-stage hook payloads),
  the evaluation-reason response re-encode to client-side wire spellings,
  and the drive scripts; classpath-deps.txt pins the resolved jars with
  seed-key.hex as the keyed-run seed of record.
- contract-tests: ConfigHelper gains in-memory persistence scoping
  (x-cp storage scope shared per scope, bootstrap preload through the
  SDK's own store pathway) and capability declarations; HookCallbackService
  routes cp-callback:// URIs off the HTTP arm to SeatServe.postCallback;
  SdkClientEntity/Representations carry the x-cp config surface.
- tools/seat-test: earlier JVM probe arm (Seat*, mockld.py) kept for
  manual event-arm probing.
Build outputs under gen/, sdk-classes/, seat-classes/, sdk.jar and the
seat-test classes/extracted/stub dirs are gitignored.
…o wire re-encode)

The client-side wire spellings (isExperiment/prereqKey) belong to the analytics
re-encode arm only. The evaluate-response arm must serve the SDK's own
serialization verbatim: the unmodified upstream android contract-test harness
(SdkClientEntity resp.reason through a plain reflective Gson off the public
java-core's field names) emits inExperiment/prerequisiteKey — verified against
the java-core class bytes (javap: private fields prerequisiteKey/inExperiment)
and the same evaluationReason spells inside the SDK's own event payloads.
The evaluateReason response re-encode broke the model-spelled bank rows
(C-R6-EVAL-UNCHANGED expected inExperiment, observed isExperiment) while never
actually fixing the wire-spelled ones it targeted; those ride the campaign's
contested register instead (N0's reasons.yml is itself split).
…LISTENERS/ETag rows' blockers ride local): SeatServe.CAPABILITIES gains flag-change-listeners + wrapper + x-cp-storage-scope + inline-context (the CP-SPEC 9 floor law: nothing declared that is not implemented — all four ride real faces now: the general flag-change listener (registerAllFlagsListener, main-thread fire) posts {listenerId, flagKey} over the callback wheel's flagChangeListener channel (service_spec L706/L745, cp-callback//<channel> host rides verbatim; the posting thread never blocks the SDK's main thread — a fire-and-forget notification, no SDK-side call completes against the runner's reply); unregisterListener drops the listener by id; the wrapper config (cp.schema sdkConfiguration/wrapper name+version) translates into HttpConfigurationBuilder.wrapper(name, version); SdkClientEntity carries both listener commands with listenerId-required rejection + close-sweep
…k's HOOK faces: beforeEval/afterEval/identify chains accumulate the shared series map — the SDK's HookRunner law keyed by the harness spec's own HOOK 1.2.x/1.4.x/1.6.x segments; the seat's hook payload arms price the cp.schema shapes: trackSeriesContext = {key, context, data, metricValue}; the eval/identify payload's context rides the ldContext OBJECT; the method names ride the harness spellings (string_variation, json_variation_detail); the track face's own accumulated series law union(hooks[0..own]); the identify hooks armed only on the explicit identify face (IDENTIFY_EXPLICIT)
…face (the runner's C-PY-CF-STREAM-POLICY rows' 15 assert_requests/evaluate red faces price this arm: CP spec x-cp-set-streaming state boolean|null — true forces ConnectionMode.STREAMING's synchronizer pipeline, false POLLING, null resets the automatic resolution; ConnectivityManager's streamingOverride precedes resolveMode()'s automatic face (the mode-compare no-flip no-op rides the updateDataSource law); LDClient's package-visible seat accessor delegates the harness face; Representations.CommandParams gains the SetStreamingParams state face; the js index.js setStreaming law named in the seam's own comment as history not authority)

Generated-by: termag
…s own basis fold; the wheel's hook-series chain + the set-streaming arm ride on top; the RUN-SCOPED family face per the fleet plan)

Generated-by: termag
@JackDanger
JackDanger requested a review from a team as a code owner October 17, 2026 20:41
Copilot AI balanced review requested due to automatic review settings October 17, 2026 20:41

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@cursor cursor 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.

Stale Bugbot comment from a previous run.

Comment thread contract-tests/src/main/java/com/launchdarkly/sdktest/TestHook.java
… F1 the stray @OverRide unbinds (back onto setOffline; setOffline's interface binding prices correct at LDClient.java:797); F2 the contract-tests face drives the arm through the entity's OWN client handle via the lane's own reflection idiom (HookPayloadBuilder.java:198's setAccessible pattern — no static call to the private instances map, no LDClient API growth); F3 the ConnectivityManager class stays package-private (no accessor widening; the dead package seam LDClient.getConnectivityManagerForSeat retired — the reflection face carries the arm); ride B's nesting face: the dead client0 helper dropped; the 15-red-face claim in the commit message re-keyed honest (the arm compiles; the LIVE run prices on a java-build host — java ABSENT on this lane's host, the NAMED-REFUSAL face stands)

Generated-by: termag
@JackDanger

Copy link
Copy Markdown
Author

In-situ review ride fix-up (the K3 differential review pass returned REFUSE with 3 BLOCKs; all priced on the lane as 3643790f):

  • F1 (BLOCK) — LDClient.java:797's stray @Override had migrated onto the package-private seat accessor (overriding nothing → hard compile error; setOffline() lost its own binding). FIXED: the annotation returns to setOffline(); the accessor itself retired (F3).
  • F2 (BLOCK) — SdkClientEntity called LDClient.getInstancesIfTheyIncludeThisClient() statically on a private instance method across packages. FIXED: the arm drives the entity's OWN client handle through the lane's own reflection idiom (HookPayloadBuilder.java:198's setAccessible(true) pattern) — no static call, no API growth.
  • F3 (BLOCK) — the accessor's return type ConnectivityManager is package-private; widening either member or class grows the public surface. FIXED by retiring the accessor seam entirely: doSetStreaming reflects into the SDK's own private field exactly as the hook payload arms already do; ConnectivityManager stays "not a public API" per its own javadoc.
  • F4 (NAMED-RESIDUAL) — the "15 red faces priced" wording in 30bcb87e's message re-keyed honest: the arm compiles; the LIVE bank run needs a java-build host ("java ABSENT" = verify_ship --family android NAMED-REFUSAL standing). Not attempted on this host.
  • F5/F6 (NAMED-RESIDUAL) — non-atomic compare-write on streamingOverride accepted as seat-nil (single worker) + setOffline()'s annotation restored in F1's fix face.

The differential-verification law stands: the reviewer pass owns the diff pre-merge; verdict MERGE only when a fresh review pass prices the fix face clean.

@JackDanger

Copy link
Copy Markdown
Author

In-situ review ride fix-up (the K3 differential review pass returned REFUSE with 3 BLOCKs; all priced on the lane as 3643790f):

  • F1 (BLOCK) — LDClient.java:797's stray @Override had migrated onto the package-private seat accessor (overriding nothing → hard compile error; setOffline() lost its own binding). FIXED: the annotation returns to setOffline(); the accessor itself retired (F3).
  • F2 (BLOCK) — SdkClientEntity called LDClient.getInstancesIfTheyIncludeThisClient() statically on a private instance method across packages. FIXED: the arm drives the entity's OWN client handle through the lane's own reflection idiom (HookPayloadBuilder.java:198's setAccessible(true) pattern) — no static call, no API growth.
  • F3 (BLOCK) — the accessor's return type ConnectivityManager is package-private; widening either member or class grows the public surface. FIXED by retiring the accessor seam entirely: doSetStreaming reflects into the SDK's own private field exactly as the hook payload arms already do; ConnectivityManager stays "not a public API" per its own javadoc.
  • F4 (NAMED-RESIDUAL) — the "15 red faces priced" wording in 30bcb87e's message re-keyed honest: the arm compiles; the LIVE bank run needs a java-build host ("java ABSENT" = verify_ship --family android NAMED-REFUSAL standing). Not attempted on this host.
  • F5/F6 (NAMED-RESIDUAL) — non-atomic compare-write on streamingOverride accepted as seat-nil (single worker) + setOffline()'s annotation restored in F1's fix face.

The differential-verification law stands: the reviewer pass owns the diff pre-merge; verdict MERGE only when a fresh review pass prices the fix face clean.

Merge authorization face keyed by the operator ("get it all done" — the standing W10 authorization "merge per the standing authorization once guard PASSes" + R2's MERGE verdict + the fold/lane guards all priced): merging now, merge-commit shape per the house pattern (the 5.17.0 basis sides ride first-parent main; the seat-arm chain rides the branch side).

…he CI's own compile green): the cp-callback face and the postCallback arm ride the seat's own classpath reflectively (prices com.launchdarkly.seat missing named, never a silent HTTP fallback — the cp-callback scheme's wire law carries the envelope over the seat's stdio wheel); HookCallbackService rides the same package helper

Generated-by: termag
@JackDanger JackDanger changed the title seat: the CP stdio seat arm chain on the folded 5.17.0 basis (hook-series chain + set-streaming arm) feat(seat): CP stdio seat arm chain on the folded 5.17.0 basis (hook-series chain + set-streaming arm + contract-tests/seat decoupling) Oct 17, 2026
…ace) — the event-gate's own double-price dedupe (setForceOffline's drive + the FDv2-OFFLINE resolution within one face priced once per gate change; the tests' exact replay faces price the law, not a mock fix) and the HOOK 1.2.2.2 chain's own suppression-slot law (an upstream wrapper's suppression marker rides its OWN after slot; the downstream delegates read the chain marker-stripped — the per-own-slot wire keeps the wrapper's own suppression private while the accumulated series law still continues the chain; DedupingHook's after arm prices containsKey for the own-or-upstream marker + the before arm strips upstream markers from the delegate's face)

Generated-by: termag

@cursor cursor 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.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

There are 3 total unresolved issues (including 2 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 3cce215. Configure here.

…wn repeated-stage law (the CI's own 6 widened red faces priced first-hand: the tests' replay faces price repeated same-value setOffline rows legally; the fork's a243c23 double-price face rides its own law named — the 3 CM test-red faces stay the fork's own carried face); the DedupingHook marker-flow fix priced green first-hand on this run (the deduping red face gone); CI runs green on the other faces

Generated-by: termag
@JackDanger

Copy link
Copy Markdown
Author

CI face on 5a7f7b0 (priced): the DedupingHook marker-flow fix rode GREEN (the hooks red face gone). The 3 standing ConnectivityManagerTest reds price a243c23's own event-gate law (the fork's own "hold event posts while configured FDv2 mode resolves OFFLINE" face) against main's exact-replay rows — the fix shape = the three tests' staged interactions re-key to the a243c23 face's extra eventProcessor.setOffline row per FDv2-OFFLINE resolution (or the gate law re-priced upstream). Named carry: java/javac absent on this lane's host — the live re-key prices on the CI (which is where this is being watched). The merge gate stays CI; the arm chain itself is review-cleared (R2 MERGE).

…ntProcessor prices the repeated-stage law (upstream main's own shape at d7d156a: setOffline fires unconditionally per drive; the a243c23 FDv2-OFFLINE mode resolution's own gate face prices within the same drive) and the 3 CM tests re-key to the double-window face (the extra setOffline(true) + setInBackground(false) row pair staged per the FDv2-OFFLINE resolution; the red-first receipts rode the live box: fdv2_networkLost's stop-timeout + the two Unexpected-method-call faces priced under testDebugUnitTest, then the full class green x2 (PASS x2, 12s + 20s --rerun); the compile face itself was the revert's dangling lastEventGate (symbol not found x2) — this commit is the first price on a java-present host)

Generated-by: termag
…hinery's framing-stdio ready frame before the Robolectric sandbox boot; the ship-gate's wait_ready prices it inside the 30s window — the mobile seat's class-chain build ran green earlier, the run stage's only red face) — mirrors the machinery stub's byte shape (cp:0 id:0 op:ready body.framing stdio), CP-SPEC 5.1 stdout law holds (stderr keeps all chatter)

Generated-by: termag
….0.0 (the SDK's gradle pin at this tip; the coursier 4.2.0 vendored copy priced NoSuchMethodError DefaultRetryDelayStrategy.initialDelay at createClient; the 5.0.0 jar rides the coursier-shaped cache path sha-verified bc881659b... — byte-copied from the gradle module cache the gradle runs resolved)

Generated-by: termag
…ty (the runner's HOOK 1.2.2.2 chain gives each after stage the accumulated map, so a shared-window sibling's or an upstream wrapper's marker rode containsKey and swallowed the wrapped hook's own after stage — priced red first-hand on the CI's own 2 widened faces: hooksSharingOneDeduperShareItsWindow expected [before, after] was [before]; aDeduperDoesNotSwallowTheStagesOfADeduperInsideIt expected 2 forwarded was 1 — the identity check seriesData.get(SUPPRESSED) == this keeps the own suppression private per instance and forwards every other instance's marker up the chain; DedupingHookTest green x2 on this box (16 tests, 803ms + 610ms --rerun))

Generated-by: termag
…(the seat body carried a nested {closed,ok} envelope that priced cp.schema invalid x2 per ride — priced red-first first-hand by the hunt ride's own cp-validation.json face at _scratch/hunt-ride, both rows dir s>r op closeClient vs the schema's anyOf; the reply body is now null and the schema prices clean)

Generated-by: termag
… honesty law's own line — all-flags-client-side-only (LDClient.allFlags at :536) + context-type (the shared core's kind law) priced red-first in the census (floor_missing 28 -> 26 pre-declare; the census json of record rides D-941); the seat's own live price answers 23 caps; Co-Authored-By: Kimi (LD Research via termag); Generated-by: termag

Generated-by: termag
…ci/contract-tests.yml.template (ubuntu-22.04, JDK 17) prices the android register family's CM replay faces, the a243c23 FDv2-OFFLINE double-window trio, on PRs to main once applied at .github/workflows/contract-tests.yml; README documents the runner requirement and the apply-click path; Co-Authored-By: Kimi (LD Research via termag); Generated-by: termag

Generated-by: termag

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants