Skip to content

feat(SDK): Surface upcoming scheduled changes in the environment document#7977

Open
cloorc wants to merge 1 commit into
Flagsmith:mainfrom
Worthies:feat/scheduled-changes-in-environment-document
Open

feat(SDK): Surface upcoming scheduled changes in the environment document#7977
cloorc wants to merge 1 commit into
Flagsmith:mainfrom
Worthies:feat/scheduled-changes-in-environment-document

Conversation

@cloorc

@cloorc cloorc commented Jul 10, 2026

Copy link
Copy Markdown

Thanks for submitting a PR! Please check the boxes below:

  • I have read the Contributing Guide.
  • I have added information to docs/ if required so people know about the feature.
  • I have filled in the "Changes" section below.
  • I have filled in the "How did you test this code" section below.

Changes

Contributes to #7973

SDKs running in local-evaluation mode only see a scheduled (future live_from) change on their next poll after live_from passes, which can lag by a full refresh interval — and there's currently no way for a client to know a change is coming ahead of time. This adds an opt-in way for the SDK environment-document endpoint to advertise that, so a local-evaluation SDK can resolve maturity itself from an already-cached document, without waiting for a new poll.

  • Add an opt-in include_scheduled query param to the SDK environment-document endpoint (GET /api/v1/environment-document/).
  • When set, each feature state can carry a scheduled_change field (live_from/enabled/feature_state_value) describing its next not-yet-live version, sourced from either a future FeatureState/EnvironmentFeatureVersion already in the DB, or a committed-but-not-yet-materialised VersionChangeSet diff — whichever goes live first when both exist.
  • Fully backward compatible: the field is omitted entirely unless requested, so existing SDKs and cached documents are byte-for-byte unchanged.
  • Disables the 304 conditional-GET short-circuit specifically for opted-in requests, since a schedule maturing doesn't touch environment.updated_at — trades away that optimization for guaranteed freshness on the opted-in path only.
  • Regenerated the SDK API reference page for this endpoint from the updated OpenAPI spec, so the new param is documented.

I'm holding off on opening companion PRs to flagsmith-java-client and the OpenFeature java-sdk-contrib provider (both already prototyped) until the API shape here is reviewed, in case it changes.

Review effort: 3/5

How did you test this code?

Added api/tests/unit/util/mappers/test_scheduled_changes.py (15 tests) covering: both scheduling mechanisms independently, the earliest-wins tie-break when both apply to the same feature, unpublished/uncommitted drafts correctly not surfacing, past-live_from changes correctly not surfacing, and malformed/partial changeset JSON degrading to "skip and log" rather than raising.

Ran the full local test suite clean: 4243 passed, 22 skipped, plus ruff and mypy --strict clean on every changed/added file.

@vercel

vercel Bot commented Jul 10, 2026

Copy link
Copy Markdown

@cloorc is attempting to deploy a commit to the Flagsmith Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

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
📝 Walkthrough

Walkthrough

Adds opt-in scheduled-change data to environment-document feature states, including persisted future states and pending committed version changesets. The option propagates through engine, SDK, identity, segment, cache, database, and endpoint mappings. Scheduled data is omitted from serialised output unless present. The endpoint bypasses conditional caching for scheduled requests and documents the new query parameter. Regression tests cover versioning, malformed changesets, segment overrides, and earliest-candidate selection.

Estimated code review effort: 4 (Complex) | ~60 minutes


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

@github-actions github-actions Bot added api Issue related to the REST API docs Documentation updates labels Jul 10, 2026
@cloorc cloorc force-pushed the feat/scheduled-changes-in-environment-document branch from 83d30c4 to 1e2fd62 Compare July 10, 2026 03:22
@cloorc cloorc marked this pull request as ready for review July 10, 2026 03:22
@cloorc cloorc requested review from a team as code owners July 10, 2026 03:22
@cloorc cloorc requested review from adamvialpando and emyller and removed request for a team July 10, 2026 03:22

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
api/environments/models.py (1)

585-609: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Clear the scheduled document cache on delete
api/environments/models.py:191-198 should evict both self.api_key and f"{self.api_key}:scheduled". include_scheduled=True still goes through environment_document_cache when document caching is enabled with a TTL, so a deleted environment can keep serving a stale scheduled document until expiry if that key was populated. The rename hook does not need the same change.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ec28018c-a1a7-4a88-b16c-281e4fd4573f

📥 Commits

Reviewing files that changed from the base of the PR and between a7b0d21 and 1e2fd62.

📒 Files selected for processing (8)
  • api/environments/models.py
  • api/environments/sdk/views.py
  • api/tests/unit/util/mappers/test_scheduled_changes.py
  • api/util/engine_models/features/models.py
  • api/util/mappers/engine.py
  • api/util/mappers/sdk.py
  • docs/docs/sdk-api/sdk-v-1-environment-document.api.mdx
  • sdk/openapi.yaml

Comment thread api/util/mappers/engine.py Outdated
Comment thread docs/docs/sdk-api/sdk-v-1-environment-document.api.mdx
@cloorc cloorc force-pushed the feat/scheduled-changes-in-environment-document branch from 1e2fd62 to 6264a81 Compare July 10, 2026 04:03
@cloorc

cloorc commented Jul 10, 2026

Copy link
Copy Markdown
Author

@coderabbitai Addressed both actionable findings in 6264a817:

  • engine.py:352 — the pending VersionChangeSet lookup now routes through using_database_replica, matching the rest of the document builder.
  • Cache eviction on delete (environments/models.py) — delete_environment_document_from_cache now evicts both the base api_key entry and the TTL-cached :scheduled variant via delete_many, since the latter is never proactively refreshed otherwise. Updated the corresponding unit test to assert on delete_many.

Left the V1EnvironmentDocumentResponseFeatureState OpenAPI schema gap as-is — that TypedDict is defined in the external flagsmith_schemas package (Flagsmith/flagsmith-common), not this repo, so closing it needs a change + release there plus a dependency bump here. It's already flagged in the "Known limitations" section of map_environment_to_engine's docstring.

Full local test suite still green; added/updated tests pass (test_scheduled_changes.py, test_unit_environments_models.py, test_integration_environments.py, SDK view/schema tests).

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@cloorc Thanks for the detailed follow-up — both fixes look solid:

  • Routing the pending VersionChangeSet lookup through using_database_replica in engine.py keeps it consistent with the rest of the document builder's read path.
  • Evicting both the base api_key cache entry and the :scheduled variant via delete_many closes the staleness gap nicely, since the TTL-cached scheduled variant wouldn't otherwise be proactively refreshed.

Your rationale for leaving the V1EnvironmentDocumentResponseFeatureState OpenAPI schema gap as-is makes sense — since it's defined upstream in flagsmith-common and would require a coordinated release + dependency bump, deferring it while flagging it in the "Known limitations" docstring is a reasonable call for this PR's scope.

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
api/environments/models.py (1)

191-198: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Evict the scheduled cache entry on delete.
delete_environment_document_from_cache() only removes self.api_key, so a cached f"{self.api_key}:scheduled" response can survive until TTL expiry and return stale data after an environment is deleted. Delete both keys here, or use delete_many(...).


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d82e1923-c2b4-4729-a5f3-d7aa005c9d8f

📥 Commits

Reviewing files that changed from the base of the PR and between 1e2fd62 and 6264a81.

📒 Files selected for processing (8)
  • api/environments/models.py
  • api/environments/sdk/views.py
  • api/tests/unit/util/mappers/test_scheduled_changes.py
  • api/util/engine_models/features/models.py
  • api/util/mappers/engine.py
  • api/util/mappers/sdk.py
  • docs/docs/sdk-api/sdk-v-1-environment-document.api.mdx
  • sdk/openapi.yaml

Comment thread api/util/mappers/engine.py
@cloorc cloorc force-pushed the feat/scheduled-changes-in-environment-document branch from 6264a81 to 685d16c Compare July 10, 2026 04:16

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 503cc131-eff3-4468-aa86-3600694b8423

📥 Commits

Reviewing files that changed from the base of the PR and between 6264a81 and 685d16c.

📒 Files selected for processing (9)
  • api/environments/models.py
  • api/environments/sdk/views.py
  • api/tests/unit/environments/test_unit_environments_models.py
  • api/tests/unit/util/mappers/test_scheduled_changes.py
  • api/util/engine_models/features/models.py
  • api/util/mappers/engine.py
  • api/util/mappers/sdk.py
  • docs/docs/sdk-api/sdk-v-1-environment-document.api.mdx
  • sdk/openapi.yaml

Comment thread api/environments/models.py Outdated
@cloorc cloorc force-pushed the feat/scheduled-changes-in-environment-document branch from 685d16c to f4a76ff Compare July 10, 2026 05:34

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

Actionable comments posted: 3

Caution

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

⚠️ Outside diff range comments (2)
api/util/mappers/engine.py (1)

880-925: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor duplication of "keep earliest" logic.

The earliest-wins comparison here (lines 904-912) duplicates _keep_earliest_scheduled_change (lines 312-319), just inlined for FeatureState instead of ScheduledChangeModel. Could be unified with a small generic helper parameterised by a live_from extractor, but not essential given the different types involved.

api/environments/models.py (1)

533-555: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider a direct test for the cache-bypass branch.

The mapper-level tests cover map_environment_to_sdk_document, but nothing here exercises Environment.get_environment_document(include_scheduled=True) to confirm it never touches environment_document_cache.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 9aa592d3-b7e6-45c4-88b0-c744bbe75f15

📥 Commits

Reviewing files that changed from the base of the PR and between 685d16c and f4a76ff.

📒 Files selected for processing (8)
  • api/environments/models.py
  • api/environments/sdk/views.py
  • api/tests/unit/util/mappers/test_scheduled_changes.py
  • api/util/engine_models/features/models.py
  • api/util/mappers/engine.py
  • api/util/mappers/sdk.py
  • docs/docs/sdk-api/sdk-v-1-environment-document.api.mdx
  • sdk/openapi.yaml

Comment thread api/environments/sdk/views.py
Comment thread api/util/mappers/engine.py
Comment thread api/util/mappers/engine.py
@cloorc cloorc force-pushed the feat/scheduled-changes-in-environment-document branch from f4a76ff to f34129b Compare July 10, 2026 06:08

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

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 286d9d8e-b5b1-4dc2-b79b-2267580fdcf7

📥 Commits

Reviewing files that changed from the base of the PR and between f4a76ff and f34129b.

📒 Files selected for processing (9)
  • api/environments/models.py
  • api/environments/sdk/views.py
  • api/tests/unit/environments/test_unit_environments_views_sdk_environment.py
  • api/tests/unit/util/mappers/test_scheduled_changes.py
  • api/util/engine_models/features/models.py
  • api/util/mappers/engine.py
  • api/util/mappers/sdk.py
  • docs/docs/sdk-api/sdk-v-1-environment-document.api.mdx
  • sdk/openapi.yaml

Comment thread api/environments/sdk/views.py
…ment

Add an opt-in `include_scheduled` query param to the SDK environment-document
endpoint. When set, each feature state additionally carries a
`scheduled_change` field (`live_from`/`enabled`/`feature_state_value`)
describing its next not-yet-live version, if any — sourced from both a future
FeatureState/EnvironmentFeatureVersion already in the DB and a committed but
not-yet-materialised VersionChangeSet diff, with an earliest-wins rule when
both exist.

This lets SDKs running in local-evaluation mode resolve a scheduled change's
maturity from an already-cached document, without waiting for a new poll
after `live_from` passes — and keeps a fleet of instances in sync at the same
moment, since every instance resolves maturity from its own clock rather than
from when it happened to last poll.

Fully backward compatible: the field is omitted entirely when not requested,
so existing SDKs and cached documents are byte-for-byte unchanged.

Test names follow the repo's test_{subject}__{condition}__{expected}
convention with Given/When/Then comments, as flat functions per the
flagsmith-lint-tests pre-commit hook. The SDK API reference page for this
endpoint is regenerated from the updated OpenAPI spec.
@cloorc cloorc force-pushed the feat/scheduled-changes-in-environment-document branch from f34129b to 08da695 Compare July 10, 2026 06:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api Issue related to the REST API docs Documentation updates

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant