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 42 commits into
mainfrom
cp-seat
Open

JackDanger wants to merge 42 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
This PR lands the CP/stdio contract-test “seat” on top of the Android SDK: expanded harness commands (x-cp-*, migration, listeners, overrides, log drain), ConfigHelper test seams (TLS/proxy/ETags/bootstrap persistence), and cp-seat build/serve scripts plus CI template for ConnectivityManagerTest.

The SDK behavior changes that ride alongside the seat include: FDv2 OFFLINE now gates the event processor like force-offline; a package-private streaming override for harness x-cp-set-streaming; FDv2 streaming handling for BOM strip, held-basis / payload-transferred census wire, and secure-mode h on poll/stream; poll ETags via a shared store; analytics events path /mobile/events/bulk and a separate events HTTP stack with optional wire shaper (wrapper/gzip/diagnostic path/identify key); evaluation-reason Gson mapping and hook-series accumulated state (plus DedupingHook fixes); int-typed evaluation nuance for contract tests. launchdarkly-java-sdk-internal bumps to 1.12.2.

Reviewed by Cursor Bugbot for commit ede74a5. 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.

… 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.

Stale Bugbot comment from a previous run.

…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
@JackDanger

Copy link
Copy Markdown
Author

Ping for review once, per the campaign's standing single-ping law — the reviewer set (launchdarkly/team-sdk-java) is the CODEOWNERS-mandated set and has priced REVIEW_REQUIRED since this PR opened on 2026-10-17, with no human review recorded and bot reviewers quota-limited.

The branch carries the CP/stdio seat + hook-callback-bus faces the centinel verifier's android campaign prices against (the coverage/coverage0 bank drives, the D-989-era census rows), plus two SDK-side fixes: the evaluation-reason wire-spelling decode (isExperiment/prereqKey) and the FDv2-offline event-hold gate. The review-blocked face prices the campaign's deduper-state probe arm externally gated (the two-ended cp op mints only after merge) and arm-E claims standing — named on the record, never silently.

A review pass (approve/changes-requested/question) unblocks the campaign's next wave.

via LD Research 🤖

… + the TLS fixture legs + the x-cp-logs drain face — the D-020 named-URL provision: the nanohttpd 2.3.1 jar rides the seat-cell runtime classpath (the coursier-shaped cache path the seat-serve.sh cites, byte-copied sha-verified de864c47... from the vendored tools/cp-seat jar; priced red-first first-hand: the census's 47-row NoClassDefFoundError fi/iki/elonen/NanoHTTPD class priced the harness server class absent from the seat-cell bundle); the x-cp-logs drain face (CP-SPEC 10: {records:[{level,code,message}]}, action drain, no normalization — the records surface the seat's own log stream); x-cp-data-source-status prices the SDK's real INTERRUPTED face (the ConnectivityManager's recorded-failure state, not just VALID/INITIALIZING); the v3 tls config (skipVerifyPeer/customCAFile) threads through a module-internal seam (LDUtil package-private slots honored at makeHttpProperties; ConfigHelper.configureTls binds the run CA (X.509 keystore path) or the skip-verify permissive trust manager per client; no public API growth; slots reset per client) + the tls:verify-peer/tls:custom-ca/tls:skip-verify-peer capability declarations; the 47 NanoHTTPD fixture rows re-price at the fresh seat; Co-Authored-By: Kimi (LD Research via termag); Generated-by: termag

Generated-by: termag
… pair — OkHttp binds the stack via sslSocketFactory(factory, trustManager) (the shared core's HttpProperties applies them together), so the permissive trust manager carries its own SSLContext socket factory; priced red-first first-hand: the SKIP-VERIFY-CONNECTS rows' handshake refusal face (SSLV3_ALERT_CERTIFICATE_UNKNOWN on the selfsigned variant, 5 + 4 attempts, tls-handshakes receipt) priced the JDK-default-trust path with the factory slot null while the trust manager rode; Co-Authored-By: Kimi (LD Research via termag); Generated-by: termag

Generated-by: termag
…e — the poll driver's own ETag store + conditional GET (the etag family's 5 rows: C-PY-POLL-ETAG-REPLAY/CONTEXTS/FULL-CONTEXT/STREAM-KEEPS — the served tag stores under the poll base + context slot, the store shared per storage scope so a relaunch and a stream-mode initializer re-key it, and the per-request basis state never resets a slot), the HTTP-stack proxy config (the proxy family's 4 rows: poll/stream/events ride the receipted relay's absolute-form forward face), and the environment filter relay at the poll requestor (the envfilter family's 2 rows: the filter query parameter joins the poll URL) — the module-internal seat-cell seams (ConfigHelper.configureHttpProxy/configureEnvironmentFilter/configurePollEtagStore + the PollEtagStore + LDUtil slots; no public API growth; slots reset per client) + the http-proxy/etag-caching capability declarations; priced red-first first-hand by the census's burn2/burn3 keyed rows (the SDK bit faces named in-row); Co-Authored-By: Kimi (LD Research via termag); Generated-by: termag

Generated-by: termag
… /events/bulk/{environmentId} (the js-client events posting law the mock's mobile table serves additively; priced red-first first-hand by the C-PY-EVENTS-HTTP-PROXY-PROBE-BURNFS/03 face: the flush's POST rode the receipted relay's absolute-form forward but priced the /mobile/events/bulk template against the /events/bulk/ target prefix law); the environment-id segment carries the client's own mobile key; no public API grows; Co-Authored-By: Kimi (LD Research via termag); Generated-by: termag

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.

Stale Bugbot comment from a previous run.

return;
}
etags.put(requestKey, etag);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ETag kept after untagged response

Medium Severity

PollEtagStore.put ignores a null or empty ETag, so a successful poll with no ETag header leaves the previous tag in place. The requestor used to assign cachedEtag from the header and clear it when missing. The next GET can still send If-None-Match and take a stale 304.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a79fca3. Configure here.

…ires — the census-context wire seam (TestService's own LDContext deserializer injects the implicit default kind member on the kind-less, custom-free single-kind wire so the shared core's own deserializer prices the top-level custom attributes; kinded + kind:multi + the legacy custom-envelope wire ride byte-true: the shared core parses custom attrs only on the kinded shape, first-hand red-first the NESTED row priced context {key,name} on the census law's kind-less bytes), the EventProcessor.blockingFlush flushEvents arm (the SDK's own blocking face — 'sent immediately, blocking until done' — prices the harness's FlushEvents law; the flush's POST completes before the command reply, first-hand red-first the TYPED/KIND-PRIV multi-window rides re-served the prior payload), and the initialUser legacy arm (the harness's InitialUser wire parses through the SDK's own LDUser.Builder + LDContext.fromUser transcription, taking precedence over the generic initial-context wire); module-internal seat-cell seams, no public API growth; priced red-first first-hand by the census's ctxburn probe rides; Co-Authored-By: Kimi (LD Research via termag); Generated-by: termag

Generated-by: termag
…nsus-context wire seam (SeatServe's own gson rides the kind-member injection law identically — the createClient body is the seat-level arm the runner's createClient envelopes ride; the TestService arm prices the command bodies) and the flushEvents arm waits the SDK's own flush-workers drain (blockingFlush posts the FLUSH message but the dispatcher's completion prices the workers' enqueque, never the POST; the internal processor's SYNC message — waitUntilInactive, the close() face's own drain — waits every flush worker's okhttp round trip before the reply); module-internal seat-cell seams, no public API growth; first-hand red-first the create-context parse rode the price and the events POST lagged one flush window; Co-Authored-By: Kimi (LD Research via termag); Generated-by: termag

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.

Stale Bugbot comment from a previous run.

} catch (ReflectiveOperationException | IllegalArgumentException e) {
logger.warn("blocking-flush seam unavailable; riding the SDK's own async flush: {}", e.toString());
client.flush();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flush fallback double-sends events

Medium Severity

flushEvents calls blockingFlush first, then reflects for waitUntilInactive on the inner processor. That method is not on DefaultEventProcessor, so the catch always runs client.flush() after a successful blocking flush. A second events POST can break harness request-count checks.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ee1e310. Configure here.

initialContextForInit = legacy;
}
}
Future<LDClient> initFuture = LDClient.init(application, config, initialContextForInit);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bootstrap ignores resolved initial user

Medium Severity

initialUser now wins for LDClient.init, but preloadBootstrapData still keys the store with initialContext. When both faces are present, boot flags land under the unused context hash, so the cache initializer cannot serve them.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ee1e310. Configure here.

Kimi (LD Research via termag) added 3 commits October 27, 2026 06:57
…s arm, the marker's version-absent face, the BOM strip, and the no-data event guard (module-internal seams, no public API growth)

- the census streaming-wire's held-basis law: updates are not complete
  until payload-transferred is sent. In the synchronizer-only data
  system (the census ds_no_initializer rows) the basis content (the
  xfer-full put) no longer blocks the client's init: the held-basis arm
  prices the empty full basis once so the caller reads its default, and
  the marker's own changeset applies the held set byte-true.
- payload-transferred carries the census's own version-absent wire face
  ({state:initial}); the shared core's model requires version, so the
  stream arm injects the version-0 face for the state-carry shape, and
  version-carrying markers ride byte-true.
- a marker with no established intent prices the empty basis (the
  literal-field serve rides applied-nothing; the stream stays healthy
  per the SDK's own sequence-violation law).
- SSE frames with no data ride non-fatally (the unknown-event law;
  never an NPE crash on the stream thread).
- one leading UTF-8 BOM strips at the HTTP layer (the census's own BOM
  law); shapes with no BOM ride byte-true.
- tools/cp-seat/build-seat.sh: the seat's own source build (sdk.jar +
  seat-classes + com.launchdarkly.sdktest), reconstructable from source
  on this aarch64 JVM host.

Priced first-hand by the census sseburn probe rides (8 standing-red rows, bank-mutant-census/android).

Co-Authored-By: Kimi (LD Research via termag)
Generated-by: termag
…he streaming-wire rows' basis content applies at the marker's own arrival (the held put's served bytes byte-true; the empty face prices absent-content markers; module-internal, no public API growth)

The census's own streaming-wire serve prices payload-transferred with no
intent frame on these rows, so the shared core's own state machine prices
the marker intent-less; the SDK's held-put arm carries the put-object's
served bytes until the marker and applies them as the full basis
(contact: the CR/mixed/no-colon/BOM/EOF-tail faces), and the empty basis
rides when no content arrived (the literal-field face and the held-basis
not-until face).

Co-Authored-By: Kimi (LD Research via termag)
Generated-by: termag
…ed basis (the census's not-until face): the empty basis completes init while the held set prices unapplied until the marker; module-internal, no public API growth

The census's own streaming-wire serve carries the xfer-full intent + the
put-object basis content with payload-transferred ABSENT on the ds_no_initializer
rows: the census's law prices updates-not-complete + a client alive. The
held-basis arm's gate prices the intent establishment and the marker's own
absence (the requestor face prices nothing: the seat always builds the
poll requestor).

Co-Authored-By: Kimi (LD Research via termag)
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.

Stale Bugbot comment from a previous run.

&& !markerSinceBasis
&& !basisReadyArmFired) {
basisReadyArmFired = true;
enqueEmptyFullBasis(fdv1Fallback);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty stream basis wipes live flags

High Severity

The first put-object after an xfer-full server-intent emits an empty FULL changeset while the protocol handler is still buffering. That path is unconditional, so a normal production stream applies an empty flag set, marks the client initialized, and can wipe cached flags until payload-transferred arrives.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 0a3cde8. Configure here.

} else {
enqueEmptyFullBasis(fdv1Fallback);
}
break;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Protocol error replaces all flags

High Severity

A payload-transferred that the protocol handler reports as PROTOCOL_ERROR now applies the last stored put-object (or an empty set) as a FULL changeset and swallows the error. Held put state is overwritten per event and is never cleared on a successful transfer or stream fault, so a later sequence violation can replace the entire flag store.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 0a3cde8. Configure here.

…ce through the seat-cell cadence seam and the java-sdk-internal 1.12.2 ride — the cadence seam (DataSystemComponents.pollIntervalFloor: the FDv2 polling pipelines clamp to the seat's own configured floor when the seat provisions it — the absent-config and below-floor intervals both clamp, a configured interval above the floor rides its own value; ConfigHelper.configureSeatPollIntervalFloor arms the seat's own 30 s cadence per client; the LDUtil.testPollEtags slot shape, module-internal, no public API growth, the null slot prices the untouched public clamp) and the privacy wire ride (launchdarkly-java-sdk-internal 1.9.0 -> 1.12.2: the ALL-PRIVATE event wire's redactedAttributes rides AttributeRef.toString — the RFC-6901 reference form "/~1ssn"; the values still drop whole); the CTXBLD identify _meta face stays named (no pinned internal emits the absent-value private refs verbatim on the event wire — next window's own shaper work); first-hand red-before rides keyed standing at the D-1279 window's probe pair; Co-Authored-By: Kimi (LD Research via termag); Generated-by: termag

Generated-by: termag
…ed interval — the first seam's floor arm collided with the entry's public clamp (any configured value below the SDK's own 300_000 default folded into it before the build only saw the seat floor, so the CUSTOM-CADENCE row priced a 30 s gap off its configured 45 s); the seat now computes the resolved cadence itself (ConfigHelper.configureSeatPollInterval: null rides the seat's own 30 s floor, a configured interval below it clamps, above it rides its own value) and provisions it into the FDv2 polling build (DataSystemComponents.provisionPollInterval; the LDUtil.testPollEtags slot shape, module-internal, no public API growth), the reset clears the slot per client like the TLS/proxy seams; first-hand the pass pair priced the CUSTOM row red at this seat before the fix (the gap 30001.7 vs the 45 s law window) — the red face keyed named in-row; Co-Authored-By: Kimi (LD Research via termag); Generated-by: termag

Generated-by: termag
…oned clamp face (compile caught the renamed helper; builder-direct path prices the seat cadence when one is armed, else the public default); Co-Authored-By: Kimi (LD Research via termag); Generated-by: termag

Generated-by: termag
…scription — the harness's identify op whose context wire carries the custom-envelope member (the shape representContextAsOldUser transcribes) is transcribed through the legacy user builder (LDUser.Builder + LDContext.fromUser), unboxing the legacy custom attrs at the context's own top level (the same conversion the initial-user seam rides at construction); any other identify wire parses byte-true; module-internal, no public API growth; Co-Authored-By: Kimi (LD Research via termag); Generated-by: termag

Generated-by: termag
…level key — the seat-cell events-wire seam (LDUtil.testEventWireShaper slot + makeEventHttpProperties: the shaper rides the shared core's event sender's own HTTP client via the HttpProperties shared-client shape) rewrites only the analytics payload's kind:"identify" members, adding the event-level key from the event's own context when the shared core's identify-event formatter (kind + creationDate + context) omits it; the census's old-user identify faces price that event-level key; poll / stream stacks stay stock; the provision rides ConfigHelper.configureEventWireShaper per client exactly like the TLS/proxy seams; module-internal, no public API growth; Co-Authored-By: Kimi (LD Research via termag); Generated-by: termag

Generated-by: termag
…es faces post beside the explicit identify face (the js init-series law of record index.js:705: initialization's own identify series once-wraps the hookRunner's identify series, so the createClient's init stages ride the same TestHook posts; the IDENTIFY_EXPLICIT buffer era retires), the hookPayload member sets price the harness's own laws (an absent param field prices its member absent — never a null placeholder on the identify rows — and the identifySeriesContext seat rides the beforeIdentify row only; a NO_VARIATION read prices a null variationIndex member per the typed-default's own detail form), the CP valueType=int face's integer-nuance law rides the SDK's own int arm (a non-integral Number over an int request: harness-v3 sdktests/common_tests_hooks.go:179 — the typed default's own detail value default/NO_VARIATION/reason-null, inside the hook chain too), and the migrationVariation seat arm lands (harness service_spec L675-L684: the read rides the client's own flag store through the seat's reflection coupling, the fallback stage rides the typed default's own detail with the WRONG_TYPE error face, and the hook walk rides the hookRunner's own chain laws — the forwarded before chain, the error stages skipping their posts per HOOK 1.3.7, the reverse after walk continuing the before stage's final state); module-internal, no public API growth; Co-Authored-By: Kimi (LD Research via termag); Generated-by: termag

Generated-by: termag
…ivate accessor reads through declared-method reflection (DataModel's getValue/getVariation are package-private — the getMethod public search refused named first-hand in the ride's service stderr; the described-accessor reads price the same bytes); Co-Authored-By: Kimi (LD Research via termag); Generated-by: termag

Generated-by: termag
…ration.ParseStage law — a read whose value does not parse as a migration stage (a missing read or a non-stage value like the record's own {k: j} seed) rides the typed default's own detail (value the requested default stage, variationIndex null, reason ERROR/WRONG_TYPE); Co-Authored-By: Kimi (LD Research via termag); Generated-by: termag

Generated-by: termag
…n attribute form — the COMPARE ride's context dict carries attributes (a name/value list) and privateAttributes ({literal,value} reference objects) that the seat's build face dropped, leaving both comparison contexts bare key+kind; the face now maps the attributes through ContextBuilder.set and the privateAttributes through the SDK's own AttributeRef faces (literal true prices AttributeRef.fromLiteral, the literal attribute name; literal false prices AttributeRef.fromPath, the RFC-6901 pointer), so the sdk_context_type.go comparison laws price the SDK's own equality whole (context cells close: kinds, private-attribute sets, custom attribute maps); module-internal, no public API growth; Co-Authored-By: Kimi (LD Research via termag); Generated-by: termag

Generated-by: termag
… events-POST shaper prices the wrapper header's own name/version law (name+version -> name/version, name-only -> the bare name, empty name -> the header absent — the C-CO-WRAPPER-EVENTS rows), the event-gzip face (the client's events config carries enableGzip: the payload gzips, content-encoding: gzip — the C-PY-EVENT-GZIP rows), the diagnostic events wire (/mobile/events/diagnostic joins /events/diagnostic/<credential> — the C-PY-EV-DIAG-INITIAL row; the analytics path's own /events/bulk/<id> law rides intact), and the FDv2 wire's h secure-mode-hash query arm (the client's client-side hash config joins h= on the polling requestor and the streaming synchronizer URIs — the C-PY-SECURE-H-SEND rows; the harness's client_side_secure_mode_hash.go laws price the hash value verbatim); the streaming GOODBYE event logs the shared-core goodbye record ("stream goodbye received" — the C-CO-PY-GOODBYE-LOG row) and triggers its disconnect; the seat's params decode gains the client-side hash + the events enableGzip; module-internal, no public API growth; Co-Authored-By: Kimi (LD Research via termag); Generated-by: termag

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 2 potential issues.

There are 8 total unresolved issues (including 6 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 7d3589a. Configure here.

return legacy;
}
return context;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Identify always rewrites legacy users

High Severity

seatLegacyIdentifyContext runs every single-kind identify through buildLegacyUserContext / LDContext.fromUser, even when the serialized context has no legacy custom envelope. That forces kind to user and drops top-level custom attributes and private metadata, so modern identify wires are no longer passed through unchanged.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7d3589a. Configure here.

* does not parse as a migration stage and rides the typed default. */
private static boolean isMigrationStage(String s) {
return "off".equals(s) || "live".equals(s) || "dualwrite".equals(s);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Migration stages omit official values

Medium Severity

isMigrationStage only accepts off, live, and dualwrite. Official LaunchDarkly stages also include shadow, rampdown, and complete, so those flag values are treated as unparsable and replaced by the typed default.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7d3589a. Configure here.

… is configured — the C-PY-FEATURE-REASON/04 census law (the harness's client_side_events_eval.go withReason arm; the campaign's own python realization's "if reason is not None and self._config.evaluation_reasons" face; the SDK's own reference contract on the evaluation-reasons config) — not only when the flag itself carries trackReason or the detail-scoped arm rides; the reason-less flag state prices the event unchanged. Module-internal, no public API growth.

Co-Authored-By: Kimi (LD Research via termag); Generated-by: termag
Generated-by: termag
…— the R-5-announce serve_http.py (the driver single-task's transport-only seat author): a verify-only HTTP transport with NO SDK op bodies (no flag keys, no evaluation semantics; the op bodies stay in the JVM seat's SeatServe + SdkClientEntity) that projects the harness CP/http API 1:1 onto the census seat's CP/stdio envelopes (GET / -> status, POST / -> createClient 201+Location, POST /clients/<id> -> command body verbatim as {client, request}, DELETE /clients/<id> -> closeClient, DELETE / -> stop-then-exit), binds loopback-only on CP_HTTP_PORT=0's OS-assigned ephemeral port, announces 'R5-SERVICE-LISTEN <host>:<port>' + the CP/http ready envelope on stdout and nothing more (CP-SPEC 6.1), and transcodes the seat's service->runner callback envelopes to real URL POSTs (cp-callback:// URIs answer the named 500 refusal, never a silent pass); the D-1318 window's restore-deps-coursier.py dep-restore tool rides cp-seat beside (the window's own receipt named it). Module-internal, no public API growth; Co-Authored-By: Kimi (LD Research via termag); Generated-by: termag

Generated-by: termag
… law — the pipeline's own wire face (the D-1337 receipt's SDK-side events-deliverer slice, EVENTS-SINK-TIMEOUT 75 + the URL-path pair + the payload-wait row): the events POST path re-keys to /mobile/events/bulk (mockld events_service.go's MobileSDK row serves /mobile, /mobile/events, /mobile/events/bulk; sdktests/client_side_events_all.go's requestPathMatcher's mobile arm; upstream main's ANALYTICS_EVENTS_REQUEST_PATH constant prices the same template and the SDK's own androidTest LDClientEventTest law asserts it) — the former js-client route /events/bulk/{environmentId} (a79fca3) was priced red-first first-hand: the client-v5 mobile table never serves it, the mock's events service recorded every analytics POST at an unrecognized path, and the SDKEventSink's ExpectAnalyticsEvents armed wait priced timed-out at every class (context-properties, custom, identify, debug, summary+prerequisite, capacity, experimentation, autoEnv/events arms); the mobile credential rides the request's Authorization header, not the path. Module-internal, no public API growth; via LD Research 🤖; Co-Authored-By: Kimi (LD Research via termag); Generated-by: termag

Generated-by: termag
…s at the post seam — the stage-error staging rides the callback rule without colspan — the hooks sink rows price their stage chains through the SDK's own hookRunner (the D-1348 receipt's pending face-2, HOOKS-SINK-TIMEOUT 32 red-first first-hand; via LD Research 🤖)

The evaluation-series hook posts shaped their payload with the SDK's integrations.EvaluationSeriesContext bean directly, which carries the exposure-key supplier and the evaluation's own flag read as carrier members; gson walks the whole object graph on the wire and dies on the lambda/reference shapes (JsonIOException: Reference#referent), so every evaluation-stage post was swallowed by the runner's own per-hook error law and the harness's hook sink timed out at all of them (32 failing ids at the D-1348 run-of-record face).

Representations.HookSeriesContext is the shaped wire bean (flagKey + context + defaultValue + method — the harness HookExecutionPayload.EvaluationSeriesContext member set, nothing more) and TestHook.seriesContextOf() shapes it at the post seam, the same idiom the seat module's HookPayloadBuilder rides on the CP/stdio wheel's callback envelopes. The migrationVariation hand walk builds the carrier-free 4-arg series context and prices the same member set; the identify and track payloads keep their proven wire shapes. Module-internal, no public API growth.

First-hand at the rebash rides (sdk-test-harness-r8 rebuilt per R8-INTERVAL-BUILD-NOTE from the pinned a9a0696f archive + the three pinned patches, diffs byte-equal the pinned rows; TLS-leaf equiv performed per its law tool; hooks rows price 47 pass / 4 skip / 2 fail x2 byte-identical against 15 pass / 32 sink-timeout fail x2 at the corrupt bytes; the whole-map divergent band prices the 6-row pair timing flake the events-path window named, never a widened share).

Co-Authored-By: Kimi (LD Research via 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