Skip to content

feat: consume dependent precomputed assignments in browser - #458

Draft
typotter wants to merge 2 commits into
dependent-flags-node-precomputefrom
dependent-flags-precomputed-browser-consumer
Draft

typotter wants to merge 2 commits into
dependent-flags-node-precomputefrom
dependent-flags-precomputed-browser-consumer

Conversation

@typotter

@typotter typotter commented Nov 5, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Browser consumer counterpart for dependent-flag precomputed packets. This PR is stacked on the Node precompute producer in #457.

  • read successful roots from attributes.flags and failures from sibling attributes.flagEvaluationErrors
  • use the caller default for failed roots without fabricated assignment values
  • fail the root closed when any prerequisite assignment is missing, quarantined, or malformed
  • retain prerequisite-specific errors while consuming normalized root errors
  • negotiate dependent precomputed assignments only through cap_dependent-flags-variant-key=v1 in the precomputed request URL, including proxy requests
  • leave the existing capability header unchanged
  • decrypt obfuscated parent references with the plaintext root key, then hash the recovered key for top-level lookup
  • reject unauthenticated references as root PARSE_ERROR without speculative descendant telemetry
  • propagate selected prerequisites to exposure hooks and RUM session enrichment
  • emit ordered dependency evaluation telemetry
  • keep legacy response parsing compatible with the optional sibling error map
  • treat nullable error fields on successful dependency records as absent
  • reject top-level ERROR assignments during dependency reconstruction
  • isolate the new precomputed wire shape in a v3 browser-cache namespace while reading v2 snapshots during upgrade
  • carry dependency details through private internal metadata; generated public declarations and package exports are unchanged
  • pin the corpus submodule to the encrypted dependent-flags fixture from Add precomputed dependent flag fixture ffe-system-test-data#62

Validation

  • full core suite: 1,390 passed, 6 skipped
  • full browser suite: 666 passed
  • full Node suite: 737 passed
  • focused core and browser suites: 349 passed
  • focused precomputed corpus: 2 passed
  • monorepo typecheck passed
  • core, browser, and Node builds passed
  • lint and formatting checks passed
  • packaging and generated-entrypoint drift checks: 126 passed
  • generated core and browser public declarations match the producer base byte-for-byte
  • protobuf regeneration produced no drift
  • git diff --check passed

Related: DataDog/ffe-system-test-data#62, ddoghq/dd-source#155394, Node producer #457.

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.

🟡 Changes recommended

Spreading hookContext can re-trigger a throwing enumerable getter despite earlier error handling, preventing dependency callbacks from being delivered.

2 open issues
What changed in this PR

Adds browser support for precomputed dependent-flag assignments, complementing the Node producer and building on the evaluator failure semantics from #453.

Changes:

  • Parses separate assignment and error maps, preserving caller defaults for failed roots.
  • Authenticates encrypted prerequisite references and resolves their assignments.
  • Propagates selected prerequisites to exposure hooks and RUM, with ordered evaluation telemetry and regression coverage.
File Description
yarn.lock Locks the new cipher dependency.
packages/​core/​test/​evaluation/​precomputed-dependencies.spec.ts Tests dependency resolution, failures, and validation.
packages/​core/​test/​evaluation/​flag-key-obfuscation.spec.ts Tests encrypted references and authentication failures.
packages/​core/​src/​evaluation/​precomputed-evaluation.ts Handles failed roots and attaches dependency details.
packages/​core/​src/​evaluation/​precomputed-dependencies.ts Resolves and retains ordered prerequisite evaluations.
packages/​core/​src/​evaluation/​index.ts Exports dependency evaluation helpers.
packages/​core/​src/​configuration/​wire.test.ts Adjusts the assignment round-trip assertion.
packages/​core/​src/​configuration/​wire-validation.ts Validates error maps and dependency records.
packages/​core/​src/​configuration/​flag-key-obfuscation.ts Advertises dependent-flag capability.
packages/​core/​src/​configuration/​dependency-key-encryption.ts Adds authenticated dependency-key encryption and decryption.
packages/​core/​src/​configuration/​configuration.ts Defines dependency records and root error types.
packages/​core/​README.md Documents the dependent-assignment wire contract.
packages/​core/​package.json Adds the cipher library.
packages/​browser/​test/​transport/​fetchConfiguration.spec.ts Updates capability-header expectations.
packages/​browser/​test/​openfeature/​tracking-lifecycle.spec.ts Tests managed-hook after forwarding.
packages/​browser/​test/​openfeature/​rumIntegration.spec.ts Tests selected prerequisite recording and failure isolation.
packages/​browser/​test/​openfeature/​provider.spec.ts Updates provider capability-header expectations.
packages/​browser/​test/​openfeature/​precomputed-corpus.spec.ts Adds a shared-fixture browser runner.
packages/​browser/​test/​openfeature/​obfuscation.spec.ts Updates obfuscation capability expectations.
packages/​browser/​test/​openfeature/​flagEvaluations.spec.ts Tests ordered and retained prerequisite telemetry.
packages/​browser/​test/​openfeature/​exposures.spec.ts Tests dependency exposure ordering and filtering.
packages/​browser/​test/​openfeature/​exposure-hook.spec.ts Tests dependency callbacks, context, and hook failures.
packages/​browser/​test/​openfeature/​core-provider-exposure-cache.spec.ts Tests prerequisite cache decisions and configuration scope.
packages/​browser/​src/​openfeature/​tracking.ts Forwards the managed hook’s after stage.
packages/​browser/​src/​openfeature/​rumIntegration.ts Records selected prerequisites before the root.
packages/​browser/​src/​openfeature/​flagEvaluations.ts Emits ordered dependency telemetry and retains it across hook failures.
packages/​browser/​src/​openfeature/​exposures.ts Prepares and emits selected prerequisite exposures.
packages/​browser/​src/​openfeature/​exposure-hook.ts Delivers prerequisite callbacks with shared tracking context.
packages/​browser/​src/​openfeature/​core-provider.ts Finalizes selected prerequisite exposure decisions.
packages/​browser/​src/​openfeature/​core-provider-metadata.ts Preserves dependency bindings when adding configuration metadata.
LICENSE-3rdparty.csv Adds cipher-library license attribution.

Review effort: Balanced

Comment thread packages/core/README.md Resolved

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.

🔵 Needs a closer look

An ERROR dependency assignment is reconstructed as a selection without an error code, allowing a successful root evaluation to emit exposure and RUM enrichment for a failed prerequisite.

No open issues 🎉

1 previously missed issue

In code that hasn't changed since last review

Medium severity Reject ERROR assignments during dependency reconstruction

packages/​core/​src/​evaluation/​precomputed-dependencies.ts:90

A top-level assignment with reason: 'ERROR' passes wire validation and is reconstructed here as a selected dependency with a variant but no error code. When the root succeeds, the browser can emit an exposure and RUM enrichment for that failed prerequisite. Reject ERROR assignments here and return dependency PARSE_ERROR details, as for missing or quarantined assignments.

2 resolved since last review

Review effort: Balanced

@aarsilv

aarsilv commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

🤖 Comment from Claude working with Aaron Silverman:

[P1] Carry-over from #453. When #453 was rebuilt from main, its threads about the precomputed path were resolved as belonging to this work. I checked them against this PR's head (62219ff):

  • Capability transport (still open). packages/browser/src/transport/fetchConfiguration.ts:146 sends dependent-flag-variant-key-v1 only in X-DD-FEATURE-FLAGS-CAPABILITIES, and omits that header by default when flaggingProxy is set (callers can supply it through customHeaders). The suggestion in this thread applies unchanged: add cap_dependent-flags-variant-key=v1 to the precompute request URL, with or without flaggingProxy, and remove the token from the header list.
  • Contract before advertising (partly open). The response shape now matches Add precomputed dependent flag fixture ffe-system-test-data#62 (dependencyEvaluations plus flagEvaluationErrors), and the consumer uses dependency-key-encryption.ts to decrypt dependency keys in obfuscated responses. That addresses the obfuscation point in this thread. The design document still uses reachedPrerequisiteKeys, and the fixture schema does not yet enforce the success and error forms of a dependency entry. The flagSelection point is still open: the flag-selection spec does not say whether the producer keeps a prerequisite that is outside the selection. packages/core/src/configuration/flag-key-obfuscation.ts:12 advertises support before Add precomputed dependent flag fixture ffe-system-test-data#62 merges.
  • Fixture pin (reminder). The fixture gitlink stays at c8cc8da, so precomputed-corpus.spec.ts does not run the dependent precomputed corpus in CI. The description explains why. Please repin after Add precomputed dependent flag fixture ffe-system-test-data#62 merges.
  • My new comment on fix: harden dependent flag failure handling #453 about the root error code in the precompute path also applies here, because this PR includes the same change.

@github-actions

github-actions Bot commented Nov 6, 2026 •

Copy link
Copy Markdown

OpenFeature Browser Provider Bundle Sizes

Measured from the Vite production output after installing packed @datadog/flagging-core and @datadog/openfeature-browser tarballs.

Both scenarios initialize an OpenFeature provider, evaluate a boolean flag, change context, and evaluate again. Telemetry is disabled for DatadogProvider; no tracking hooks are registered for DatadogCoreProvider. DatadogProvider fetches precomputed assignments for each context; DatadogCoreProvider receives rules from fetchRulesConfiguration once and evaluates locally.

Sizes include OpenFeature and the same small scenario harness. Configuration responses are supplied by Playwright and are not bundled. These are complete scenario JS sizes, not configuration payload sizes or isolated provider/Protobuf costs; the difference between rows is not a decoder-only delta.

Scenario HTML JS Assets Raw JS Gzip JS Protobuf Markers
DatadogProvider (precomputed fetching) provider.html 15 122.7 KiB 44.1 KiB no
DatadogCoreProvider + fetchRulesConfiguration core-provider.html 15 222.4 KiB 71.6 KiB yes

Dependency checks passed: no Protobuf markers in default/precomputed scenarios; markers present in rules-based scenarios.

OpenFeature Browser Tracking Hook Bundle Sizes

Synthetic entrypoints import and call tracking hook factories from the packed @datadog/openfeature-browser/rules-based ESM package. Deltas are measured against the no-hook baseline from the same Vite production build.

Scenario HTML JS Assets Raw JS Raw Δ Gzip JS Gzip Δ
tracking hook baseline tracking-baseline.html 5 2.8 KiB 0 B 1.5 KiB 0 B
exposure logging hook tracking-exposure.html 10 73.2 KiB +70.5 KiB 27.0 KiB +25.5 KiB
evaluation logging hook tracking-evaluation.html 11 69.2 KiB +66.4 KiB 25.6 KiB +24.1 KiB
RUM tracking hook tracking-rum.html 8 7.3 KiB +4.5 KiB 3.7 KiB +2.2 KiB
all tracking hooks tracking-all.html 13 78.6 KiB +75.8 KiB 29.4 KiB +27.9 KiB

OpenFeature Node Server Bundle Sizes

Independent Webpack production builds of the installed core and Node SDK tarballs, targeting Node 18 with ESM (module export condition). Node built-ins remain external. OpenFeature, dd-trace, and configuration fixtures are not bundled.

Scenario Raw JS Gzip JS Protobuf Modules
Node provider only 57.4 KiB 17.1 KiB no
SSR helpers only (fetch client rules + serialize) 84.8 KiB 24.9 KiB yes
Node provider + SSR helpers 140.4 KiB 40.7 KiB yes
Precompute only (parsed rules) 50.5 KiB 16.7 KiB no
Node provider + precompute 74.9 KiB 23.1 KiB no

Adding SSR helpers to the provider adds 83.1 KiB raw / 23.6 KiB gzip in this build. This includes fetching, parsing, and serialization, not just the decoder. SSR helpers fetch rules for the browser, not for configuring that provider (which uses its existing JSON API).

Adding precomputation to the provider adds 17.5 KiB raw / 6.0 KiB gzip in this build. Both precompute scenarios import precomputeConfiguration from /rules-based and receive already-parsed rules from the test harness; they do not fetch, parse, or serialize configuration.

These are bundler sizes, not npm install sizes, unbundled Node require/import costs, payload sizes, or latency. Gzip compares compressed artifacts. Deltas compare scenarios in this build, not changes against the base branch. The browser scenarios include OpenFeature and a different harness, so their sizes are not directly comparable.

Dependency checks passed: no Protobuf runtime or generated schema modules in the provider-only or either precompute bundle; present in both SSR bundles (checked using Webpack module statistics).

This report shows current PR artifact sizes only; it does not compare against the base branch.

@typotter
typotter force-pushed the dependent-flags-precomputed-browser-consumer branch from cd90482 to 83fdfa7 Compare November 6, 2026 20:43
@typotter
typotter changed the base branch from dependent-flags-precomputed-web to dependent-flags-node-precompute November 6, 2026 20:44
typotter and others added 2 commits November 6, 2026 21:39
Negotiate dependent precomputed assignments through the request URL, fail malformed dependency graphs closed, and carry dependency telemetry through internal metadata without changing the public API.

Environment: Datadog workspace

Co-Authored-By: Codex GPT-5 <noreply@localhost>
Pin the shared fixtures to the corrected dependency failure contract: GENERAL at the requested root while preserving prerequisite-specific errors.

Environment: Datadog workspace

Co-Authored-By: Codex GPT-5 <noreply@localhost>
@typotter
typotter force-pushed the dependent-flags-precomputed-browser-consumer branch from 83fdfa7 to e717ad4 Compare November 6, 2026 21:39

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.

3 participants