Skip to content

feat: serve release with unresolved configs - #379

Open
yuvrajjsingh0 wants to merge 1 commit into
mainfrom
feat/serve-release-extended
Open

feat: serve release with unresolved configs#379
yuvrajjsingh0 wants to merge 1 commit into
mainfrom
feat/serve-release-extended

Conversation

@yuvrajjsingh0

@yuvrajjsingh0 yuvrajjsingh0 commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Release-serving endpoints now support an optional extended parameter to return unresolved configuration, version details, experiments, and targeting data.
    • Extended responses include file size information where available.
    • The system gracefully falls back to resolved releases if extended data cannot be retrieved.
  • Bug Fixes

    • Configuration and release changes now refresh extended release data to keep responses current.
    • Caching improves extended-data performance while supporting expiration and invalidation.

@semanticdiff-com

semanticdiff-com Bot commented Jul 31, 2026

Copy link
Copy Markdown

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Release serving now accepts extended=true and can return unresolved configuration and experiment data. The server retrieves this data from Superposition, caches it in Redis, degrades gracefully on retrieval failure, and invalidates cached data after related mutations. API documentation and schemas describe the new response fields.

Changes

Extended release properties

Layer / File(s) Summary
Release contract and response shape
smithy/models/release.smithy, airborne_server/src/release/types.rs, airborne_docs/openapi/airborne.openapi.json, airborne_docs/docs/api-reference/overview.md
The release APIs define the extended query parameter and optional unresolved_properties response data. The schemas document configuration, experiments, versions, timestamps, filtering, and file size.
Unresolved-properties retrieval and caching
airborne_server/src/release/utils.rs
The server fetches filtered configuration and experiment data concurrently, converts the results, and uses Redis with a seven-day TTL.
Mutation-triggered cache invalidation
airborne_server/src/release.rs, airborne_server/src/organisation/application/dimension.rs, airborne_server/src/organisation/application/dimension/cohort.rs, airborne_server/src/organisation/application/properties.rs
Successful release, dimension, cohort, and property-schema changes invalidate unresolved-property cache entries.
Extended release serving
airborne_server/src/release.rs
Release serving recognizes enabled extended requests, attaches unresolved properties when available, and returns the resolved release when retrieval fails.

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

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant ServeRelease
  participant Redis
  participant Superposition
  Client->>ServeRelease: Request release with extended=true
  ServeRelease->>Redis: Read unresolved-properties cache
  alt Cache miss
    ServeRelease->>Superposition: Fetch filtered config and experiments
    Superposition-->>ServeRelease: Return unresolved properties
    ServeRelease->>Redis: Store unresolved properties
  end
  ServeRelease-->>Client: Return resolved release with optional unresolved properties
Loading

Possibly related PRs

Suggested reviewers: jamesgeorg

Poem

A rabbit packs configs neat,
With experiments fresh and fleet.
Redis guards the bundle bright,
Superposition fills the light.
Extended releases hop along!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: serving releases with unresolved configuration data.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/serve-release-extended

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@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: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@airborne_docs/docs/api-reference/overview.md`:
- Line 96: Update the TTL description in the overview documentation to state
that Redis fallback caching lasts seven days, matching
UNRESOLVED_PROPERTIES_TTL, instead of claiming a 24-hour TTL.

In `@airborne_server/src/organisation/application/dimension.rs`:
- Around line 145-150: Update the cohort-dimension, standard-dimension, and
update mutation flows in
airborne_server/src/organisation/application/dimension.rs at lines 145-150,
177-182, and 330-331: perform invalidate_unresolved_properties after each
weight_recompute() attempt, including when recompute returns an error, then
propagate the recompute error.

In `@airborne_server/src/organisation/application/properties.rs`:
- Around line 326-331: Update put_properties_schema_api and its
rollback_config_update flow so any rollback failure is propagated as an error
instead of merely logged, preventing a successful response when schema writes
may remain partially applied. Preserve the existing rollback handling for
successful rollbacks and ensure all run_fail_end paths (Operation, Join, Panic,
and MissingResult) surface rollback failure.

In `@airborne_server/src/release/utils.rs`:
- Around line 608-619: Update the fetch-and-cache flow around
fetch_unresolved_properties and the cache.set_ex call to prevent a
pre-invalidation response from being written after a workspace mutation. Add or
reuse a per-workspace generation/version check (or equivalent serialization with
invalidation), verify it immediately before writing, and skip the cache write
when the generation changed.

In `@smithy/models/release.smithy`:
- Line 373: Decide release serving is public and make the contract consistent:
in smithy/models/release.smithy lines 373-373 and 387-387, remove `@requiresauth`
from ServeRelease and ServeReleaseV2 while preserving public descriptions; in
airborne_docs/openapi/airborne.openapi.json lines 2093-2093 and 2195-2195,
retain public descriptions and add explicit security: [] overrides for both
operations so the generated contract does not inherit bearer authentication.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 7417cc39-31e4-4220-a220-1acd0599195c

📥 Commits

Reviewing files that changed from the base of the PR and between a706e7f and b7f6422.

📒 Files selected for processing (9)
  • airborne_docs/docs/api-reference/overview.md
  • airborne_docs/openapi/airborne.openapi.json
  • airborne_server/src/organisation/application/dimension.rs
  • airborne_server/src/organisation/application/dimension/cohort.rs
  • airborne_server/src/organisation/application/properties.rs
  • airborne_server/src/release.rs
  • airborne_server/src/release/types.rs
  • airborne_server/src/release/utils.rs
  • smithy/models/release.smithy


### Caching and freshness

The bundle is cached in Redis per (organisation, application), so a request with `extended=true` normally costs no extra Superposition round-trip. The cache is dropped explicitly whenever anything that can change the bundle changes: a release is **created, updated, ramped, concluded or discarded**, the **`config.properties` schema** is updated, or a **dimension or cohort** is created, updated or deleted (cohort definitions travel in `dimensions`). A 24-hour TTL backstops anything that bypasses those paths — for example editing the workspace in Superposition directly. If Redis is unavailable the server falls back to Superposition rather than dropping the bundle.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Correct the Redis TTL statement.

UNRESOLVED_PROPERTIES_TTL is seven days, not 24 hours. The current text understates how long direct Superposition changes can remain stale. Either change the implementation to one day or document the seven-day TTL.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@airborne_docs/docs/api-reference/overview.md` at line 96, Update the TTL
description in the overview documentation to state that Redis fallback caching
lasts seven days, matching UNRESOLVED_PROPERTIES_TTL, instead of claiming a
24-hour TTL.

Comment on lines +145 to +150
crate::release::utils::invalidate_unresolved_properties(
&state,
&organisation,
&application,
)
.await;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Invalidate after every dimension mutation outcome.

Each dimension write completes before the separate weight_recompute() call. A recompute error returns before the new invalidation call, so the persisted dimension can leave the unresolved-properties cache stale.

  • airborne_server/src/organisation/application/dimension.rs#L145-L150: invalidate after the cohort-dimension recompute attempt, then return its error.
  • airborne_server/src/organisation/application/dimension.rs#L177-L182: invalidate after the standard-dimension recompute attempt, then return its error.
  • airborne_server/src/organisation/application/dimension.rs#L330-L331: invalidate after the update recompute attempt, then return its error.
📍 Affects 1 file
  • airborne_server/src/organisation/application/dimension.rs#L145-L150 (this comment)
  • airborne_server/src/organisation/application/dimension.rs#L177-L182
  • airborne_server/src/organisation/application/dimension.rs#L330-L331
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@airborne_server/src/organisation/application/dimension.rs` around lines 145 -
150, Update the cohort-dimension, standard-dimension, and update mutation flows
in airborne_server/src/organisation/application/dimension.rs at lines 145-150,
177-182, and 330-331: perform invalidate_unresolved_properties after each
weight_recompute() attempt, including when recompute returns an error, then
propagate the recompute error.

Comment on lines +326 to +331
crate::release::utils::invalidate_unresolved_properties(
&state_for_cache,
&organisation,
&application,
)
.await;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 15 --glob '*.rs' \
  -e '\b(run_fail_end|rollback_config_update)\s*\(' \
  -e '\bTxnError::(Operation|Join|Panic|MissingResult)\b'

Repository: juspay/airborne

Length of output: 153


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files | sed -n '1,120p'

echo "== locate properties transaction rollback candidates =="
fd -i 'properties|transaction|release' -e rs . | sed -n '1,200p'

echo "== search relevant identifiers =="
rg -n --glob '*.rs' \
  'invalidate_unresolved_properties|run_fail_end|transaction::|rollback|TxnError|Operation|Join|Panic|MissingResult|release::utils' \
  .

echo "== diff stat =="
git diff --stat || true
git diff -- airborne_server/src/organisation/application/properties.rs | sed -n '1,240p' || true

Repository: juspay/airborne

Length of output: 29207


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== transaction.rs =="
cat -n airborne_server/src/organisation/application/properties/transaction.rs

echo "== properties.rs relevant section =="
sed -n '1,440p' airborne_server/src/organisation/application/properties.rs | cat -n

echo "== release/utils invalidation relevant section =="
sed -n '460,685p' airborne_server/src/release/utils.rs | cat -n

Repository: juspay/airborne

Length of output: 37875


Rollback failures must not allow partial property mutations to remain applied.

run_fail_end calls rollback_config_update for Operation, Join, Panic, and MissingResult paths, so the success-only cache invalidation is not the primary concern here. However, rollback_config_update logs rollback failures but does not enforce the partial write is reverted. Add a rollback-failure path that returns an error from put_properties_schema_api or otherwise prevents returning success while schema writes may remain inconsistently applied.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@airborne_server/src/organisation/application/properties.rs` around lines 326
- 331, Update put_properties_schema_api and its rollback_config_update flow so
any rollback failure is propagated as an error instead of merely logged,
preventing a successful response when schema writes may remain partially
applied. Preserve the existing rollback handling for successful rollbacks and
ensure all run_fail_end paths (Operation, Join, Panic, and MissingResult)
surface rollback failure.

Comment on lines +608 to +619
let fresh = fetch_unresolved_properties(state, workspace_name).await?;

if let (Some(cache), Some(key)) = (state.redis_cache.as_ref(), cached_key.as_ref()) {
if let Err(e) = cache.set_ex(key, &fresh, UNRESOLVED_PROPERTIES_TTL).await {
log::warn!(
"[UNRESOLVED PROPERTIES] cache write failed for {}/{}: {}",
organisation,
application,
e
);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Prevent stale cache writes after invalidation.

A request can miss the cache and start fetching before a mutation. The mutation can then delete the key, and this request can write its pre-mutation response at Line 611 after that delete. This restores stale unresolved properties for up to seven days.

Use a per-workspace generation/version check, or serialize fetch-and-invalidate operations, before writing the fetched value.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@airborne_server/src/release/utils.rs` around lines 608 - 619, Update the
fetch-and-cache flow around fetch_unresolved_properties and the cache.set_ex
call to prevent a pre-invalidation response from being written after a workspace
mutation. Add or reuse a per-workspace generation/version check (or equivalent
serialization with invalidation), verify it immediately before writing, and skip
the cache write when the generation changed.

}

/// Resolve and return the active release configuration for an application, given the caller's targeting dimensions. This is the endpoint the SDK calls at boot. Public — no auth token required.
/// Resolve and return the active release configuration for an application, given the caller's targeting dimensions. This is the endpoint the SDK calls at boot. Pass `extended=true` to also receive the unresolved `config.properties` bundle for client-side resolution. Public — no auth token required.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Make the release-serving authentication contract consistent.

The descriptions state that these endpoints are public. The Smithy operations retain @requiresauth. The OpenAPI document also applies its global bearer-security requirement because these operations do not set security: [].

Decide whether release serving is public before exposing unresolved_properties. If it is public, remove the auth requirement and generate an explicit OpenAPI security override. If it requires authentication, correct the public-endpoint descriptions.

  • smithy/models/release.smithy#L373-L373: align the ServeRelease description and authentication trait.
  • smithy/models/release.smithy#L387-L387: align the ServeReleaseV2 description and authentication trait.
  • airborne_docs/openapi/airborne.openapi.json#L2093-L2093: align the ServeReleaseV2 description with its effective security requirement.
  • airborne_docs/openapi/airborne.openapi.json#L2195-L2195: align the ServeRelease description with its effective security requirement.
📍 Affects 2 files
  • smithy/models/release.smithy#L373-L373 (this comment)
  • smithy/models/release.smithy#L387-L387
  • airborne_docs/openapi/airborne.openapi.json#L2093-L2093
  • airborne_docs/openapi/airborne.openapi.json#L2195-L2195
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@smithy/models/release.smithy` at line 373, Decide release serving is public
and make the contract consistent: in smithy/models/release.smithy lines 373-373
and 387-387, remove `@requiresauth` from ServeRelease and ServeReleaseV2 while
preserving public descriptions; in airborne_docs/openapi/airborne.openapi.json
lines 2093-2093 and 2195-2195, retain public descriptions and add explicit
security: [] overrides for both operations so the generated contract does not
inherit bearer authentication.

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.

1 participant