Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
814 changes: 729 additions & 85 deletions Cargo.lock

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@ tracing-actix-web = "0.7.21"
url = "2.5.0"
uuid = { version = "1.20.0", features = ["v4", "serde", "js"] }
uniffi = { version = "0.29.3", features = ["cli"] }
kronos-worker = { path = "../kronos/crates/worker" }
kronos-common = { path = "../kronos/crates/common" }
Comment on lines +94 to +95
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.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if the Kronos submodule or sibling repository exists at the expected location
if [ -d "../kronos" ]; then
  echo "✓ ../kronos directory exists"
  ls -la ../kronos/crates/
else
  echo "✗ ../kronos directory not found"
  echo "The Cargo.toml expects kronos to be a sibling directory to superposition"
fi

# Check if it might be a git submodule
git submodule status | grep kronos || echo "No kronos submodule found"

# Check the actual repository structure
fd -t d "kronos" -d 3

Repository: juspay/superposition

Length of output: 195


🏁 Script executed:

# First, check the Cargo.toml file at the specified lines
cat -n Cargo.toml | sed -n '90,100p'

Repository: juspay/superposition

Length of output: 715


🏁 Script executed:

# Search for all kronos references in the codebase
rg "kronos" --type toml

Repository: juspay/superposition

Length of output: 583


🏁 Script executed:

# Check git configuration and submodule setup
cat -n .gitmodules 2>/dev/null || echo "No .gitmodules file found"

Repository: juspay/superposition

Length of output: 90


🏁 Script executed:

# Check if there's any documentation about kronos setup
fd -t f -e md -e MD | xargs grep -l "kronos" 2>/dev/null | head -10

Repository: juspay/superposition

Length of output: 46


🏁 Script executed:

# Look for README or setup documentation
fd -t f -e md -e MD -e txt | xargs grep -l "kronos\|setup\|build" 2>/dev/null | head -5

Repository: juspay/superposition

Length of output: 206


🏁 Script executed:

# Check GitHub Actions or CI configuration
find .github -type f -name "*.yml" -o -name "*.yaml" 2>/dev/null | xargs cat 2>/dev/null | head -100

Repository: juspay/superposition

Length of output: 3612


🏁 Script executed:

# Look for any Makefile, script, or documentation about dependencies
fd -t f \( -name "Makefile" -o -name "setup*" -o -name "build*" \) 2>/dev/null

Repository: juspay/superposition

Length of output: 46


🏁 Script executed:

# Check if there are environment setup scripts or docker files that might clone kronos
fd -t f \( -name "*.sh" -o -name "Dockerfile*" \) | xargs grep -l "kronos" 2>/dev/null

Repository: juspay/superposition

Length of output: 297


🏁 Script executed:

# Search README for kronos and build instructions
rg "kronos|build|setup|prerequisite" README.md -i -A 3

Repository: juspay/superposition

Length of output: 1608


🏁 Script executed:

# Check if there's a Makefile that might handle dependencies
head -50 Makefile 2>/dev/null || echo "No Makefile found"

Repository: juspay/superposition

Length of output: 82


🌐 Web query:

kronos-worker kronos-common crates.io rust

💡 Result:

kronos-worker and kronos-common are internal Rust crates from the Juspay Kronos project, a distributed job scheduling and execution system. They are not published on crates.io but are part of the monorepo at https://github.com/juspay/kronos [1]. kronos-common: Shared library providing models, database layer, configuration, tenant management, caching, and metrics [1]. kronos-worker: Execution engine that polls the database for queued, retrying, or pending job executions, resolves templates, dispatches to endpoints (including Kafka and Redis Streams via feature flags), and exposes metrics via HTTP [1]. The project includes other crates like kronos-api (REST API server with Actix-web), kronos-mock-server (test fixture), and kronos-dashboard (Leptos/WASM web UI). Build with features for dispatchers: cargo build --workspace --features kronos-worker/kafka [1]. No crates.io pages found for these exact names; they appear to be private/internal to the Juspay repo [1][all searches]. Juspay develops open-source Rust projects like Hyperswitch, and Kronos fits as a job scheduler [2][3].

Citations:


Add kronos as a git submodule or update the path dependency.

The path dependencies for kronos-worker and kronos-common at lines 94–95 point to ../kronos/crates/worker and ../kronos/crates/common, which do not exist in the repository. The kronos project is a separate monorepo at https://github.com/juspay/kronos and is not published on crates.io, so path dependencies must be resolved.

Options:

  1. Add kronos as a git submodule at ../kronos (relative to superposition)
  2. Clone kronos as a sibling directory before building
  3. Update the path to match the actual kronos location if cloned elsewhere
🤖 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 `@Cargo.toml` around lines 94 - 95, Cargo.toml references local path
dependencies kronos-worker and kronos-common which point to
../kronos/crates/worker and ../kronos/crates/common that are missing; resolve by
adding the kronos repo as a git submodule at ../kronos (or clone it as a
sibling) so those paths exist, or update the path entries for kronos-worker and
kronos-common to the actual location where you cloned the kronos monorepo;
ensure the paths in Cargo.toml match the repository layout so cargo can find the
crate packages.

async-trait = "0.1"
sqlx = { version = "0.7", features = ["runtime-tokio-rustls", "postgres", "chrono", "uuid", "json"] }
tokio-util = { version = "0.7" }
superposition = { path = "crates/superposition", version = "0.105.0" }
superposition_types = { path = "crates/superposition_types", version = "0.105.0" }
superposition_derives = { path = "crates/superposition_derives", version = "0.105.0" }
Expand Down
2 changes: 1 addition & 1 deletion crates/context_aware_config/src/api/secrets/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ async fn update_handler(
};

let updated_secret = diesel::update(secrets::table)
.filter(secrets::name.eq(secret_name))
.filter(secrets::name.eq(&secret_name))
.set((
changeset,
secrets::last_modified_at.eq(chrono::Utc::now()),
Expand Down
2 changes: 2 additions & 0 deletions crates/frontend/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -839,6 +839,7 @@ pub async fn create_webhook(
payload_version: PayloadVersion,
custom_headers: CustomHeaders,
events: Vec<WebhookEvent>,
max_retries: i32,
change_reason: String,
workspace: &str,
org_id: &str,
Expand All @@ -852,6 +853,7 @@ pub async fn create_webhook(
payload_version: Some(payload_version),
custom_headers: Some(custom_headers),
events,
max_retries: Some(max_retries),
change_reason: ChangeReason::try_from(change_reason)?,
};
let host = use_host_server();
Expand Down
29 changes: 29 additions & 0 deletions crates/frontend/src/components/webhook_form.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,16 +43,21 @@ fn try_update_payload(
payload_version: PayloadVersion,
custom_headers: CustomHeaders,
events: Vec<WebhookEvent>,
max_retries: i32,
description: String,
change_reason: String,
) -> Result<UpdateWebhookRequest, String> {
if max_retries > 3 {
return Err("Max retries must not exceed 3".to_string());
Comment on lines +50 to +51
}
Comment on lines +50 to +52
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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Validate that max_retries is non-negative.

The validation only checks the upper bound (> 3) but allows negative values. Negative retry counts are semantically invalid and should be rejected.

🛡️ Proposed fix to add lower bound validation
-    if max_retries > 3 {
-        return Err("Max retries must not exceed 3".to_string());
-    }
+    if max_retries < 0 || max_retries > 3 {
+        return Err("Max retries must be between 0 and 3".to_string());
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if max_retries > 3 {
return Err("Max retries must not exceed 3".to_string());
}
if max_retries < 0 || max_retries > 3 {
return Err("Max retries must be between 0 and 3".to_string());
}
🤖 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 `@crates/frontend/src/components/webhook_form.rs` around lines 50 - 52,
Validation currently only enforces the upper bound for max_retries (if
max_retries > 3) but allows negative values; update the check that validates
max_retries in the webhook form validation code to ensure it's within 0..=3 by
adding a lower-bound check (reject when max_retries < 0) and returning a clear
Err (e.g., "Max retries must be between 0 and 3") where the existing max_retries
validation lives so both negative and too-large values are rejected.

Ok(UpdateWebhookRequest {
enabled: Some(enabled),
url: Some(NonEmptyString::try_from(url)?),
method: Some(method),
payload_version: Some(payload_version),
custom_headers: Some(custom_headers),
events: Some(events),
max_retries: Some(max_retries),
description: Some(Description::try_from(description)?),
change_reason: ChangeReason::try_from(change_reason)?,
})
Expand All @@ -69,6 +74,7 @@ pub fn WebhookForm(
#[prop(default = PayloadVersion::default())] payload_version: PayloadVersion,
#[prop(default = CustomHeaders::default())] custom_headers: CustomHeaders,
#[prop(default = Vec::new())] events: Vec<WebhookEvent>,
#[prop(default = 3)] max_retries: i32,
#[prop(into)] handle_submit: Callback<()>,
) -> impl IntoView {
let workspace = use_context::<Signal<Workspace>>().unwrap();
Expand All @@ -83,6 +89,7 @@ pub fn WebhookForm(
let (custom_headers_rs, custom_headers_ws) = create_signal(custom_headers);
let (change_reason_rs, change_reason_ws) = create_signal(String::new());
let (events_rs, events_ws) = create_signal(events);
let (max_retries_rs, max_retries_ws) = create_signal(max_retries);
let (req_inprogess_rs, req_inprogress_ws) = create_signal(false);
let update_request_rws = RwSignal::new(None);

Expand All @@ -108,6 +115,7 @@ pub fn WebhookForm(
let custom_headers = custom_headers_rs.get_untracked();
let change_reason = change_reason_rs.get_untracked();
let events = events_rs.get_untracked();
let max_retries = max_retries_rs.get_untracked();
let workspace = workspace.get_untracked().0;
let org_id = org.get_untracked().0;

Expand All @@ -132,6 +140,7 @@ pub fn WebhookForm(
payload_version,
custom_headers,
events,
max_retries,
description,
change_reason,
);
Expand All @@ -152,6 +161,7 @@ pub fn WebhookForm(
payload_version,
custom_headers,
events,
max_retries,
change_reason,
&workspace,
&org_id,
Expand Down Expand Up @@ -295,6 +305,25 @@ pub fn WebhookForm(
/>
</div>

<div class="form-control">
<Label
title="Max Retries"
description="Number of retry attempts on failure (0–3)."
/>
<Input
placeholder="Max Retries"
class="input input-bordered w-full max-w-md"
value=Value::Number(serde_json::Number::from(max_retries_rs.get_untracked()))
schema_type=Single(JsonSchemaType::Integer)
r#type=InputType::Integer
on_change=Callback::new(move |value: Value| {
if let Some(n) = value.as_i64() {
max_retries_ws.set(n as i32);
}
Comment on lines +319 to +322
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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add bounds checking before casting i64 to i32.

The code casts i64 to i32 without checking if the value fits in the target type's range. Although users are unlikely to enter extreme values, numeric inputs can be manipulated to overflow the i32 range, potentially resulting in unexpected retry counts.

🛡️ Proposed fix to validate range before cast
                     on_change=Callback::new(move |value: Value| {
                         if let Some(n) = value.as_i64() {
-                            max_retries_ws.set(n as i32);
+                            if n >= 0 && n <= 3 {
+                                max_retries_ws.set(n as i32);
+                            }
                         }
                     })

This validates the range before updating the signal, ensuring only valid values (0–3) are accepted.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
on_change=Callback::new(move |value: Value| {
if let Some(n) = value.as_i64() {
max_retries_ws.set(n as i32);
}
on_change=Callback::new(move |value: Value| {
if let Some(n) = value.as_i64() {
if n >= 0 && n <= 3 {
max_retries_ws.set(n as i32);
}
}
🤖 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 `@crates/frontend/src/components/webhook_form.rs` around lines 319 - 322, The
on_change callback casts value.as_i64() to i32 without bounds checking; update
the closure (the code handling value.as_i64() and calling max_retries_ws.set) to
validate the integer is within the allowed range before casting and setting the
signal (e.g., ensure n is between 0 and 3 or within i32::MIN..=i32::MAX as
appropriate), and only call max_retries_ws.set(n as i32) when the check passes
(otherwise ignore or clamp the value).

})
/>
</div>

<div class="form-control gap-3">
<Label
title="Custom Headers"
Expand Down
1 change: 1 addition & 0 deletions crates/frontend/src/pages/webhook.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ pub fn Webhook() -> impl IntoView {
custom_headers=webhook_st
.with_value(|w| w.custom_headers.clone())
events=webhook_st.with_value(|w| w.events.clone())
max_retries=webhook_st.with_value(|w| w.max_retries)
handle_submit=move |_| {
webhook_resource.refetch();
action_rws.set(Action::None);
Expand Down
5 changes: 5 additions & 0 deletions crates/service_utils/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ once_cell = { workspace = true }
openidconnect = "3.5.0"
rand = "0.8"
tokio = { workspace = true }
tokio-util = { workspace = true }
kronos-worker = { workspace = true }
kronos-common = { workspace = true }
async-trait = { workspace = true }
sqlx = { workspace = true }
tracing = { workspace = true }
regex = { workspace = true }
reqwest = { workspace = true }
Expand Down
Loading