Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
ce6d43f
fix(sdk): decode evaluation-reason wire spellings (isExperiment/prere…
JackDanger Oct 11, 2026
a243c23
fix(conn): hold event posts while configured FDv2 mode resolves OFFLINE
JackDanger Oct 11, 2026
c4bf163
seat(local lane): CP/stdio seat + hook callback bus for the centinel …
JackDanger Oct 11, 2026
3a73e59
seat: serve the SDK's own reason serialization on the response arm (n…
JackDanger Oct 11, 2026
be6fb88
seat: the coverage-lane capability face lands (the C-CO-WRAPPER/C-PY-…
JackDanger Oct 13, 2026
e9aec8f
seat: the hook series chain rides the SDK's own arm (the coverage ban…
JackDanger Oct 14, 2026
30bcb87
seat: the x-cp-set-streaming arm rides the SDK's own mode-resolution …
JackDanger Oct 17, 2026
bddf56f
fold origin/main d7d156a7 into the cp-seat lane (W10: the family face…
JackDanger Oct 17, 2026
3643790
seat (the K3 PR review ride's fix face): the 3 BLOCK findings close —…
JackDanger Oct 17, 2026
551dc55
fix(seat): decouple the contract-tests module from the seat module (t…
JackDanger Oct 17, 2026
3cce215
fix(hooks,conn): the family's own red faces close (the K3 rides' CI f…
JackDanger Oct 17, 2026
5a7f7b0
revert(seat): the connector event-gate dedupe over-repressed main's o…
JackDanger Oct 17, 2026
1deba84
fix(conn): the event-gate dedupe's last-stage face closes — updateEve…
JackDanger Oct 18, 2026
b725748
fix(seat): the CP ready envelope rides FIRST (Seat.main emits the mac…
JackDanger Oct 18, 2026
884efd2
fix(seat): the CP seat's runtime classpath rides okhttp-eventsource 5…
JackDanger Oct 18, 2026
0f46c5b
fix(hooks): the deduping after face prices ONLY its own marker identi…
JackDanger Oct 18, 2026
ec5a4d8
fix(seat): the closeClient reply prices CP-SPEC 4.4's EMPTY-2xx face …
JackDanger Oct 20, 2026
32f6b2a
declare(honors): the two implemented-but-undeclared faces crossed the…
JackDanger Oct 20, 2026
591eab7
ci: land the ConnectivityManagerTest gate's apply-click bytes — docs/…
JackDanger Oct 20, 2026
2df6a7c
fix(seat): the seat-cell harness carries its tRun-site server library…
JackDanger Oct 26, 2026
255dfee
fix(seat): the skip-verify TLS face prices a consistent factory/trust…
JackDanger Oct 26, 2026
afbd9e3
fix(seat): the three FEATURE seams price the standing-red feature wav…
JackDanger Oct 27, 2026
a79fca3
fix(seat): the events POST joins the client-side wire law of record —…
JackDanger Oct 27, 2026
b94f4f2
fix(seat): the events-context census row faces price their SDK-side w…
JackDanger Oct 27, 2026
ee1e310
fix(seat): the CP create-instance envelopes parse through the same ce…
JackDanger Oct 27, 2026
877a3b0
fix(seat): the streaming wire's census-law seats — the FDv2 held-basi…
Oct 27, 2026
7fc78a5
fix(seat): the held put rides the census's intent-less marker arm — t…
Oct 27, 2026
0a3cde8
fix(seat): the held-basis arm rides the xfer-full intent's own unmark…
Oct 27, 2026
c91000f
fix(seat): the poll-interval + privacy window rows' SDK-side bits pri…
JackDanger Oct 30, 2026
76ae449
fix(seat): the cadence seam rides the seat's own per-client provision…
JackDanger Oct 30, 2026
606f469
fix(seat): the polling builder own-setter rides the same seat-provisi…
JackDanger Oct 30, 2026
02236ec
fix(seat): the old-user identify rides the SDK's own legacy-user tran…
JackDanger Oct 30, 2026
e847041
fix(seat): the events-POST wire carries the identify event's own top-…
JackDanger Oct 30, 2026
38abaec
fix(seat): the hooks census wave's SDK-side seat bits — the init-seri…
JackDanger Oct 30, 2026
e045de7
fix(seat): the migrationVariation read rides the SDK's own package-pr…
JackDanger Oct 30, 2026
d7a9e44
fix(seat): the migrationVariation read prices the harness's own ldmig…
JackDanger Oct 30, 2026
1a21c48
fix(seat): the context-comparison census wire prices the harness's ow…
JackDanger Oct 30, 2026
7d3589a
fix(seat): the REPRICE census re-ride wave's SDK-side seat bits — the…
JackDanger Oct 30, 2026
b011c4b
fix(sdk): the feature event's reason rides whenever evaluationReasons…
JackDanger Oct 30, 2026
11a2d01
fix(seat): the CP/http transport framing of the census CP/stdio seat …
JackDanger Oct 30, 2026
7a8b4a1
fix(sdk): the analytics events POST joins the client-v5 mobile events…
JackDanger Oct 30, 2026
ede74a5
fix(seat): the hooks evaluation-series payload shapes its wire member…
JackDanger Oct 30, 2026
a707103
fix(seat): the events context-properties wire rides the core's own co…
JackDanger Oct 31, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,13 @@ build
.project
.settings/
.vscode/

# CENTINEL seat lane (local branches; never pushed upstream): build outputs and probe leftovers
/gen/
/sdk-classes/
/seat-classes/
/sdk.jar
tools/seat-test/classes/
tools/seat-test/extracted-cons/
tools/seat-test/stub-src/
tools/seat-test/stub-src-tmp/
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,10 @@ We encourage pull requests and other contributions from the community. Check out
* [docs.launchdarkly.com](https://docs.launchdarkly.com/ "LaunchDarkly Documentation") for our documentation and SDK reference guides
* [apidocs.launchdarkly.com](https://apidocs.launchdarkly.com/ "LaunchDarkly API Documentation") for our API documentation
* [launchdarkly.com/blog](https://launchdarkly.com/blog/ "LaunchDarkly Blog Documentation") for the latest product updates

## Continuous integration

`docs/ci/contract-tests.yml.template` holds the CI gate's bytes: an operator applies
them at `.github/workflows/contract-tests.yml`, and the landed workflow prices the
`ConnectivityManagerTest` replay faces — the `a243c235` FDv2-OFFLINE double-window
trio — on every pull request to `main` (Ubuntu Actions runner, ubuntu-22.04, JDK 17).

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ public HookCallbackService(URI serviceUri) {
}

public void post(Object params) {
// CENTINEL seat extension (CP-SPEC 7.2): a cp-callback:// callback URI rides
// the CP/stdio callback wheel — the seat MUST NOT dial it; it emits a
// `callback` envelope and awaits the runner's reply on stdin before the
// SDK-side call completes. Any other scheme keeps the harness's own
// HTTP receiver arm (used when the seat runs under CP/http on a device).
if (serviceUri != null && "cp-callback".equals(serviceUri.getScheme())) {
SdkClientEntity.seatPostCallbackHTTPFace(serviceUri, params);
return;
}
RequestBody body = RequestBody.create(
TestService.gson.toJson(params == null ? "{}" : params),
MediaType.parse("application/json")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package com.launchdarkly.sdktest;

import com.launchdarkly.logging.LDLogAdapter;
import com.launchdarkly.logging.LDLogLevel;
import com.launchdarkly.logging.LogValues;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentLinkedDeque;

/**
* The x-cp-logs drain face (CP-SPEC 10): one ring of {level, code, message}
* records riding the seat logging chain. The capture adapter wraps the base
* adapter in TestService, so every SDK-facing log record (the seat's own
* logger included) both prints through the base adapter and records here —
* no SDK behavior changes, the records surface the seat's own log stream.
*
* NOT verifier-specific: the command maps CP onto the SDK's public logging
* API (CP-SPEC 11 item 8). The record shape prices the python seat's own
* precedent ({level, code, message}: code = the message's first four chars
* upper-cased).
*/
public final class LogCapture {
private final ConcurrentLinkedDeque<Map<String, Object>> records =
new ConcurrentLinkedDeque<>();
private final int limit;

public LogCapture(int limit) {
this.limit = limit;
}

public List<Map<String, Object>> drain() {
List<Map<String, Object>> out = new ArrayList<>();
Map<String, Object> row;
while ((row = records.poll()) != null) {
out.add(row);
}
return out;
}

void add(LDLogLevel level, String message) {
java.util.Map<String, Object> row = new java.util.LinkedHashMap<>();
row.put("level", level == null ? "info" : level.name().toLowerCase());
row.put("code", message != null
? message.substring(0, Math.min(4, message.length())).toUpperCase()
: "");
row.put("message", message == null ? "" : message);
records.add(row);
while (records.size() > limit) {
records.poll();
}
}

/** LDLogAdapter that records every log call and forwards to the base
* adapter's own channel (the seat's log stream keeps printing raw). */
public static final class CaptureLogAdapter implements LDLogAdapter {
private final LDLogAdapter base;
private final LogCapture capture;

public CaptureLogAdapter(LDLogAdapter base, LogCapture capture) {
this.base = base;
this.capture = capture;
}

@Override
public LDLogAdapter.Channel newChannel(String name) {
return new CaptureChannel(base.newChannel(name), capture);
}
}

private static final class CaptureChannel implements LDLogAdapter.Channel {
private final LDLogAdapter.Channel base;
private final LogCapture capture;

CaptureChannel(LDLogAdapter.Channel base, LogCapture capture) {
this.base = base;
this.capture = capture;
}

private static String render(String message, Object... args) {
if (message == null) {
return "";
}
if (args == null || args.length == 0) {
return message;
}
// the logging lib's "{}" substitution face: the record's message prices
// the rendered line the SDK actually logged
StringBuilder out = new StringBuilder(message.length() + 40);
int argIndex = 0;
int i = 0;
while (i < message.length()) {
int brace = message.indexOf("{}", i);
if (brace < 0) {
out.append(message, i, message.length());
break;
}
out.append(message, i, brace);
if (argIndex < args.length) {
Object arg = args[argIndex++];
out.append(arg instanceof LogValues.StringProvider
? ((LogValues.StringProvider) arg).get()
: String.valueOf(arg));
}
i = brace + 2;
}
return out.toString();
}

private void record(LDLogLevel level, String message, Object[] args) {
capture.add(level, render(message, args != null ? args : new Object[0]));
}

@Override
public boolean isEnabled(LDLogLevel level) {
return base.isEnabled(level);
}

@Override
public void log(LDLogLevel level, Object message) {
capture.add(level, message instanceof LogValues.StringProvider
? ((LogValues.StringProvider) message).get()
: String.valueOf(message));
base.log(level, message);
}

@Override
public void log(LDLogLevel level, String message, Object arg1) {
record(level, message, new Object[] {arg1});
base.log(level, message, arg1);
}

@Override
public void log(LDLogLevel level, String message, Object arg1, Object arg2) {
record(level, message, new Object[] {arg1, arg2});
base.log(level, message, arg1, arg2);
}

@Override
public void log(LDLogLevel level, String message, Object... args) {
record(level, message, args);
base.log(level, message, args);
}
}
}
Loading
Loading