Skip to content
Merged
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
22 changes: 21 additions & 1 deletion crates/ordo-platform/src/release/executions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1056,6 +1056,26 @@ async fn build_rollback_context(
))
})?;

// Re-inline sub-rules into the target snapshot before dispatching the rollback.
// A direct publish's deployment row stores the un-inlined authored draft, so
// rolling it back verbatim would ship a dangling SubRule reference (silently
// dropped at studio→engine conversion). Inlining is idempotent, so an
// already-inlined snapshot (governed releases) is untouched. A sub-rule that no
// longer exists makes the rollback unassemblable — a permanent failure — so map
// it to SetupDependencyMissing to settle as RollbackFailed instead of looping.
let snapshot = crate::ruleset_draft::inline_sub_rules_into_draft(
&state,
&release.org_id,
&release.project_id,
rollback_deployment.snapshot,
)
.await
.map_err(|e| {
anyhow::Error::new(SetupDependencyMissing(format!(
"Rollback snapshot could not be assembled: {e}"
)))
})?;

execution.instances.sort_by(|left, right| {
left.batch_index
.cmp(&right.batch_index)
Expand All @@ -1074,7 +1094,7 @@ async fn build_rollback_context(
rollback_version,
env,
instances: execution.instances,
snapshot: rollback_deployment.snapshot,
snapshot,
strategy: execution.strategy,
actor: system_history_actor("release_worker"),
release_note: Some(format!("Rollback for release {}", release_request_id)),
Expand Down
24 changes: 19 additions & 5 deletions crates/ordo-platform/src/ruleset_draft.rs
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,20 @@ pub async fn redeploy(
.map_err(PlatformError::Internal)?
.ok_or_else(|| PlatformError::not_found("Environment not found"))?;

// Re-inline sub-rules before rolling the snapshot out again. A direct publish
// stores the *authored* (un-inlined) draft in its deployment row, and older
// rows predate inlining entirely, so dispatching the stored snapshot verbatim
// would ship a ruleset whose SubRule steps reference a graph that isn't there
// (studio→engine conversion drops the dangling reference silently). Inlining is
// idempotent — an already-inlined graph is left untouched (see the
// `sub_rules.contains_key` guard in `inline_sub_rules_with_manifest`) — so this
// heals old/direct-publish rows and no-ops on already-inlined ones. Do it before
// creating the row so a now-missing sub-rule fails fast without orphaning a
// `Dispatched` deployment.
let inlined =
inline_sub_rules_into_draft(&state, &org_id, &project_id, original.snapshot.clone())
.await?;

let dep_id = Uuid::new_v4().to_string();
let deployment = RulesetDeployment {
id: dep_id.clone(),
Expand All @@ -713,7 +727,7 @@ pub async fn redeploy(
ruleset_name: ruleset_name.clone(),
version: original.version.clone(),
release_note: req.release_note.clone(),
snapshot: original.snapshot.clone(),
snapshot: inlined.clone(),
deployed_at: Utc::now(),
deployed_by: Some(claims.sub.clone()),
status: DeploymentStatus::Dispatched,
Expand All @@ -724,16 +738,16 @@ pub async fn redeploy(
.await
.map_err(PlatformError::Internal)?;

// The stored deployment snapshot already contains inlined sub-rules — roll it out
// through the release engine, exactly like a fresh publish (async, worker-settled).
// On enqueue failure, settle the `Dispatched` row to `Failed` so it doesn't orphan.
// Roll it out through the release engine, exactly like a fresh publish (async,
// worker-settled). On enqueue failure, settle the `Dispatched` row to `Failed`
// so it doesn't orphan.
if let Err(e) = enqueue_publish_release(
&state,
&org_id,
&project_id,
&ruleset_name,
&original.version,
original.snapshot.clone(),
inlined,
&dep_id,
&env,
&claims,
Expand Down
Loading