Skip to content

feat(flags): serve the typed v3 record on /flags?v=3 behind a service setting - #107732

Merged
trunk-io[bot] merged 6 commits into
masterfrom
feat/flag-rules-v2-flags-v3-route
Oct 1, 2026
Merged

trunk-io[bot] merged 6 commits into
masterfrom
feat/flag-rules-v2-flags-v3-route

Conversation

@andehen

@andehen andehen commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Problem

Feature flag rules v2 ships a new remote response protocol, /flags?v=3, whose record carries one typed value instead of enabled and variant, the evaluator's own reason code, and the terminal rule. Until now the Rust service answered every v >= 2 on /flags with the v2 record, so SDKs that will adopt v3 had only harness fixtures to develop against.

Some clients already send v=3 to /flags and read the v2 record they get today. So the v3 arm ships behind a service setting that is off by default: deploying this PR changes nothing on the wire.

Changes

  • Setting. FLAGS_V3_RESPONSE_ENABLED (Config::flags_v3_response_enabled), default false, one switch for the whole service. Off: /flags with v >= 3 returns the v2 record, exactly as master does. On: it returns the v3 record described below. The /decide arms, v=2, v=1, and an absent or unparsable v behave the same in both states. The body log follows the shape actually served, and the canonical log line gains response_format (FlagsV1, FlagsV2, FlagsV3, DecideV1, DecideV2), so the served shape is visible per request.
  • One internal result, two projections. get_match_v2 keeps its typed outcome on FeatureFlagMatch (evaluation_v2, an owned EvaluationDetail, because the evaluator's Evaluation borrows from the parsed config). FlagDetails carries the config format plus that outcome in a serde-skipped field. The v3 record is built from the same FlagsResponse as the v2 record, so nothing is re-evaluated. The v2 projection ignores both fields.
  • The v3 record (FlagDetailsV3) has key, value, failed (only when true), reason, metadata and the unchanged optional conditions.
    • For a v2 flag, value is the configured value as raw JSON: boolean, string, number or object, never a JSON-encoded string. It is null for a null default or a failed record. reason.code is targeting_match, rollout_miss or no_rule_match, and condition_index is the rule index. metadata adds config_version: 2, plus rule_type and rule_id when a rule terminated. metadata.payload is always null, because the value travels only in value; the v2 record still carries the JSON-encoded payload for number and object flags. A failed v2 flag reports error with value: null and no rule fields.
    • For a v1 flag the record keeps the existing v1 reason and error codes. value is the variant key or the boolean, metadata.config_version is 1, and payload is unchanged. variant_key carries the matched variant key verbatim; the API has never restricted v1 variant keys, so dots, slashes and spaces pass through.
  • Contract pin. The vendored contract subset moves to harness release 1.13.1 (contract package 2.3.1). Next to the evaluation corpora master already vendors, it adds the v3 producer schema (1.0.1), its presence companion, the presence matrix and the wire response fixtures. Tests validate every v3 record they produce against both schemas, the map-key rule and the seed scan, and replay the 150 fixture cases through the same validator. jsonschema is a new dev-dependency (no default features).
  • Master merged, including the typed value evaluator from feat(flags): accept and evaluate string, number and object rules v2 values #107733. The two conflicts were in flag_matching.rs, where both sides are kept, and in the vendored contract directory, which is now the single 1.13.1 copy.

Example with the setting on: a v2 string flag whose first rule matched.

{
  "key": "checkout-layout",
  "value": "compact",
  "reason": { "code": "targeting_match", "condition_index": 0, "description": "Matched rule 1" },
  "metadata": {
    "id": 123, "version": 4, "config_version": 2, "description": null, "payload": null,
    "has_experiment": false, "rule_type": "targeted_release", "rule_id": "6bb788aa-df76-4a77-8bed-9f247e279ccd"
  }
}

Out of scope: experiment context, additive metadata on the v2 record, any /decide change, a per-team allowlist, and SDK support. The protocol version selects the shape; it never proves that a client understands it.

Turning it on

  • Locally: FLAGS_V3_RESPONSE_ENABLED=true bin/start-rust-service feature-flags, or add FLAGS_V3_RESPONSE_ENABLED: 'true' to the feature-flags service environment in docker-compose.dev.yml.
  • Production: a separate deployment configuration change, made once the clients that send v=3 today have moved off it. It is not part of this PR.

Compatibility

Every maintained SDK at HEAD requests /flags/?v=2 (Python, browser and Node JS, Android, iOS, Flutter through the native SDKs, Go, .NET, PHP, Ruby, Rust), so no SDK changes shape at any setting. The service used to answer any v >= 2 with the v2 record, so clients that already send v=3 to /flags are the only ones the setting affects: tens of thousands of requests a day, almost all from one hand-written Node caller. While the setting is off they keep receiving the v2 record. Once it is on, a caller that reads enabled must read value instead.

Rollback

  • Turn the setting off or unset it. No code deploy; v >= 3 returns the v2 record again.
  • Or revert this PR. With the setting off, the revert changes nothing on the wire.

How did you test this code?

  • Setting off (default). New v3_and_above_serve_the_v2_record_while_the_setting_is_off serves v1 flags, v2 boolean flags and a v2 string flag. POST /flags with v=3 or v=99 returns the same body as v=2 (same request ID, evaluatedAt dropped), with both config=false and config=true. GET /flags?v=3 matches GET /flags?v=2. In test_flags.rs, it_get_new_response_when_version_is_2_or_more is back to master's v=2/v=3 cases. test_rules_v2_v1_contract, test_legacy_decide_formats and test_config_format pass unchanged.
  • Setting on.
    • The existing v3 tests pass unchanged: nine v1 and v2 boolean flags through v=3 and v=99, flag_keys, detailed_analysis, the v=2/v=1/v=abc/decide shapes, and GET.
    • New v3_carries_every_typed_corpus_value_through_the_request_path serves the 38 successful cases of the typed value corpus (string, number, object, null defaults) as flags over /flags. Against the corpus, it checks value, reason, rule fields and payload: null at v=3, and enabled, variant and the payload at v=2.
    • test_rules_v2_evaluation.rs projects 163 corpus cases (114 boolean, 49 typed) through the matcher into schema-valid v3 records, and the 150 wire fixtures agree with the validator.
  • Full feature-flags suite, locally. Every integration binary passes. In the lib tests, 1854 pass and 1 fails: test_realtime_provider_queries_cohort_membership_table needs a table this machine lacks, and it fails without the change too. Strict Clippy, cargo fmt --check, git diff --check and hogli ci:preflight --strict are clean.
  • Manual check on the local stack with this branch's binary and v2 boolean, string, number and object flags:
    • Setting off: v=3 returned the same body as v=2 and the canonical log said response_format=FlagsV2.
    • Setting on: v=3 returned true, "compact", 12.5 and the configured object with payload: null, and the log said FlagsV3.

Release status

  • No feature flag controls this change
  • This change is behind a feature flag and is not available to users
  • This change makes a previously flagged feature available to everyone

The switch is the service setting above, not a PostHog feature flag.

Automatic notifications

  • Publish to changelog?

🤖 Generated with Claude Code

On /flags, v >= 3 now returns the v3 envelope: one typed `value` per
record instead of `enabled` and `variant`, the evaluator's reason code
and terminal rule for a v2 flag, and `config_version` in the metadata.
v=2 keeps today's record byte for byte, and the /decide arms are
untouched. Both shapes are built from the same internal result: the v2
evaluator's typed outcome now rides on FeatureFlagMatch and FlagDetails
in fields the v2 wire never serializes.

The vendored contract package 2.2.0 subset gains the v3 producer schema,
its presence companion, the presence matrix and the wire fixtures; tests
validate every produced record against them.
@trunk-io

trunk-io Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

😎 Merged successfully - details.

@coderabbitai

coderabbitai Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: PostHog/posthog/.coderabbit.yaml

Review profile: QUIET

Plan: Enterprise

Run ID: eccff753-5bbf-434f-b005-374cf5657655

📥 Commits

Reviewing files that changed from the base of the PR and between 1ac8444 and 4940e08.

⛔ Files ignored due to path filters (1)
  • rust/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (32)
  • rust/feature-flags/Cargo.toml
  • rust/feature-flags/src/api/batch_flag_evaluation.rs
  • rust/feature-flags/src/api/endpoint.rs
  • rust/feature-flags/src/api/types.rs
  • rust/feature-flags/src/config.rs
  • rust/feature-flags/src/flags/evaluate_v2.rs
  • rust/feature-flags/src/flags/flag_matching.rs
  • rust/feature-flags/src/flags/test_flag_matching.rs
  • rust/feature-flags/src/handler/body_logger.rs
  • rust/feature-flags/src/handler/canonical_log.rs
  • rust/feature-flags/src/handler/tests.rs
  • rust/feature-flags/tests/fixtures/rules_v2_evaluation/2.3.1/README.md
  • rust/feature-flags/tests/fixtures/rules_v2_evaluation/2.3.1/SHA256SUMS
  • rust/feature-flags/tests/fixtures/rules_v2_evaluation/2.3.1/SOURCE.json
  • rust/feature-flags/tests/fixtures/rules_v2_evaluation/2.3.1/corpus/v2_boolean_evaluation.json
  • rust/feature-flags/tests/fixtures/rules_v2_evaluation/2.3.1/corpus/v2_value_evaluation.json
  • rust/feature-flags/tests/fixtures/rules_v2_evaluation/2.3.1/fixtures/wire/responses.json
  • rust/feature-flags/tests/fixtures/rules_v2_evaluation/2.3.1/manifest.json
  • rust/feature-flags/tests/fixtures/rules_v2_evaluation/2.3.1/registries/literals.json
  • rust/feature-flags/tests/fixtures/rules_v2_evaluation/2.3.1/rules/response_presence.json
  • rust/feature-flags/tests/fixtures/rules_v2_evaluation/2.3.1/schemas/config.schema.json
  • rust/feature-flags/tests/fixtures/rules_v2_evaluation/2.3.1/schemas/flags_response_v3.schema.json
  • rust/feature-flags/tests/fixtures/rules_v2_evaluation/2.3.1/schemas/flags_response_v3_presence.schema.json
  • rust/feature-flags/tests/fixtures/rules_v2_evaluation/2.3.1/schemas/v2_boolean_evaluation.schema.json
  • rust/feature-flags/tests/fixtures/rules_v2_evaluation/2.3.1/schemas/v2_value_evaluation.schema.json
  • rust/feature-flags/tests/test_flag_matching_consistency.rs
  • rust/feature-flags/tests/test_flags.rs
  • rust/feature-flags/tests/test_flags_v3.rs
  • rust/feature-flags/tests/test_rules_v2_evaluation.rs
  • rust/feature-flags/tests/test_rules_v2_evaluation/corpus.rs
  • rust/feature-flags/tests/test_rules_v2_evaluation/wire.rs
  • rust/feature-flags/tests/test_rules_v2_v1_contract.rs
💤 Files with no reviewable changes (6)
  • rust/feature-flags/tests/fixtures/rules_v2_evaluation/2.3.1/corpus/v2_value_evaluation.json
  • rust/feature-flags/tests/fixtures/rules_v2_evaluation/2.3.1/corpus/v2_boolean_evaluation.json
  • rust/feature-flags/tests/fixtures/rules_v2_evaluation/2.3.1/registries/literals.json
  • rust/feature-flags/tests/fixtures/rules_v2_evaluation/2.3.1/schemas/v2_boolean_evaluation.schema.json
  • rust/feature-flags/tests/fixtures/rules_v2_evaluation/2.3.1/schemas/v2_value_evaluation.schema.json
  • rust/feature-flags/tests/fixtures/rules_v2_evaluation/2.3.1/schemas/config.schema.json

Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 3 remain after this review.


📝 Walkthrough

Walkthrough

The API now serves V3 flag records for enabled non-Decide requests at version 3 or higher. The setting defaults to disabled, and Decide response mapping remains unchanged. V3 conversion uses retained evaluation data. Body and canonical logs record the served response format. The change also adds wire schemas, validation helpers, fixtures, and integration tests for response fields, typed values, filtering, and version compatibility.

Priority: ⬇️ Low

Merge Risk: ⚪ Minimal · up to 4940e

The typed v3 /flags response is behind a setting that is off by default, so existing v2, legacy, and /decide callers keep their current behavior. No unresolved merge-blocking issue remains at the current head.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 4940e

The new response is disabled by default and preserves the reviewed project authorization and filtering controls. A conditional failure-status mismatch could undermine failed-record handling. Production rollout and client fallback behavior remain unverified.

Retained concerns

  • Low · reliability · inferred: If precomputed dependency metadata marks a supported V2 flag as missing a dependency, evaluation returns an Ok non-match and constructs a record with failed=false. V3 then substitutes value=null and reason=error while omitting failed, violating the supplied failed-record contract. The base response instead exposes false with the MissingDependency reason. This creates a conditional failure-containment risk: strict consumers could reject the response rather than isolate the affected record. Production reachability of this metadata state and downstream recovery behavior remain unverified.
Security review details

Security Blast Radius

  • inferred — The changed wire representation can affect every project served by an enabled instance whose request selects non-Decide version 3 or higher, plus its opted-in response logs. The reviewed selector operates on already team-scoped results; no expansion to another tenant, credential authority or privileged service operation was demonstrated.

Trust Boundaries and Controls

  • observed — Attacker-controlled version selection changes response shape after existing authorization and evaluation controls. X-Original-Endpoint already determined Decide classification in the base; the PR adds V3 exclusion to that classification but does not introduce the header-derived identity. Proxy sanitization of the marker was not established.

Resilience and Maintainability Implications

  • inferred — The conditional missing-evaluation mismatch concerns failure isolation, not a demonstrated authorization bypass. Internal dependency evaluation still records a false non-match, whereas V3 exposes null/error without the required failed marker. Consumer handling and production metadata reachability must be established before attributing a security consequence.
🚥 Pre-merge checks | ✅ 1
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The description is detailed and standalone. It explains the problem, user-visible changes, compatibility, rollout, rollback, testing, and release status. Minor template items are missing, including th…
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot 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.

🧹 Nitpick comments (1)
rust/feature-flags/src/api/types.rs (1)

557-564: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Make the description match depend on code, not on evaluation.

The (None, _) arm tests evaluation. The rollout_miss guard tests the code string. Both values come from one match block just above. The logic works now. It depends on string comparison, so it will break without warning if someone renames a code. Compute description inside the first match so that each arm returns all four values.

♻️ Proposed refactor
-                let (value, code, rule) = match evaluation {
-                    Some(Evaluation::TargetingMatch { value, rule }) => {
-                        (Value::from(value), "targeting_match", Some(rule))
-                    }
-                    Some(Evaluation::RolloutMiss { value, rule }) => {
-                        (Value::from(value), "rollout_miss", Some(rule))
-                    }
-                    Some(Evaluation::NoRuleMatch { value }) => {
-                        (Value::from(value), "no_rule_match", None)
-                    }
-                    None => (Value::Null, "error", None),
-                };
-                let description = match (evaluation, rule) { ... };
+                let (value, code, rule, description) = match evaluation {
+                    Some(Evaluation::TargetingMatch { value, rule }) => (
+                        Value::from(value), "targeting_match", Some(rule),
+                        Some(format!("Matched rule {}", rule.index + 1)),
+                    ),
+                    Some(Evaluation::RolloutMiss { value, rule }) => (
+                        Value::from(value), "rollout_miss", Some(rule),
+                        Some(format!("Rule {} rollout miss", rule.index + 1)),
+                    ),
+                    Some(Evaluation::NoRuleMatch { value }) => (
+                        Value::from(value), "no_rule_match", None,
+                        Some("No rule matched".to_string()),
+                    ),
+                    None => (Value::Null, "error", None, flag.reason.description),
+                };

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: PostHog/posthog/.coderabbit.yaml

Review profile: QUIET

Plan: Enterprise

Run ID: db268e23-d403-452e-b550-0a0b686ed0c1

📥 Commits

Reviewing files that changed from the base of the PR and between 28c8b75 and e618bd6.

⛔ Files ignored due to path filters (1)
  • rust/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (18)
  • rust/feature-flags/Cargo.toml
  • rust/feature-flags/src/api/endpoint.rs
  • rust/feature-flags/src/api/types.rs
  • rust/feature-flags/src/flags/flag_matching.rs
  • rust/feature-flags/src/flags/test_flag_matching.rs
  • rust/feature-flags/src/handler/body_logger.rs
  • rust/feature-flags/src/handler/tests.rs
  • rust/feature-flags/tests/fixtures/rules_v2_evaluation/2.2.0/SOURCE.json
  • rust/feature-flags/tests/fixtures/rules_v2_evaluation/2.2.0/fixtures/wire/responses.json
  • rust/feature-flags/tests/fixtures/rules_v2_evaluation/2.2.0/rules/response_presence.json
  • rust/feature-flags/tests/fixtures/rules_v2_evaluation/2.2.0/schemas/flags_response_v3.schema.json
  • rust/feature-flags/tests/fixtures/rules_v2_evaluation/2.2.0/schemas/flags_response_v3_presence.schema.json
  • rust/feature-flags/tests/test_flag_matching_consistency.rs
  • rust/feature-flags/tests/test_flags.rs
  • rust/feature-flags/tests/test_flags_v3.rs
  • rust/feature-flags/tests/test_rules_v2_evaluation.rs
  • rust/feature-flags/tests/test_rules_v2_evaluation/wire.rs
  • rust/feature-flags/tests/test_rules_v2_v1_contract.rs

Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 11 remain after this review.

@github-actions

Copy link
Copy Markdown
Contributor

🤖 CI report

✅ Trunk lane — non-backend lane

This PR is assigned to the non-backend lane. It does not run backend Python tests and may merge in parallel with PRs in other lanes.

@greptile-apps

greptile-apps Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Retrigger

[Medium risk] Adds a new API response format for feature flags.

The PR should not merge until v3 accepts variant keys already valid in stored v1 flags.

Reviews (1) · Last reviewed commit: "feat(flags): serve the typed v3 record o..."

Comment thread rust/feature-flags/src/api/types.rs Resolved
@trunk-io

trunk-io Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Static Badge   Static Badge   Static Badge

View Full Report ↗︎ ⋅ Docs

…t 2.2.1

The vendored v3 schema restricted metadata.variant_key to [a-zA-Z0-9_-],
but the API has never restricted v1 variant keys: stored flags carry dots,
slashes and spaces because variant keys double as configuration values.
The correction lives in the contract (PostHog/posthog-sdk-test-harness#70,
contract package 2.2.1, wire schemas 1.0.1); this pins that revision
provisionally until it is released and serves a v1 flag with the variant
key "provider/model-1.2 beta" through /flags?v=3 in the tests.

Also computes the v3 reason description in the same match as its code.
@posthog

posthog Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

🦔 PostHog Review reviewed this pull request

Found 0 must fix, 0 should fix, 1 consider.

Published 1 finding (view the review).

Resolved comments: 1 fixed

@posthog

posthog Bot commented Sep 28, 2026

Copy link
Copy Markdown
Contributor

PostHog Review alpha 🦔 If you find any issues helpful - please reply "valid", "invalid", etc., for evaluation purposes 🙏

@posthog posthog Bot 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.

PostHog Review

Found 1 consider.

Comment thread rust/feature-flags/src/api/types.rs Resolved

@haacked haacked 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.

Looks good, and nothing inline is blocking. Greptile's variant_key comment does check out, though: filters_schema.py deliberately allows keys like provider/model-1.2, which the vendored v3 pattern rejects.

Comment thread rust/feature-flags/src/api/types.rs Resolved
Comment thread rust/feature-flags/src/api/types.rs Resolved
Comment thread rust/feature-flags/src/api/endpoint.rs Resolved
Comment thread rust/feature-flags/src/api/types.rs Resolved
Comment thread rust/feature-flags/src/api/types.rs Resolved
Comment thread rust/feature-flags/src/api/types.rs Resolved
Comment thread rust/feature-flags/src/api/types.rs Resolved
The opt-in body log recorded the internal FlagsResponse before the
versioning step. For /flags?v=3 that record shows `enabled` and the v1
reason code, and it cannot show the typed `value`, the v2 reason code or
the rule fields, so a null default logged as `enabled: false`.

When the request gets the v3 record, the body logger now serializes each
filtered flag as its v3 record. The v3 routing check moves into
`serves_flags_v3`, which both `get_versioned_response` and the logger
call, so the logged shape follows the served shape. Other versions log
the internal record as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Generated-By: PostHog Desktop
Task-Id: 441c3d40-fb89-44b3-a6e1-6435d1a3a4ca

@coderabbitai coderabbitai Bot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to GitHub limitations.

⚠️ Outside diff range comments (1)

🟡 Minor · Preserve missing-dependency failures in V3 conversion. · types.rs:544-571

rust/feature-flags/src/api/types.rs:544-571
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Preserve missing-dependency failures in V3 conversion.

A reachable missing dependency produces ConfigOutcome::V2(None) with reason code missing_dependency. The conversion changes that reason to generic error and keeps failed false, so serialization omits failed. The V3 schema requires failed for both error codes, and the V2 contract defines dependency_error for unresolvable dependencies.

Suggested fix
             ConfigOutcome::V2(evaluation) => {
+                let evaluation_failed = evaluation.is_none();
+                let is_dependency_error = flag.reason.code == "missing_dependency";
                 let (value, code, rule, description) = match evaluation {
...
-                    None => (Value::Null, "error", None, flag.reason.description),
+                    None => (
+                        Value::Null,
+                        if is_dependency_error {
+                            "dependency_error"
+                        } else {
+                            "error"
+                        },
+                        None,
+                        flag.reason.description,
+                    ),
                 };
...
-                (value, reason, rule, 2, None)
+                (value, reason, rule, 2, None, flag.failed || evaluation_failed)
             }
...
-            failed: flag.failed,
+            failed,

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: PostHog/posthog/.coderabbit.yaml

Review profile: QUIET

Plan: Enterprise

Run ID: abc24e04-199d-4637-80e1-92aaf44aa38e

📥 Commits

Reviewing files that changed from the base of the PR and between a66c04e and 444cc44.

📒 Files selected for processing (3)
  • rust/feature-flags/src/api/endpoint.rs
  • rust/feature-flags/src/api/types.rs
  • rust/feature-flags/src/handler/body_logger.rs

Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 2 remain after this review.

andehen and others added 3 commits September 29, 2026 10:30
- Evaluation and RuleKind name their own wire code, value, rule and
  description, so the v3 conversion no longer compares code strings.
- The panic fallback keeps a v2 flag's config label, so its v3 record says
  config_version 2 with code error instead of posing as v1.
- Test the failed v1 record, the v2 reason descriptions,
  minimalFlagCalledEvents and supportedCompression on v=3.
- The wire seed scan follows the harness: camelCase, dash and $ spellings
  count, and only the flags map keys are exempt.
- Drop assert_presence_row, which the presence schema already covers.
- Doc and indentation fixes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…flags-v3-route

Master now evaluates string, number and object rules v2 values, and its
`Evaluation` borrows the value from the parsed config. `FeatureFlagMatch`
keeps an owned `EvaluationDetail` for the v3 record instead, and `get_match_v2`
keeps master's typed value and legacy payload alongside it. A v2 record at v3
carries its value only in `value`; `metadata.payload` stays null, as the
v3 producer schema requires.

The vendored rules v2 contract moves to harness release 1.13.1 (contract
2.3.1), which carries both the typed value corpus and the corrected v3 wire
schemas, replacing the branch's unreleased 2.2.1 pin and master's 2.3.0 pin.
…is on

`/flags` with `v >= 3` returns the v2 record, exactly as master does, unless
the new service setting `FLAGS_V3_RESPONSE_ENABLED` is true. It defaults to
false: some clients already send `v=3` and read the v2 record, so the arm
ships dark and is turned on once they have moved. The `/decide` arms, `v=2`,
`v=1` and an unparsable `v` do not depend on it, and the body log follows the
shape actually served. The canonical log line records that shape as
`response_format`.

Tests: the v3 suites run with the setting on, and a new test serves every
successful typed value corpus case over `/flags`. With the setting off,
`v=3` and `v=99` match `v=2` for v1, v2 boolean and v2 string flags, and
`it_get_new_response_when_version_is_2_or_more` is back to master's cases.
@andehen andehen changed the title feat(flags): serve the typed v3 record on /flags?v=3 feat(flags): serve the typed v3 record on /flags?v=3 behind a service setting Oct 1, 2026
@andehen
andehen requested a review from haacked October 1, 2026 11:33
@trunk-io
trunk-io Bot merged commit 6235b96 into master Oct 1, 2026
290 checks passed
@trunk-io
trunk-io Bot deleted the feat/flag-rules-v2-flags-v3-route branch October 1, 2026 15:47
@deployment-status-posthog

deployment-status-posthog Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Deploy status

Environment Status Deployed At Workflow
dev ✅ Deployed 2026-10-01 16:11 UTC Run
prod-us ✅ Deployed 2026-10-01 16:26 UTC Run
prod-eu ✅ Deployed 2026-10-01 16:25 UTC Run

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

2 participants