Skip to content

feat: precompute dependent flags in Node - #457

Open
typotter wants to merge 7 commits into
mainfrom
dependent-flags-node-precompute
Open

typotter wants to merge 7 commits into
mainfrom
dependent-flags-node-precompute

Conversation

@typotter

@typotter typotter commented Nov 5, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Tracking: FFLSDK-289

Builds dependent-flag precomputation on the precomputeConfiguration() utility merged in #448 and the evaluator failure semantics in #456.

  • keep attributes.flags limited to complete successful assignments
  • emit failed roots in sibling attributes.flagEvaluationErrors without fabricated values
  • preserve ordered dependency traces and selected-path isPresentInAssignment semantics
  • normalize root dependency failures to GENERAL while retaining prerequisite-specific diagnostics
  • fail a dependent root when a prerequisite has no assignment or cannot be serialized
  • optionally hash both top-level maps and AES-GCM-encrypt nested prerequisite keys with the plaintext root flag key
  • expose the updated packet types and document Node usage

This PR is stacked on #456. It is the producer counterpart to browser consumption in #458, the edge contract in ddoghq/dd-source#155394, and the shared corpus in DataDog/ffe-system-test-data#62.

Validation

  • core: 1,362 passed, 6 skipped
  • Node server: 737 passed
  • focused dependent precompute/evaluator/crypto/wire validation: 157 passed
  • core and Node typechecks passed
  • core CJS/ESM/legacy bundle and Node CJS/ESM/declaration builds passed
  • Node clean-install suites passed with and without current OpenFeature dependencies
  • Biome lint and Prettier checks passed
  • entrypoint drift and third-party license validation passed
  • git diff --check passed

Produce successful assignments and sibling evaluation errors with ordered dependency traces, including root-scoped encrypted parent references for obfuscated packets.

Environment: Datadog workspace

Co-Authored-By: Codex GPT-5 <noreply@localhost>
@github-actions

github-actions Bot commented Nov 5, 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 108.8 KiB 39.1 KiB no
DatadogCoreProvider + fetchRulesConfiguration core-provider.html 15 209.2 KiB 66.8 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.7 KiB 0 B 1.5 KiB 0 B
exposure logging hook tracking-exposure.html 10 72.6 KiB +69.9 KiB 26.8 KiB +25.3 KiB
evaluation logging hook tracking-evaluation.html 11 68.4 KiB +65.7 KiB 25.4 KiB +23.9 KiB
RUM tracking hook tracking-rum.html 8 6.8 KiB +4.1 KiB 3.5 KiB +2.0 KiB
all tracking hooks tracking-all.html 13 77.3 KiB +74.7 KiB 29.0 KiB +27.5 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.

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

Serialization failures can leave prerequisite exposures marked as committed and dependent evaluations reported as successful even when the corresponding assignments are missing.

2 open issues
What changed in this PR

Extends local Node precomputation to produce dependent-flag packets, building on the shared core utility from #448.

Changes:

  • Separates successful assignments from evaluation failures and preserves ordered dependency traces.
  • Adds optional top-level key hashing and root-bound AES-GCM encryption for prerequisite keys.
  • Updates packet validation, tests, Node declarations, and usage documentation.
File Description
yarn.lock Locks the new cipher dependency.
packages/​node-server/​rules-based.d.ts Exposes updated packet types and precomputation options.
packages/​node-server/​README.md Documents dependency failures and obfuscated output.
packages/​core/​src/​configuration/​wire-validation.ts Validates error maps and dependency traces.
packages/​core/​src/​configuration/​precompute-configuration.ts Produces dependency-aware assignments, errors, and encoded keys.
packages/​core/​src/​configuration/​precompute-configuration.test.ts Adds failure, trace, and encryption-vector coverage.
packages/​core/​src/​configuration/​dependency-key-encryption.ts Adds authenticated prerequisite-key encryption and decryption.
packages/​core/​src/​configuration/​configuration.ts Defines dependency-trace and evaluation-error packet types.
packages/​core/​README.md Describes the updated precomputation contract and encryption format.
packages/​core/​package.json Adds @noble/ciphers.
LICENSE-3rdparty.csv Records the cipher dependency’s license attribution.

Review effort: Balanced

Use a small base64url codec so precomputation does not couple consumers to Protobuf wire helpers.

Environment: Datadog workspace

Co-Authored-By: Codex GPT-5 <noreply@localhost>
Reconcile successful roots against final portable assignments, fail roots that reference omitted prerequisites independent of iteration order, and roll back selected exposure markers when serialization fails.

Environment: Datadog workspace

Co-Authored-By: Codex GPT-5 <noreply@localhost>

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

Active issues expose plaintext prerequisite keys in obfuscated packets and leave dependency diagnostics order-dependent, potentially referencing removed assignments.

1 open issue
1 previously missed issue

In code that hasn't changed since last review

Medium severity Dependency diagnostics remain stale after assignment reconciliation

packages/​core/​src/​configuration/​precompute-configuration.ts:327

For a root → parent → nonportable grandparent chain, processing flags in [parent, root] order demotes the root first, leaving its parent entry marked successful even after the parent assignment is removed. The root's error trace is never revisited, so prerequisite diagnostics depend on flag order and can reference unavailable assignments. Normalize all error traces against the final assignment set after reconciliation, and add a regression covering both orders.

2 resolved since last review

Review effort: Balanced

Normalize transitive dependency failures against the final assignment set and prevent plaintext flag-key leakage from obfuscated diagnostics.

Environment: Datadog workspace

Co-Authored-By: Codex GPT-5 <noreply@localhost>

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

Deeply nested prerequisite JSON can cause cloning to drop its evaluation trace, so reconciliation misses the prerequisite’s PARSE_ERROR and incorrectly reports the child as successful.

No open issues 🎉

1 previously missed issue

In code that hasn't changed since last review

High severity Preserve dependency status when cloning deeply nested JSON fails

packages/​core/​src/​configuration/​precompute-configuration.ts:276

A deeply nested JSON prerequisite can overflow cloneFlagValue(), causing DependencyEvaluationSession.recordEvaluation() to drop its trace entry, while its own cloneJson() later produces PARSE_ERROR. Because this conversion only maps recorded evaluations, reconciliation misses that prerequisite and still emits the child as successful. Preserve a value-free key/status record when dependency-value cloning fails, and add a deeply nested JSON regression.

1 resolved since last review

Review effort: Balanced

Refresh on current main and evaluator semantics, preserve value-free dependency failures, and cover encryption and wire validation boundaries.

Co-authored-by: tyler.potter <tyler.potter@datadoghq.com>
@typotter
typotter marked this pull request as ready for review November 6, 2026 16:49
@typotter
typotter requested a review from a team as a code owner November 6, 2026 16:49
@typotter
typotter requested a balanced review from Copilot November 6, 2026 16:58

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

Successful dependency records with null error fields are incorrectly rejected, causing otherwise valid flag evaluations to return the caller’s default with PARSE_ERROR.

1 open issue

Review effort: Balanced

Comment on lines +157 to +158
value.errorCode === undefined &&
value.errorMessage === undefined
typotter and others added 2 commits November 6, 2026 14:43
Reject ERROR assignments from the success map and accept null optional dependency error fields as absent.

Environment: Datadog workspace

Co-Authored-By: Codex GPT-5 <noreply@localhost>
Preserve authored trace order while replacing repeated linear scans with rollback-safe key sets for evaluations and exposure candidates.

Environment: Datadog workspace

Co-Authored-By: Codex GPT-5 <noreply@localhost>

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.

2 participants