Skip to content

Fix: build semver and duplicate entries for release id and versions#253

Open
yuvrajjsingh0 wants to merge 1 commit intomainfrom
fix-build-semver
Open

Fix: build semver and duplicate entries for release id and versions#253
yuvrajjsingh0 wants to merge 1 commit intomainfrom
fix-build-semver

Conversation

@yuvrajjsingh0
Copy link
Contributor

@yuvrajjsingh0 yuvrajjsingh0 commented Feb 16, 2026

Summary by CodeRabbit

  • New Features

    • Semantic versioning for builds (major/minor/patch) and a build status field (BUILDING/READY).
  • Improvements

    • Background build processing to return latest build immediately.
    • Automatic retry and conflict handling when claiming new build versions.
    • Faster, more accurate build version sorting and queries.
  • Bug Fixes

    • Clearer conflict responses for version/release collisions.

@semanticdiff-com
Copy link

semanticdiff-com bot commented Feb 16, 2026

@coderabbitai
Copy link

coderabbitai bot commented Feb 16, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

This PR introduces semantic versioning infrastructure for builds by adding version columns to the database schema, implementing retry mechanisms with advisory locking for build creation, adding Maven metadata synchronization, integrating Superposition RID updates, and establishing conflict-based error handling for version collisions.

Changes

Cohort / File(s) Summary
Database Migrations & Schema
airborne_server/migrations/2026-02-13-094547_add_version_columns_to_builds/up.sql, airborne_server/migrations/2026-02-13-094547_add_version_columns_to_builds/down.sql, airborne_server/src/utils/db/schema.rs
Adds major\_version, minor\_version, patch\_version integer columns to builds table; creates composite index for version-based sorting and three unique constraints (release\_id, build\_version, semantic version tuple); defines application\_settings and organisation\_invites tables with Postgres composite types.
Build Flow Enhancement
airborne_server/src/build.rs
Introduces async helpers for Maven metadata updates and Superposition RID management; implements retry loop with UNIQUE constraint collision handling (up to 10 attempts); adds pre-check for existing builds by release\_id; supports background build spawning for non-forced rebuilds; propagates workspace\_name parameter through build chain.
Error Handling
airborne_server/src/types.rs
Adds Conflict error variant to ABError and ABErrorCodes, mapped to HTTP 409 StatusCode; introduces AB\_007 error label for conflict scenarios.
Database Models
airborne_server/src/utils/db/models.rs
Extends BuildEntry and NewBuildEntry structs with major\_version, minor\_version, patch\_version i32 fields to support version component storage.
Release Configuration
airborne_server/src/release/utils.rs
Filters out "build." prefixed keys during control\_overrides construction to prevent propagation into build overrides.
Superposition Config
airborne_server/superposition-default-configs.json
Adds build.last\_created\_build\_rid configuration key with empty string default and dimension override support.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant BuildService
    participant Database as Database<br/>(Builds Table)
    participant S3 as S3<br/>(Maven Metadata)
    participant Superposition

    Client->>BuildService: build(org, app, release_id, ...)
    
    BuildService->>Database: Check existing build for release_id
    alt Existing build found
        BuildService-->>Client: Return cached build version
    else No existing build
        BuildService->>BuildService: Start retry loop (max 10 attempts)
        
        loop Retry until success or max attempts
            BuildService->>Database: Claim new version with UNIQUE constraints<br/>(org, app, major.minor.patch)
            alt Unique constraint collision
                BuildService->>Database: Fetch existing build for release_id
                alt Build found
                    BuildService-->>Client: Return existing version
                else Build not found
                    BuildService->>BuildService: Retry version claim
                end
            else Success
                BuildService->>BuildService: Generate build artifacts
                BuildService->>S3: update_maven_metadata()<br/>(read, merge, upload)
                BuildService->>Superposition: update_superposition_build_rid()<br/>(set last_created_build_rid)
                BuildService->>Database: Mark build as complete
                BuildService-->>Client: Return new version
            end
        end
        
        alt Previous build exists (non-forced)
            BuildService->>BuildService: Spawn background build task<br/>(async, same function)
            BuildService-->>Client: Return immediately
        end
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • JamesGeorg
  • Yaswanth0606

Poem

🐰 A builder's tale, version by version,
Retrying with locks, in smart conversion,
Metadata merged, RIDs aligned,
Semver precision, dimensions refined,
Builds flow like carrots, perfectly designed! 🥕

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main changes: adding semantic versioning (semver) support to builds and enforcing uniqueness constraints to prevent duplicate entries for release IDs and versions.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-build-semver

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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 and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In
`@airborne_server/migrations/2026-02-13-094547_add_version_columns_to_builds/up.sql`:
- Around line 30-38: The migration creates unique indexes
builds_unique_release_idx, builds_unique_version_idx and
builds_unique_semver_idx but will fail if duplicate rows already exist; before
running these CREATE UNIQUE INDEX statements add a deduplication step that
removes duplicates for the grouping keys (organisation, application, release_id)
and (organisation, application, build_version) and (organisation, application,
major_version, minor_version, patch_version) — e.g. find rows with the same
group and delete all but the intended canonical row (keep by max(id) or newest
created_at), or alternatively write the migration to abort with a clear message
if duplicates exist so the duplicates can be cleaned manually; ensure the dedupe
targets the same columns used by builds_unique_release_idx,
builds_unique_version_idx and builds_unique_semver_idx and runs before the
CREATE UNIQUE INDEX statements.
- Around line 7-14: The backfill UPDATE uses split_part and CAST on
build_version and will fail on malformed strings; modify the UPDATE to only
touch rows where build_version matches a strict semver regex (e.g., add "AND
build_version ~ '^[0-9]+\\.[0-9]+\\.[0-9]+$'" to the WHERE clause) so
CAST(split_part(... ) AS INTEGER) is safe for
major_version/minor_version/patch_version, and separately ensure duplicate
(organisation, application, release_id) and (organisation, application,
build_version) tuples are cleaned or handled before running the CREATE UNIQUE
INDEX statements (dedupe or skip/create CONCURRENTLY after cleanup) to avoid
index creation failures.

In `@airborne_server/src/build.rs`:
- Around line 493-563: The update_maven_metadata function is labeled "Must be
called under advisory lock" but build() never acquires one, causing a
read-modify-write race on maven-metadata.xml; fix by acquiring a consistent
advisory lock in build() around the call to update_maven_metadata (derive a lock
key from org+app or another stable identifier), call your existing DB advisory
lock helper (acquire then release) before/after invoking update_maven_metadata,
and only proceed with the metadata update while the lock is held; alternatively,
if you prefer eventual consistency, remove the misleading comment and/or
implement S3 conditional writes (use If-Match/ETag with retry) inside
update_maven_metadata (in push_file_byte_arr) and document the chosen approach.
- Around line 579-619: When an existing build is found the code updates
Superposition via update_superposition_build_rid but returns early without
attempting to call update_maven_metadata; add a call to update_maven_metadata
(using the same context: &state, &workspace_name, &org, &app, &release_id and
any required dimensions/metadata args) before returning existing.build_version,
and mirror the error handling used for update_superposition_build_rid (log
errors with tracing::error and continue to return the build version) so a
previously failed maven metadata upload can be retried on this early-return
path.
🧹 Nitpick comments (3)
airborne_server/src/build.rs (3)

646-752: Retry loop increments only patch but the initial candidate may also collide on release_id.

The retry logic on unique violation (line 749) only bumps version_candidate.patch += 1. This correctly handles version-number collisions. However, consider this scenario:

  • Request A for release_id=X starts, queries latest version = 1.0.5, candidate = 1.0.6
  • Request B for release_id=Y starts, queries latest version = 1.0.5, candidate = 1.0.6
  • Request A inserts (org, app, 1.0.6, release_id=X) → success
  • Request B tries (org, app, 1.0.6, release_id=Y) → unique violation on version
  • Request B checks if release_id=Y exists → no → bumps to 1.0.7 → retries → success

This works correctly. The logic is sound. One minor concern: the __UNIQUE_VIOLATION__ sentinel string approach works but is fragile — if someone changes the string in one place but not the other, the retry silently breaks. Consider using a dedicated error variant or constant.

Extract sentinel as a constant
+const UNIQUE_VIOLATION_SENTINEL: &str = "__UNIQUE_VIOLATION__";
+
 // In the insert match:
-                )) => Err(ABError::Conflict("__UNIQUE_VIOLATION__".to_string())),
+                )) => Err(ABError::Conflict(UNIQUE_VIOLATION_SENTINEL.to_string())),

 // In the check:
-                    ABError::Conflict(msg) if msg == "__UNIQUE_VIOLATION__"
+                    ABError::Conflict(msg) if msg == UNIQUE_VIOLATION_SENTINEL

1013-1051: Background build has no deduplication guard — multiple concurrent requests can spawn redundant background tasks.

If several non-force requests arrive while last_built_rid != release_id, each one will spawn a separate tokio::spawn background build. The retry loop in build() will handle the DB-level dedup via unique constraints, but redundant S3 uploads (zip/aar/pom/maven-metadata) will still execute, wasting resources and amplifying the maven metadata race condition.

Consider using a concurrency-limiting mechanism (e.g., a DashMap or tokio::sync::Semaphore keyed by (org, app, release_id)) to ensure at most one background build runs per release.


655-662: Use try_from() instead of as i32 cast for safer version component conversion.

SemVer fields are u32, and casting to i32 will silently wrap on overflow for values exceeding i32::MAX (2,147,483,647). While practically unlikely for version numbers, defensive casting with error handling is preferred:

Suggested safe cast
-                major_version: ver_insert.major as i32,
-                minor_version: ver_insert.minor as i32,
-                patch_version: ver_insert.patch as i32,
+                major_version: i32::try_from(ver_insert.major).map_err(|_| {
+                    ABError::BadRequest("Version component exceeds i32 range".into())
+                })?,
+                minor_version: i32::try_from(ver_insert.minor).map_err(|_| {
+                    ABError::BadRequest("Version component exceeds i32 range".into())
+                })?,
+                patch_version: i32::try_from(ver_insert.patch).map_err(|_| {
+                    ABError::BadRequest("Version component exceeds i32 range".into())
+                })?,

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@airborne_server/src/build.rs`:
- Around line 863-864: The call to update_maven_metadata leaves the DB row stuck
in BUILDING if it returns an error; handle that error explicitly: wrap
update_maven_metadata(&org, &app, &new_build_version, &state).await and on
Err(e) either perform the same cleanup used for artifact upload failures (call
the existing cleanup function that removes the build row / reverts state) or
mark the build READY and log a warning (call the same routine used to set build
status to READY, e.g., set_build_status/mark_build_ready with &state) and emit a
processLogger.warn or similar including the error; implement Option 2 (mark
READY + warning) if you prefer, but ensure the error path updates the build row
state (or deletes it) instead of leaving it in BUILDING.
🧹 Nitpick comments (3)
airborne_server/src/build.rs (3)

493-493: Misleading comment: "advisory lock" is not actually used.

The comment states "Must be called under advisory lock" but the build() function relies on UNIQUE constraint retries rather than PostgreSQL advisory locks. Consider updating the comment to reflect the actual concurrency mechanism, or acknowledge that concurrent maven metadata updates may result in last-write-wins behavior.

📝 Suggested comment update
-/// Update maven-metadata.xml in S3. Must be called under advisory lock.
+/// Update maven-metadata.xml in S3.
+/// Note: Concurrent updates may result in last-write-wins; the UNIQUE constraint
+/// on build versions ensures no duplicate version numbers are created.

678-810: Version claim loop looks solid with a minor improvement opportunity.

The retry loop correctly handles UNIQUE constraint violations by re-querying the latest version and incrementing. The 5-second deadline prevents infinite loops.

Consider exponential backoff instead of fixed 10ms delay to reduce database pressure under high contention:

📝 Optional: Exponential backoff
                 version_candidate = increment_build_version(refreshed);
                 attempt += 1;
-                tokio::time::sleep(std::time::Duration::from_millis(10)).await;
+                let backoff_ms = std::cmp::min(10 * (1 << attempt), 500);
+                tokio::time::sleep(std::time::Duration::from_millis(backoff_ms)).await;
             }

970-970: Inconsistent log level for errors.

Lines 970 and 1003 use info! to log failures when updating Superposition, but the messages describe errors. Consider using error! or warn! for consistency with other error logging in this file.

📝 Suggested fix
             .map_err(|e| {
-                info!("Failed to update default config for build rid: {:?}", e);
+                error!("Failed to update default config for build rid: {:?}", e);
                 ABError::InternalServerError(
             .map_err(|e| {
-                info!("Failed to create context override for build rid: {:?}", e);
+                error!("Failed to create context override for build rid: {:?}", e);
                 ABError::InternalServerError(

Also applies to: 1003-1003

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