Repository navigation
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
JackDanger wants to merge 60 commits into
Conversation
…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
… 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
|
In-situ review ride fix-up (the K3 differential review pass returned REFUSE with 3 BLOCKs; all priced on the lane as
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. |
|
In-situ review ride fix-up (the K3 differential review pass returned REFUSE with 3 BLOCKs; all priced on the lane as
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
…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
…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
|
CI face on 5a7f7b0 (priced): the DedupingHook marker-flow fix rode GREEN (the hooks red face gone). The 3 standing |
…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
|
Ping for review once, per the campaign's standing single-ping law — the reviewer set ( 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 ( 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
… 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
…ntext deserializer byte-true — the census identify seam's legacy-user arm only takes the custom-envelope wire (the D-1359-rekey2 receipt's pair-divergence class's content face: a v5 context wire never carries a top-level "custom" member, so the context-properties debug/identify arms + the autoEnv no-collisions kinds ride the client's evaluation/identify ops attr-whole; the old-user conversion law keeps its own discriminator byte-true) — and the environment reporter's platform values defer per member (the auto-env tags law's application-version-name pair rides the next chain tier's SDK constants when the platform build carries no versionName, the member the headers battery priced missing first-hand; the whole-chain defer had dropped it) — plus the seat launcher's own nanohttpd jar back on the runtime classpath (the coursier-cache restore never re-materializes the bridge's explicit path; the census dispatch priced NoClassDefFoundError named first-hand). Module-internal, no public API growth; via LD Research 🤖; Co-Authored-By: Kimi (LD Research via termag); Generated-by: termag Generated-by: termag
| ? info.getApplicationName() : fallback.getApplicationName()); | ||
| merged.applicationVersionName(info.getApplicationVersionName() != null | ||
| ? info.getApplicationVersionName() : fallback.getApplicationVersionName()); | ||
| info = merged.createApplicationInfo(); |
There was a problem hiding this comment.
Missing app fields inherit SDK identity
Medium Severity
super.getApplicationInfo() walks to SDKEnvironmentReporter, not customer config. When a platform field is absent or rejected by ApplicationInfoBuilder validation, the merge fills it with android-client-sdk and the SDK version. Those values then ride X-LaunchDarkly-Tags and auto-env ld_application attributes, so a real app is identified as the SDK whenever versionName is null or the launcher label contains characters outside the tag charset.
Reviewed by Cursor Bugbot for commit a707103. Configure here.
…es relay (CP-SPEC 8.2 E-6) — the SDK's own URL parser prices okhttp HttpUrl.get(URI)==null raw for the cp-pipe scheme, so createClient timed out at every streaming_secondary scenario (the REKEY3 ride's own 500 face): the seat rewrites every cp-pipe://<svc>[/path] endpoint to the reserved loopback literal http://127.66.0.N:80 (no DNS, no leak) and arms the SDK's module-internal socketFactory slot (LDUtil.testPipeSocketFactory via ConfigHelper.configureSeatPipeTransport, resetting per client) with a javax SocketFactory whose sockets dial and relay their bytes over the CP/stdio channel byte-transparently; module-internal, no public API growth; via LD Research 🤖; Co-Authored-By: Kimi (LD Research via termag); Generated-by: termag Generated-by: termag
…M 1.2.6 recoverable-fallback advance (module-internal, no public API growth) The census recoverable-fallback rows (C-CO-DS-SEC-RECOVER/04, C-PY-DS-RECOVERY-NEGATIVE/03) priced deterministic fail x2 at the fresh seat ride: the SDK's FallbackCondition armed only on an INTERRUPTED status, so a first synchronizer whose stream connects but never reports (a silent/hanging connection) could never arm the fallback and the synchronizer chain stalled (count=1 vs the census's count=2). The public harness law prices the advance after the init timeout (~10 seconds; DATASYSTM 1.2.6 "recoverable error triggers fallback to next synchronizer"; the recovery timeout itself is the CSFDV2 8.2.1 300 s law and rides UNMOVED). - FDv2DataSourceConditions.FallbackCondition: armFirstUpdateFallback() arms a 10 s first-update fallback deadline (FIRST_UPDATE_FALLBACK_SECONDS); a CHANGE_SET cancels it; an INTERRUPTED still arms the error-driven clock only when no fallback timer is live. - FDv2DataSource.Conditions.armFirstUpdateFallback() routes the arm; runSynchronizers arms it when each synchronizer starts, before racing its next() result against the conditions. - The synchronizer stays in-list (recoverable); single-synchronizer clients still create no conditions (the census state machine is unchanged there). via LD Research 🤖; Co-Authored-By: Kimi (LD Research via termag) Generated-by: termag
| if (conn != null) { | ||
| sockets.remove(conn); | ||
| } | ||
| } |
There was a problem hiding this comment.
Socket close skips runner EOF
High Severity
PipeSocket.close() sets closed before calling sendEof(), and sendEof() returns immediately when closed is already true, so the runner never receives the CP bytes half-close. OkHttp closes sockets this way, so pipe connections stay open on the runner after the SDK finishes or reconnects.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit e22bb2f. Configure here.
| int id = ids.incrementAndGet(); | ||
| CompletableFuture<Boolean> f = new CompletableFuture<>(); | ||
| pending.put(id, f); | ||
| bus.dial(id, conn, "cp-pipe://" + svc, svc); |
There was a problem hiding this comment.
Dial and callback IDs collide
High Severity
SeatPipeTransport allocates dial envelope ids from its own counter, independent of CpCallbackBus. Both start at 1, so an in-flight identify/eval hook callback and a pipe dial can share the same id. CP replies are correlated by id, so the runner can complete the wrong pending op during client init.
Reviewed by Cursor Bugbot for commit e22bb2f. Configure here.
…T-META-EVAL/01 + the PREREQ-CYCLE pair (module-internal, no public API growth)
The BOOT TRIO's census faces priced the SDK's own faces first-hand at the instrumented focused ride (the D-1407 refusal's own owner-or-fix rows):
- C-PY-BOOT-META-EVAL/01 ($exact: $flagsState/$valid metadata): the SDK's own evaluateAll face rode client.allFlags() (values only; the SDK prices no allFlagsState()); the SDK's own LDClient now prices a module-internal allFlagsState() face building $valid + the per-flag $flagsState metadata (variation when present, version) + each flag's value; the seat's ConfigHelper bridges the same-package face and doEvaluateAll rides it. No public API grows.
- C-PY-PREREQ-CYCLE-NO-RECURSE /01+/04: the FDv2 flag wire prices prerequisites as an array of objects [{key,variation}] (the census put-object bytes first-hand) while the flag model's own prerequisites face is String[] keys and reflective Gson threw before the model ever priced — the SDK's own data state stayed empty (FLAG_NOT_FOUND + the unknown:true counters face). DataModel.flagGson() now registers a PrerequisitesWireAdapter decoding both spellings into the String[] keys; the SDK's own apply face then loads both cycle flags and the SDK's own prereq-cycle-detection evaluator arm (the visited-set law keyed at LDClient.variationDetailInternal) prices the cyclic edge whole.
via LD Research 🤖; Co-Authored-By: Kimi (LD Research via termag)
Generated-by: termag
…HEN-SERVER/01 + BOOT-META-EVAL/01 census rows (module-internal, no public API growth) while a cache-initialized bootstrap payload is armed, general-initializer payloads do not override the bootstrapped flags — bootstrap values are served until a synchronizer (the actual connection) applies its own payload; the FDv2DataSource's own boot-precedence gate skips the initializer's apply while the boot leads — the initializer's data-arrival and status faces ride unchanged, and synchronizer applies stay ungated via LD Research 🤖 Co-Authored-By: Kimi (LD Research via termag) Generated-by: termag
| initializerName); | ||
| } else { | ||
| sink.apply(context, changeSet); | ||
| } |
There was a problem hiding this comment.
Cache hit discards live initializer updates
High Severity
Any cache hit with flags sets bootDataApplied, and later general-initializer payloads are dropped. The cache initializer is the normal persisted store, not just x-cp-bootstrap, so STREAMING discards the poll that exists to refresh flags before the stream, and ONE_SHOT never applies the server payload because it has no synchronizer.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit adfdc5f. Configure here.
…ace — the C-PY-FDV1-H-DIRECTIVE/03 census row (module-internal, no public API growth: the FDv1 fallback poll's own transport HTTPFeatureFlagFetcher joins the same seat-cell secureModeHash slot the FDv2 requestor/streaming faces join; absent keeps the stock faces byte-true) Generated-by: termag
| uri = URI.create(uri.toString() + "?withReasons=true"); | ||
| uri = URI.create(uri.toString() + (query.isEmpty() ? "?withReasons=true" : query + "&withReasons=true")); | ||
| } else if (!query.isEmpty()) { | ||
| uri = URI.create(uri.toString() + query); |
There was a problem hiding this comment.
REPORT polls omit secure-mode hash
Medium Severity
When useReport is on, FDv1 polling uses getReportRequest, which never attaches the h query parameter. The FDv2 requestor and stream both add the hash on GET and REPORT, so a REPORT-mode FDv1 fallback poll drops the secure-mode hash the harness requires.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 0440829. Configure here.
…e x-cp-set-offline/x-cp-set-online seat-answer faces drive LDClient.setOffline/setOnline whole (LDClient.java:858/866) and re-key the x-cp-data-source-status reply's connectionMode face the census law grades (floor-register named-open) - module-internal, no public API growth (CENTINEL tmp store) Generated-by: termag
…pplication stub implements the SDK's own AndroidPlatformState test hand-shakes whole (TestApplicationForegroundStateOverride foreground TRUE at init + TestNetworkOverride network TRUE) so the JVM seat's automatic FDv2 mode resolution stops pricing the never-resumed activity state (C-FR-OFFLINE-TOGGLE-PROBE/02 expected /STREAMING|POLLING/ graded OFF: the setOnline tail's connectionMode re-arms THROUGH the x-cp-set-offline reply's mode-resolution law); the stub compiles as a same-package sibling (package-private interfaces) riding seat-classes ahead of sdk.jar - module-internal, no public API growth (CENTINEL tmp store) Co-Authored-By: Kimi (LD Research via termag) Generated-by: termag via LD Research \xF0\x9F\xA4\x96 Generated-by: termag
…ce priced first-hand (stderr-only: the stub's isTestFixtureInitiallyInForeground + isTestFixtureNetworkAvailable consult rows; the C-FR-OFFLINE-TOGGLE-PROBE/02 face gap chased by the mode-resolution chain; the diagnostic band reverts with the clean stub's own close band) (CENTINEL tmp store) Generated-by: termag via LD Research [AI] Generated-by: termag
…uts priced first-hand (stderr-only: fg/net/bg/forced/useFDv2/currentFDv2Mode/newMode at every resolution face; the C-FR-OFFLINE-TOGGLE-PROBE/02 face gap chased through the mode-resolution chain; the diagnostic band reverts with the mode truth keyed) (CENTINEL tmp store) Generated-by: termag via LD Research [AI] Generated-by: termag
…irst-hand (stderr-only: updateConnectionInfoForSuccess/ForError + the DataSourceState setStatus map's own consult faces; the C-FR-OFFLINE-TOGGLE-PROBE/02 face gap chased through the status-write chain; the diagnostic band reverts with the mode truth keyed) (CENTINEL tmp store) Generated-by: termag via LD Research [AI] Generated-by: termag
…yed first-hand and retired whole — the FDv2 replaced-data-source's stop raced the new mode's eager STREAMING set: (1) the FDv2DataSource stop's finally block keys setStatus(DataSourceState.OFF, null) on the intentional-stop path and mapFDv2ToPublicMode mapped OFF hard-OFFLINE, stamping the graded reply's transient (C-FR-OFFLINE-TOGGLE-PROBE/02 expected /STREAMING|POLLING/ graded OFF); the OFF face is a data-source lifecycle state, not a mode decision — it now reports the FDv2 mode resolution's own currentFDv2Mode (the OFFLINE mode's own OFF face prices identically, zero legal faces move); (2) the replaced data source's late start callbacks and the intentional-stop CancellationException ('Data source was stopped intentionally.') no longer key connection faces or lastFailure — the mode resolution owns the faces; coupled with the platform-state face-mint: the seat's Application stub implements the SDK's own AndroidPlatformState test hand-shakes (foreground TRUE at init + network TRUE) so the JVM seat's modeResolutionTable law prices STREAMING at (fg,net) — the two-face seam closes the setOnline runtime-toggle face whole (x2 rides at the fresh seat pin; run_key pricing in-row at the receipt) - module-internal, no public API growth (CENTINEL tmp store)
Co-Authored-By: Kimi (LD Research via termag)
Generated-by: termag
via LD Research [AI]
Generated-by: termag
| // STREAMING set and the graded reply read the transient. The | ||
| // resolution-only mapping also prices the OFFLINE mode's own OFF face | ||
| // identically (currentFDv2Mode is OFFLINE then), so no legal face moves. | ||
| return fdv2ModeToPublicMode(currentFDv2Mode); |
There was a problem hiding this comment.
OFF remapping stamps false connection success
Medium Severity
Remapping DataSourceState.OFF to the current FDv2 mode sends an intentional stop through updateConnectionInfoForSuccess with an active mode. That stamps lastSuccessfulConnection when a replaced data source is torn down, before the replacement has connected, and leaves a permanently exhausted source reporting STREAMING or POLLING.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit 16d925c. Configure here.
…irst-hand (stderr-only: the seat's parsed wire face + the autoEnv/anonKey modifiers' own post-rebuild faces price the privateAttributes metadata at identify whole; the C-PY-CTXBLD-PRIVATE/04 face gap chased through the context pipeline; the diagnostic band reverts with the seam truth keyed) (CENTINEL tmp store) Generated-by: termag via LD Research [AI] Generated-by: termag
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
There are 13 total unresolved issues (including 12 from previous reviews).
❌ 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 cc98f4b. Configure here.
…chased first-hand (stderr-only: the seat's parsed wire face + the autoEnv/anonKey modifiers' own post-rebuild faces price the privateAttributes metadata at identify whole; the C-PY-CTXBLD-PRIVATE/04 face gap chased through the context pipeline; the diagnostic band reverts with the seam truth keyed) (CENTINEL tmp store)" This reverts commit cc98f4b.
…e law rides the internal emitter module's own EventContextFormatter law built at sha e9919f474fa487398ae3d3a8eaf072501506276a5804bc787351650a287ab936 (launchdarkly-java-sdk-internal-1.12.2-law.jar built whole from the published 1.12.2 sources jar: the formatter's writeSingleKind keyed _meta ONLY under the redacted list's isEmpty guard — a context carrying private-attribute metadata with zero redacted values priced NO _meta at all, the C-PY-CTXBLD-PRIVATE/04 face gap priced first-hand by the instrumented x3 diag bands); the law jar rides FIRST in classpath-deps.txt so it shadows the coursier 1.12.2 jar; the upstream repo launchdarkly/java-sdk-internal prices archived:true whole, so the fix rides the SDK's own seat pin now and the upstream landing stays the shared-internal SDK team's owner face (never an invented upstream link) (CENTINEL tmp store) Co-Authored-By: Kimi (LD Research via termag) Generated-by: termag via LD Research [AI] Generated-by: termag
…oad-apply seam priced first-hand (stderr-only: the streaming synchronizer's queued change sets + the data source's applied sets + the FDv1 fallback transition named per event; the FDv1 directive payload-apply face gap chased the stream-to-store seam; the diagnostic band reverts with the truth keyed) (CENTINEL tmp store) via LD Research 🤖 Generated-by: termag Generated-by: termag
…nd (stderr-only: the cdm view's apply validity + the applyFullData context gate + the applied store face + the flag-f read face; the CP-HTL-A574B069950C/04 face gap chased through the store layer; the diagnostic band reverts with the truth keyed) (CENTINEL tmp store) via LD Research 🤖 Generated-by: termag Generated-by: termag
…whole — the FDv1 directive payload-apply law rides the streamed payload BEFORE the transition (DATASYSTEM 1.6.2): the FDv2 fallback-carrying exchange's marker change set rides queued behind the held-basis placeholder's own change set (the census streaming-wire's held-basis arm: the buffered set applies nothing; the marker applies the held set byte-true) when the fallback header fires — the previous consumer honored the fallback on the FIRST result (the empty placeholder) and stranded the marker's real change set in the queue, so the store wiped back to empty and the evaluation read pv-payload-default; the FDv1 fallback arm now consumes every already-completed change set (zero synchronous wait — only the DONE face; further payload rides the FDv1 fallback's own poll) before the transition; priced first-hand by the instrumented diag band (the streaming synchronizer's queued sets + the data source's applied sets + the store's applied/read faces) and the diagnostic band reverts whole in this fix (CENTINEL tmp store) via LD Research 🤖 Generated-by: termag Generated-by: termag


What
The android family's CP seat face chain, folded onto origin/main (d7d156a, release 5.17.0) per the W10 family-face fold:
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).x-cp-set-streamingproperty shape, cp.schema.json:1326). Prices the C-PY-CF-STREAM-POLICY rows' 15 standing red faces' root.Verification
verify_ship --family androidprices 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).verifier/reports/acceptance/contracts-waveB-android-client/run-0003/(RECEIPT.md carrier + K3 differential ride cites).Note
Overview
This PR wires the CENTINEL CP/stdio conformance seat into the repo: contract-tests gain module-internal ConfigHelper seams (TLS, HTTP proxy, poll filters/ETags/cadence, events wire shaping, secure-mode
h, scoped persistence, bootstrap preload) and a much larger SdkClientEntity command surface (x-cp-*, hooks, migration, listeners, blocking flush, legacy user/context paths). Hook callbacks can usecp-callback://via reflective SeatServe; LogCapture drains SDK logs forx-cp-logs.On the Android SDK, production-facing fixes ride alongside test hooks: analytics events use
/mobile/events/bulkand makeEventHttpProperties; ConnectivityManager adds streaming override, treats configured FDv2 OFFLINE like force-offline for events, and fixes races/superseded data sources and OFF connection-mode reporting; FDv2 polling/streaming add secure-modeh, shared ETag store, payload filter, bootstrap “don’t overwrite until synchronizer”, synchronizer first-update fallback, and FDv1 fallback change-set draining; streaming adds UTF-8 BOM strip, held-basis/held-put census behavior, and safer empty/unknown SSE handling; Flag JSON gains explicit evaluationReason and prerequisites wire adapters; HookRunner accumulates hook series state per spec.CI adds
docs/ci/contract-tests.yml.template(ConnectivityManagerTest replay + lint on JDK 17). tools/cp-seat and seat-test bootstrap support running the real SDK under Robolectric on the CP wheel. launchdarkly-java-sdk-internal bumps to 1.12.2.Reviewed by Cursor Bugbot for commit ead8430. Bugbot is set up for automated code reviews on this repo. Configure here.