Skip to content

feat: Verify per-context private attributes are not applied to other contexts - #430

Merged
kinyoklion merged 1 commit into
v2from
devin/1787074920-private-attribute-scoping-tests-v2
Aug 19, 2026
Merged

feat: Verify per-context private attributes are not applied to other contexts#430
kinyoklion merged 1 commit into
v2from
devin/1787074920-private-attribute-scoping-tests-v2

Conversation

@kinyoklion

@kinyoklion kinyoklion commented Aug 18, 2026

Copy link
Copy Markdown
Member

Adds contract coverage for private attributes declared in a context's _meta.privateAttributes leaking into an SDK's globally configured private attribute list, so they get applied to unrelated contexts.

  • Port of feat: Verify per-context private attributes are not applied to other contexts #429 to the v2 line. Nearly every SDK's CI pulls the harness from v2 (ruby, php, erlang, rust, haskell, java, dotnet, ios, android, flutter, roku, node-client), so without this the regression below is untested for those SDKs.
  • 3 new subtests under events/context properties; runs for server-side, client-side and PHP suites. No existing test or expectation changed.
  • Reproduces ruby-server-sdk#416: ContextFilter did @private_attributes.concat(context.private_attributes), mutating the configured list, so a context that declared nothing private still had the previous context's private attributes redacted.

Requirements

  • I have added test coverage for new or changed functionality
  • I have followed the repository's pull request submission guidelines
  • I have validated my changes against all supported platform versions — validation across SDKs in progress, results posted as a comment

Related issues

Implementation details

Why the existing tests could not catch it

makeEventContextTestParams creates a new client per parameter, so the ContextFilter is always fresh, and every context within one parameter comes from a single factory with identical _meta.privateAttributes. A leaked private attribute is therefore always an already-expected private attribute. The existing multi-kind fixtures don't catch it either: they either set no per-context privates, or set them only on the kind that is filtered last, and the leaked names don't exist as attributes on the other kind.

What the new tests do

eventContextPrivateAttributeScoping uses one client for multiple identify events, and each context carries the same three attributes (selfPrivate, globallyPrivate, visible) while differing only in what it declares private:

  1. private attributes of one context are not applied to later contexts — identify a context declaring selfPrivate private, then identify a different context that declares nothing private, and assert selfPrivate is still visible on the second one.
      1. private attributes of one kind are not applied to other kinds of the same context (declared by org / by user) — a multi-kind context where only one kind declares selfPrivate private; the other kind must keep it. Both orderings are covered because SDKs filter the individual contexts in an arbitrary order — with the Ruby bug present, only the org variant fails (Ruby filters org first), so a single-ordering test would catch this only half the time.

globallyPrivate is configured via GlobalPrivateAttributes and asserted redacted in every expectation, so an SDK cannot pass by throwing away its configured private attributes along with the per-context ones.

Verification

Negative control against ruby-server-sdk with the #416 fix locally reverted to .concat(...): subtests 1 and 2 fail with the second context wrongly reporting redactedAttributes: ["selfPrivate","globallyPrivate"]; all three pass with the fix in place.

Link to Devin session: https://app.devin.ai/sessions/6e3076285f2849919b966a4f801075ca
Requested by: @kinyoklion


Note

Overview
Adds three new subtests under events/context properties via eventContextPrivateAttributeScoping, hooked from EventContexts. Existing expectations are unchanged.

The harness reuses one SDK client for multiple identify events so failures like mutating the global private-attribute list (e.g. ruby-server-sdk#416) are detectable—prior cases mostly created a fresh client per scenario.

Coverage: (1) a context that marks selfPrivate private must not cause a later context with no per-context privates to redact selfPrivate; (2) for multi-kind contexts, privates declared on org or user must not redact the same attribute on the other kind—both orderings are tested.

Every assertion still expects globallyPrivate (from GlobalPrivateAttributes) to be redacted so SDKs cannot pass by dropping all configured privates.

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

…contexts

Co-Authored-By: rlamb@launchdarkly.com <4955475+kinyoklion@users.noreply.github.com>
@kinyoklion kinyoklion self-assigned this Aug 18, 2026
@devin-ai-integration

Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@devin-ai-integration devin-ai-integration Bot added the devin-pr PRs created by Devin label Aug 18, 2026
@kinyoklion
kinyoklion marked this pull request as ready for review August 18, 2026 17:46
@kinyoklion
kinyoklion requested a review from a team as a code owner August 18, 2026 17:46

@joker23 joker23 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

assuming this is the same as the v3 PR

@devin-ai-integration

Copy link
Copy Markdown
Contributor

Yes — identical to #429 apart from the v2 branch's data source naming (dataSource vs v3's dataSystem); a diff of the two diffs shows only those 3 identifier lines plus hunk offsets.

I opened both because most SDKs' CI pulls the harness from v2 (ruby, php, erlang, rust, haskell, java, dotnet, node-client, cpp-client, ios, android, flutter, roku), so the v3-only version wouldn't have covered the Ruby SDK this was written for. Happy to drop either one if you'd rather it live on a single line.

@devin-ai-integration

Copy link
Copy Markdown
Contributor

SDK validation

Ran the 3 new subtests (events/context properties/private attributes of one ...) 3x against each SDK's own contract test service, plus the full events/context properties group with both the PR binary and a baseline binary built from the base branch, to separate new failures from pre-existing ones. Every SDK was run on the harness line(s) its CI actually pulls.

No SDK fails the new tests, and no SDK gains a new failure. 18 services PASS, 2 N/A (no/incapable test service), 2 BLOCKED (need a macOS runner / a physical Roku), 1 pending.

Negative control: reverting launchdarkly/ruby-server-sdk#416 (+ back to .concat) makes the sequential subtest and the multi-kind "declared by org" subtest fail, while "declared by user" still passes — which is why both kind orderings are included.

Per-SDK results
SDK Verdict Line(s) run Version Commit Exit codes Notes
go-server-sdk PASS v2 + v3 7.15.6 88bd0dc 0,0,0 / 0,0,0
python-server-sdk PASS v2 + v3 9.16.1 5da1515 0,0,0 / 0,0,0 v3-only /~1ssn failures pre-existing, and absent from the v3.0.0-alpha.6 tag CI pins
ruby-server-sdk PASS v2 8.14.0 2fd26ea 0,0,0 includes #416
php-server-sdk PASS v2 6.8.2 80f0d88 0,0,0
erlang-server-sdk PASS v2 3.11.2 4bb97ec 0,0,0 OTP 24 only
rust-server-sdk PASS v2 3.2.0 e26d224 0,0,0
haskell-server-sdk PASS v2 4.6.0 aab4645 0,0,0 built with system GHC 8.8.4
java-server-sdk PASS v2 + v3 7.15.0 2a98583 0,0,0 / 0,0,0
dotnet-server-sdk PASS v2 + v3 8.16.0 7a9529c 0,0,0 / 0,0,0
dotnet-client-sdk PASS v2 5.9.5 7a9529c 0,0,0 run as net8.0 on Linux, not CI's macOS target
node-server-sdk (js-core) PASS v2 + v3 9.13.0 965008f 0,0,0 / 0,0,0
shopify-oxygen (js-core) N/A v2 + v3 0.1.19 965008f 1,1,1 HTTP 400 on client creation for the whole events group, identical on baseline; its CI skips event.*
cpp-server-sdk PASS v2 + v3 3.13.1 120b79a 0,0,0 / 0,0,0 Beast backend only, not the -curl jobs
cpp-client-sdk PASS v2 3.11.4 120b79a 0,0,0
node-client-sdk PASS v2 3.4.3 91b619c 0,0,0
electron-client-sdk N/A 5ed0bb6 no contract test service in the repo
flutter-client-sdk PASS v2 + v3 4.20.2 a11df74 0,0,0 / 0,0,0 Dart implementation via flutter test, headless Linux
android-client-sdk PASS v2 + v3 5.15.0 347ca2c 0,0,0 / 0,0,0 emulator API 25; v3-head-only /~1ssn failures are newer than its v3.1.0-alpha.6 pin
ios-client-sdk BLOCKED 5bfd8f8 ContractTests is Apple-platform only; needs a macOS + Xcode runner
roku-client-sdk BLOCKED fb5c24b needs sideloading to a physical Roku; its CI runs lint + package only
js-core browser/react/react-native/vue pending v3 965008f still running

Each PASS row was checked to have actually executed the new subtests (Test Summary: ... 5 ran, subtests listed as executed rather than SKIPPED) and to be unaffected by that repo's suppressions file / TEST_HARNESS_PARAMS. Full logs (3 runs + PR/baseline group runs per SDK) were kept per session.

Rollout note: several repos pin a released harness rather than a branch tip (dotnet downloads the latest v2 release; python/go/java/dotnet pin v3.0.0-alpha.6; android pins v3.1.0-alpha.6), so these subtests only reach those CI jobs once a new v2 release / v3 alpha is cut.

@devin-ai-integration

Copy link
Copy Markdown
Contributor

Addendum — the js-core client-side workspaces finished, all PASS with no new failures (js-core 965008f):

Workspace Verdict Line(s) run Version Exit codes
node-client PASS v2 + v3 4.0.2 0,0,0 / 0,0,0
browser PASS v2 + v3 4.9.4 0,0,0 / 0,0,0
vue PASS v2 + v3 0.2.0 0,0,0 / 0,0,0
react-native PASS v2 + v3 10.19.4 0,0,0 / 0,0,0
react PASS v2 4.1.12 0,0,0
electron PASS v2 0.0.1 0,0,0

Test Summary: 65 total, 60 skipped, 5 ran on v2 and 66 total, 61 skipped, 5 ran on v3 for each; browser/vue/react ran under Playwright chromium on Xvfb, react-native on a headless API-31 emulator. The group run against the baseline binary is failure-free for every workspace, so no suppression entries are needed.

That closes out the run: 24 services PASS, 2 N/A (standalone electron-client-sdk repo has no contract test service; js-core shopify-oxygen's service 400s on the whole events group, pre-existing and skipped in its CI), 2 BLOCKED (ios needs a macOS + Xcode runner, roku a physical device).

@kinyoklion
kinyoklion merged commit 4d9b3a6 into v2 Aug 19, 2026
8 checks passed
@kinyoklion
kinyoklion deleted the devin/1787074920-private-attribute-scoping-tests-v2 branch August 19, 2026 20:42
kinyoklion pushed a commit that referenced this pull request Aug 19, 2026
🤖 I have created a release *beep* *boop*
---


##
[2.41.0](v2.40.0...v2.41.0)
(2026-08-19)


### Features

* Verify per-context private attributes are not applied to other
contexts
([#430](#430))
([4d9b3a6](4d9b3a6))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Overview**
> **Release 2.41.0** bumps the package version from **2.40.0** to
**2.41.0** in the release manifest, `main.go` `versionString`, and
**CHANGELOG**.
> 
> The changelog entry for this release documents the main functional
change shipped in this version: **contract tests** (from #430) that
verify **per-context `_meta.privateAttributes` are scoped to that
context only**—including that private attrs on one context or kind are
**not** incorrectly applied when later events use other contexts or
other kinds in a multi-context.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
c5f9c21. 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: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

devin-pr PRs created by Devin

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants