feat: Add environment ID support for hooks. - #594
Conversation
Co-Authored-By: rlamb@launchdarkly.com <4955475+kinyoklion@users.noreply.github.com>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
|
@cursor review |
**Requirements** - [x] I have added test coverage for new or changed functionality - [x] I have followed the repository's [pull request submission guidelines](../blob/master/CONTRIBUTING.md#submitting-pull-requests) - [x] I have validated my changes against all supported platform versions **Related issues** Ports #410 (v2) to v3. Companion SDK work: launchdarkly/python-server-sdk#484, launchdarkly/cpp-sdks#594. **Describe the solution you've provided** Same capability and test as #410, adapted to the v3 data system: - the `hook-environment-id` capability, documented in `docs/service_spec.md` - `DataSystemOptionEnvironmentID(...)`, a top-level `SDKDataSystemOption` that makes every mock service of the data system (initializers and synchronizers, in all connection modes) send an `X-LD-EnvID` response header - an evaluation-series test (`hooks/evaluation/provides the environment ID`) asserting `evaluationSeriesContext.environmentId` on `afterEvaluation`, run against the SDK's default data source plus an explicit polling subtest for client-side/`server-side-polling` SDKs Unlike v2, where the option wrapped the single `SDKDataSource` handler, the header is applied in `createEndpoints`, so both FDv2 initializers and synchronizers report it — matching production, where the header is on every flag-delivery response. **Describe alternatives you've considered** Making the option per-connection-mode. Environment ID is a property of the environment, not of a connection mode, so a single top-level option keeps call sites simple and covers every mode. **Additional context** Verified against the FDv2 contract services of python-server-sdk (#484) and cpp-sdks (#594): `hooks/evaluation/provides the environment ID` passes for both, in default (streaming) and polling modes. Track hooks (`trackSeriesContext.environmentId`) are documented but not yet asserted, same as in v2. Link to Devin session: https://app.devin.ai/sessions/bfe54128e2804a96bb100e6120e9a3ef Requested by: @kinyoklion <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Overview** > Adds the **`hook-environment-id`** capability so SDKs that read LaunchDarkly’s `X-LD-EnvID` from streaming/polling responses must surface it on hook series contexts (`evaluationSeriesContext.environmentId`, and `trackSeriesContext` when track hooks are supported). The harness documents this in `service_spec.md` and gates a new evaluation hook test on the capability. > > **Mock flag delivery** now supports `DataSystemOptionEnvironmentID(...)`, a top-level data-system option that wraps every initializer and synchronizer endpoint (all connection modes) to emit `X-LD-EnvID`, matching production flag-delivery responses in the v3 data system. > > The test **“provides the environment ID”** evaluates a flag and asserts `environmentId` on the `afterEvaluation` hook payload, under the default data source and an explicit polling subtest for client-side / server-side-polling SDKs. Track-hook `environmentId` is documented but not asserted yet (same as v2). > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit 8331c29. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: rlamb@launchdarkly.com <4955475+kinyoklion@users.noreply.github.com>
Co-Authored-By: rlamb@launchdarkly.com <4955475+kinyoklion@users.noreply.github.com>
Co-Authored-By: rlamb@launchdarkly.com <4955475+kinyoklion@users.noreply.github.com>
|
@cursor review |
Co-Authored-By: rlamb@launchdarkly.com <4955475+kinyoklion@users.noreply.github.com>
Co-Authored-By: rlamb@launchdarkly.com <4955475+kinyoklion@users.noreply.github.com>
|
|
||
| void PollingDataSource::HandlePollResult(network::HttpResult const& res) { | ||
| if (!res.IsError() && (res.Status() == 200 || res.Status() == 304)) { | ||
| if (auto const it = res.Headers().find("X-LD-EnvID"); |
There was a problem hiding this comment.
We should have a centralized location for this header definition. Also potentially a shared function for getting this header and setting it into a sink.
There was a problem hiding this comment.
Done in e2ac563 — new data_systems/environment_id_header.hpp holds kEnvironmentIdHeader, ReadEnvironmentId overloads for HttpResult and Beast headers, and a ReportEnvironmentId(destination, headers) helper. All four sites (FDv1 polling/streaming, FDv2 polling/streaming) use it, and the local copy in fdv2_polling_impl.cpp is gone.
| if (status == | ||
| DataSourceEventHandler::MessageStatus::kInvalidMessage) { | ||
| // Invalid data received - restart the connection with backoff | ||
| // to get a fresh stream. The backoff mechanism prevents rapid | ||
| // reconnection attempts. | ||
| LD_LOG(self->logger_, LogLevel::kWarn) | ||
| << "Received invalid data from stream, restarting connection"; | ||
| << "Received invalid data from stream, restarting " | ||
| "connection"; | ||
| if (self->client_) { |
There was a problem hiding this comment.
Why were these lines changed?
Co-Authored-By: rlamb@launchdarkly.com <4955475+kinyoklion@users.noreply.github.com>
Summary
Server SDK: the
X-LD-EnvIDresponse header now reachesEvaluationSeriesContext/TrackSeriesContext, for both FDv1 and FDv2 data systems.The environment ID travels with the data rather than through a side channel, mirroring dotnet-core#81 (
InitWithMetadata→ store metadata → client reads it back):IDestinationgainsSetEnvironmentId(std::string);MemoryStoreretains it and only reports it once initialized, so hooks never see an ID before a payload has been applied.FDv2SourceResult::environment_id), andFDv2DataSystemwrites it to the store alongside the changeset.IDataSystem::EnvironmentId()is whatClientImplpasses into the hook contexts (std::nulloptfor offline/lazy load).Only successful responses count: errors, missing headers, and empty values leave the value untouched.
Verified with harness
hooks/evaluation/provides the environment ID(default + polling) against both v2.39.0 (FDv1) and v3.2.0-alpha.6 (FDv2), plus the server unit suite.Link to Devin session: https://app.devin.ai/sessions/bfe54128e2804a96bb100e6120e9a3ef
Requested by: @kinyoklion