diff --git a/docs/public/api-reference/fabro-api.yaml b/docs/public/api-reference/fabro-api.yaml index e7e3dbc69..da3e0ad2e 100644 --- a/docs/public/api-reference/fabro-api.yaml +++ b/docs/public/api-reference/fabro-api.yaml @@ -2297,6 +2297,197 @@ paths: schema: $ref: "#/components/schemas/ErrorResponse" + /api/v1/runs/{id}/retry: + post: + operationId: retryRun + tags: [Runs] + summary: Retry Run + description: > + Creates a new run from the terminal source run's last checkpoint and + starts it. When the source failed on a stage, that stage runs again on + the files of the stage before it; otherwise the new run continues from + the last checkpoint as it stands. The new run records `retried_from` + and `fork_source_ref`; the source run is left unchanged. Active and + archived runs are not retryable. + parameters: + - $ref: "#/components/parameters/RunId" + responses: + "201": + description: New retry run created and started + content: + application/json: + schema: + $ref: "#/components/schemas/Run" + "400": + description: The source has no checkpoint to retry from + headers: + x-request-id: + $ref: "#/components/headers/XRequestId" + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + "404": + description: Run not found + headers: + x-request-id: + $ref: "#/components/headers/XRequestId" + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + "409": + description: Source run is not retryable + headers: + x-request-id: + $ref: "#/components/headers/XRequestId" + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + + /api/v1/runs/{id}/rewind: + post: + operationId: rewindRun + tags: [Runs] + summary: Rewind Run + description: > + Creates a new run from a checkpoint of a terminal source run and + starts it, then archives the source run and records + `run.superseded_by` on it. Returns 207 when the new run was created + but the source archive step failed. + parameters: + - $ref: "#/components/parameters/RunId" + requestBody: + required: false + content: + application/json: + schema: + $ref: "#/components/schemas/RewindRequest" + responses: + "200": + description: Source archived and new run created + content: + application/json: + schema: + $ref: "#/components/schemas/RewindResponse" + "207": + description: New run created but source archive failed + content: + application/json: + schema: + $ref: "#/components/schemas/RewindResponse" + "400": + description: Invalid rewind target + headers: + x-request-id: + $ref: "#/components/headers/XRequestId" + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + "404": + description: Run not found + headers: + x-request-id: + $ref: "#/components/headers/XRequestId" + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + "409": + description: Source run is archived or is not terminal + headers: + x-request-id: + $ref: "#/components/headers/XRequestId" + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + + /api/v1/runs/{id}/fork: + post: + operationId: forkRun + tags: [Runs] + summary: Fork Run + description: > + Creates a new run from a checkpoint of the source run and starts it. + The new run holds the source's records up to the checkpoint's + position and continues from there in a fresh workspace restored to + the checkpoint's commit. The source run is left untouched. A + checkpoint inside a parallel branch cannot be forked at; fork at the + parallel stage instead. + parameters: + - $ref: "#/components/parameters/RunId" + requestBody: + required: false + content: + application/json: + schema: + $ref: "#/components/schemas/ForkRequest" + responses: + "200": + description: New run created and started + content: + application/json: + schema: + $ref: "#/components/schemas/ForkResponse" + "400": + description: Invalid fork target + headers: + x-request-id: + $ref: "#/components/headers/XRequestId" + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + "404": + description: Run not found + headers: + x-request-id: + $ref: "#/components/headers/XRequestId" + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + "409": + description: Source run is archived + headers: + x-request-id: + $ref: "#/components/headers/XRequestId" + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + + /api/v1/runs/{id}/timeline: + get: + operationId: getRunTimeline + tags: [Runs] + summary: Get Run Timeline + description: > + Returns the run's checkpoints in the order they were recorded, each + at its Petri position with the commit it made and the stage it + belongs to, and where the run was forked from when it is a fork. + parameters: + - $ref: "#/components/parameters/RunId" + responses: + "200": + description: Run checkpoint timeline + content: + application/json: + schema: + $ref: "#/components/schemas/RunTimelineResponse" + "404": + description: Run not found + headers: + x-request-id: + $ref: "#/components/headers/XRequestId" + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + /api/v1/runs/{id}/unarchive: post: operationId: unarchiveRun @@ -12726,6 +12917,11 @@ components: retried_from: type: ["string", "null"] description: Source run ID when this run was created by manual retry. + forked_from: + oneOf: + - $ref: "#/components/schemas/ForkOrigin" + - type: "null" + description: Where the run's records came from when it is a fork. git_identity: oneOf: - $ref: "#/components/schemas/GitIdentity" @@ -13076,6 +13272,176 @@ components: type: ["string", "null"] format: uri + ForkOrigin: + description: >- + Where a forked run came from: the source run, the position (an + execution and a firing of its root invocation) the source's records + were kept up to, and whether that firing runs again in the fork. + type: object + required: + - source_run_id + - execution + - firing + - rerun_last + properties: + source_run_id: + type: string + execution: + type: integer + format: uint64 + firing: + type: integer + format: uint64 + rerun_last: + type: boolean + + ForkRequest: + description: Request body for creating a new run from a source run checkpoint. + type: object + properties: + target: + type: ["string", "null"] + description: Optional checkpoint target such as `@2`, `build`, or `build@1`. Defaults to the latest checkpoint. + + ForkResponse: + description: Response returned after creating and starting a forked run. + type: object + required: + - source_run_id + - new_run_id + - target + - checkpoint_sha + - execution + - firing + - rerun_last + properties: + source_run_id: + type: string + new_run_id: + type: string + target: + type: string + description: The checkpoint the fork was resolved to, as `@ordinal`. + checkpoint_sha: + type: string + description: The commit the new run's workspace starts on. + execution: + type: integer + format: uint64 + description: The Petri execution of the fork position. + firing: + type: integer + format: uint64 + description: The Petri firing of the fork position. + rerun_last: + type: boolean + description: Whether the position's stage runs again in the new run. + + RewindRequest: + description: Request body for creating a replacement run from a source run checkpoint. + type: object + properties: + target: + type: ["string", "null"] + description: Optional checkpoint target such as `@2`, `build`, or `build@1`. Defaults to the latest checkpoint. + + RewindResponse: + description: Response returned after rewind creates and starts a new run. + type: object + required: + - source_run_id + - new_run_id + - target + - checkpoint_sha + - execution + - firing + - archived + properties: + source_run_id: + type: string + new_run_id: + type: string + target: + type: string + description: The checkpoint the rewind was resolved to, as `@ordinal`. + checkpoint_sha: + type: string + execution: + type: integer + format: uint64 + firing: + type: integer + format: uint64 + archived: + type: boolean + archive_error: + type: ["string", "null"] + + RunTimelineResponse: + description: The run's checkpoints in order, and its fork origin when it is a fork. + type: object + required: + - entries + properties: + entries: + type: array + items: + $ref: "#/components/schemas/TimelineEntryResponse" + forked_from: + oneOf: + - $ref: "#/components/schemas/ForkOrigin" + - type: "null" + + TimelineEntryResponse: + description: One checkpoint of a run. + type: object + required: + - ordinal + - checkpoint_seq + - execution + - firing + - attempt + - node_name + - visit + properties: + ordinal: + type: integer + format: uint64 + description: 1-based, in the order the checkpoints were recorded; the `@ordinal` a fork target names. + checkpoint_seq: + type: integer + format: uint64 + description: The checkpoint record's position among the run's platform records. + execution: + type: integer + format: uint64 + description: The Petri execution the checkpoint belongs to. + firing: + type: integer + format: uint64 + description: The Petri firing the checkpoint belongs to. + attempt: + type: integer + format: uint32 + description: The attempt of the firing whose files the commit holds, from 1. + stage: + type: ["string", "null"] + description: The stage id (`node@visit`) when the projection shows the stage. + node_name: + type: string + visit: + type: integer + format: uint32 + workspace: + type: ["string", "null"] + description: The Petri workspace id the commit was made in. + run_commit_sha: + type: ["string", "null"] + diff_summary: + oneOf: + - $ref: "#/components/schemas/DiffSummary" + - type: "null" + BoardColumn: description: | Status bucket for a run, shared by list and kanban renderings and by diff --git a/lib/apps/fabro-server/src/server/handler/lifecycle.rs b/lib/apps/fabro-server/src/server/handler/lifecycle.rs index 163b5e79b..1d53aec93 100644 --- a/lib/apps/fabro-server/src/server/handler/lifecycle.rs +++ b/lib/apps/fabro-server/src/server/handler/lifecycle.rs @@ -35,7 +35,7 @@ pub(super) fn routes() -> Router> { .route("/runs/{id}/unarchive", post(unarchive_run)) } -async fn run_response(state: &AppState, id: RunId, status: StatusCode) -> Response { +pub(super) async fn run_response(state: &AppState, id: RunId, status: StatusCode) -> Response { match state.stores.run_summaries.get(&id, Utc::now()).await { Ok(Some(summary)) => { (status, Json(state.decorate_run_summary(summary).await)).into_response() @@ -116,7 +116,21 @@ pub(in crate::server) async fn queue_run_start( )); } } + queue_run(state, id, &run_state, resume, actor).await +} +/// Queue the run for the scheduler: record that its start was requested +/// and that it is runnable (or pending approval), and register it as a +/// managed run in start or resume mode. The caller has checked that the run +/// may be queued; a fork, seeded to resume, is queued here without the +/// checkpoint check a resume of an interrupted run makes. +pub(super) async fn queue_run( + state: &AppState, + id: RunId, + run_state: &fabro_store::RunProjection, + resume: bool, + actor: Principal, +) -> Result<(), ApiError> { let run_dir = Storage::new(state.server_storage_dir()) .run_scratch(&id) .root() @@ -810,7 +824,7 @@ async fn batch_delete_runs( } #[derive(Clone, Copy)] -enum ArchiveAction { +pub(super) enum ArchiveAction { Archive, Unarchive, } @@ -986,7 +1000,7 @@ fn batch_result_failure( /// Archive a terminal run, or unarchive one: idempotent either way, refused /// with a precondition error when the run is not terminal. -async fn run_archive_operation( +pub(super) async fn run_archive_operation( state: &AppState, id: &RunId, actor: Option, diff --git a/lib/apps/fabro-server/src/server/handler/lineage.rs b/lib/apps/fabro-server/src/server/handler/lineage.rs new file mode 100644 index 000000000..c640165a1 --- /dev/null +++ b/lib/apps/fabro-server/src/server/handler/lineage.rs @@ -0,0 +1,364 @@ +//! A run's checkpoint timeline, and the runs made from it: fork, rewind and +//! retry (the integration plan's F5.1). +//! +//! The timeline is the run's `checkpoint` platform records, labelled with +//! the stages the projector folded them onto. A fork resolves a target on +//! that timeline (`@ordinal`, a node, or `node@visit`; the latest checkpoint +//! by default), creates the new run's row (`fabro_workflow::operations`), +//! seeds its records, checkpoints, snapshots and run branch from the source +//! (`fabro_petri::fork`), and queues it in resume mode, so its worker +//! acquires a fresh workspace, restores the checkpoint's commit into it and +//! continues from the position. A rewind is a fork of a terminal run that +//! archives the source and records `run.superseded` on it; a retry is a +//! fork of a terminal run at its last checkpoint, with the failed stage run +//! again when the run failed on one. + +use std::sync::Arc; + +use axum::extract::{Path, State}; +use axum::http::{HeaderMap, StatusCode}; +use axum::response::{IntoResponse, Response}; +use axum::routing::{get, post}; +use axum::{Json, Router}; +use fabro_api::types as api; +use fabro_config::Storage; +use fabro_petri::SqliteRunStore; +use fabro_petri::fork::{self as petri_fork, ForkError, ForkRequest}; +use fabro_petri::petri::RunStore; +use fabro_petri::platform_records::SqlitePlatformRecords; +use fabro_store::{PlatformRecordKind, RunProjection}; +use fabro_types::{FailureReason, Principal, RunId}; +use fabro_util::error as error_util; +use fabro_workflow::Error as WorkflowError; +use fabro_workflow::operations::{self, ForkTarget, ResolvedForkTarget, RunTimeline}; +use tracing::{error, warn}; + +use super::super::{ + ApiError, AppState, RequireRunManagementTarget, RequiredUser, parse_run_id_path, run_records, +}; +use super::lifecycle::{ArchiveAction, queue_run, run_archive_operation, run_response}; +use super::runs::run_provenance; + +pub(super) fn routes() -> Router> { + Router::new() + .route("/runs/{id}/timeline", get(run_timeline)) + .route("/runs/{id}/fork", post(fork_run)) + .route("/runs/{id}/rewind", post(rewind_run)) + .route("/runs/{id}/retry", post(retry_run)) +} + +/// Which operation a fork is made for: what it requires of the source and +/// what it records. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum ForkKind { + Fork, + Rewind, + Retry, +} + +/// A fork made and queued. +struct ForkOutcome { + source_run_id: RunId, + new_run_id: RunId, + target: ResolvedForkTarget, + rerun_last: bool, +} + +async fn run_timeline( + _auth: RequiredUser, + State(state): State>, + Path(id): Path, +) -> Response { + let id = match parse_run_id_path(&id) { + Ok(id) => id, + Err(response) => return response, + }; + let projection = match run_records::require_projection(state.as_ref(), id).await { + Ok(projection) => projection, + Err(err) => return err.into_response(), + }; + match timeline(state.as_ref(), id).await { + Ok(timeline) => Json(timeline_response(&timeline, &projection)).into_response(), + Err(err) => err.into_response(), + } +} + +async fn fork_run( + RequireRunManagementTarget(id, actor): RequireRunManagementTarget, + State(state): State>, + headers: HeaderMap, + body: Option>, +) -> Response { + let target = match parse_fork_target(body.and_then(|Json(body)| body.target)) { + Ok(target) => target, + Err(err) => return err.into_response(), + }; + match fork_at(state.as_ref(), id, actor, &headers, ForkKind::Fork, target).await { + Ok(outcome) => ( + StatusCode::OK, + Json(api::ForkResponse { + source_run_id: outcome.source_run_id.to_string(), + new_run_id: outcome.new_run_id.to_string(), + target: outcome.target.response_target(), + checkpoint_sha: outcome.target.checkpoint_sha.clone(), + execution: outcome.target.position.execution, + firing: outcome.target.position.firing, + rerun_last: outcome.rerun_last, + }), + ) + .into_response(), + Err(err) => err.into_response(), + } +} + +async fn rewind_run( + RequireRunManagementTarget(id, actor): RequireRunManagementTarget, + State(state): State>, + headers: HeaderMap, + body: Option>, +) -> Response { + let target = match parse_fork_target(body.and_then(|Json(body)| body.target)) { + Ok(target) => target, + Err(err) => return err.into_response(), + }; + let outcome = match fork_at( + state.as_ref(), + id, + actor.clone(), + &headers, + ForkKind::Rewind, + target, + ) + .await + { + Ok(outcome) => outcome, + Err(err) => return err.into_response(), + }; + // The source is replaced: archived, and marked with what replaced it. + // A failed archive leaves the new run in place and says so. + let archived = run_archive_operation(state.as_ref(), &id, Some(actor), ArchiveAction::Archive) + .await + .map(|_| ()); + if archived.is_ok() { + let record = operations::superseded_record(outcome.new_run_id, &outcome.target); + if let Err(err) = run_records::append(state.as_ref(), id, record).await { + error!( + source_run_id = %id, + new_run_id = %outcome.new_run_id, + error = %err, + "the rewound run was archived but its superseded record was not written" + ); + } + } + let (status, archive_error) = match archived { + Ok(()) => (StatusCode::OK, None), + Err(err) => (StatusCode::MULTI_STATUS, Some(err.to_string())), + }; + ( + status, + Json(api::RewindResponse { + source_run_id: outcome.source_run_id.to_string(), + new_run_id: outcome.new_run_id.to_string(), + target: outcome.target.response_target(), + checkpoint_sha: outcome.target.checkpoint_sha.clone(), + execution: outcome.target.position.execution, + firing: outcome.target.position.firing, + archived: archive_error.is_none(), + archive_error, + }), + ) + .into_response() +} + +async fn retry_run( + RequireRunManagementTarget(id, actor): RequireRunManagementTarget, + State(state): State>, + headers: HeaderMap, +) -> Response { + match fork_at(state.as_ref(), id, actor, &headers, ForkKind::Retry, None).await { + Ok(outcome) => run_response(state.as_ref(), outcome.new_run_id, StatusCode::CREATED).await, + Err(err) => err.into_response(), + } +} + +/// The run's timeline: its checkpoint records, labelled through the +/// projector's fold state. +async fn timeline(state: &AppState, id: RunId) -> Result { + let checkpoints = state + .stores + .run_summaries + .platform_records() + .read_kind(&id, PlatformRecordKind::Checkpoint) + .await + .map_err(|err| ApiError::new(StatusCode::INTERNAL_SERVER_ERROR, err.to_string()))?; + let labels = petri_fork::stage_labels(&state.stores.run_summaries.pool(), id) + .await + .map_err(|err| ApiError::new(StatusCode::INTERNAL_SERVER_ERROR, err.to_string()))?; + Ok(RunTimeline::build(&checkpoints, &labels)) +} + +fn timeline_response( + timeline: &RunTimeline, + projection: &RunProjection, +) -> api::RunTimelineResponse { + api::RunTimelineResponse { + entries: timeline + .entries + .iter() + .map(|entry| api::TimelineEntryResponse { + ordinal: u64::try_from(entry.ordinal).unwrap_or(u64::MAX), + checkpoint_seq: entry.checkpoint_seq, + execution: entry.position.execution, + firing: entry.position.firing, + attempt: entry.position.attempt, + stage: entry.stage_id.clone(), + node_name: entry.node_name.clone(), + visit: entry.visit, + workspace: entry.workspace.clone(), + run_commit_sha: entry.run_commit_sha.clone(), + diff_summary: entry.diff_summary.as_ref().map(|summary| api::DiffSummary { + files_changed: summary.files_changed, + additions: summary.additions, + deletions: summary.deletions, + }), + }) + .collect(), + forked_from: projection.forked_from.as_ref().map(fork_origin_response), + } +} + +fn fork_origin_response(origin: &fabro_types::ForkOrigin) -> api::ForkOrigin { + api::ForkOrigin { + source_run_id: origin.source_run_id.to_string(), + execution: origin.execution, + firing: origin.firing, + rerun_last: origin.rerun_last, + } +} + +fn parse_fork_target(target: Option) -> Result, ApiError> { + target + .map(|target| { + target + .parse::() + .map_err(workflow_operation_error) + }) + .transpose() +} + +/// Make the fork: check the source, resolve the target, create the new +/// run's row, seed it from the source, and queue it in resume mode. +async fn fork_at( + state: &AppState, + id: RunId, + actor: Principal, + headers: &HeaderMap, + kind: ForkKind, + target: Option, +) -> Result { + let source = run_records::require_projection(state, id).await?; + match kind { + ForkKind::Fork => operations::ensure_forkable(&source, &id), + ForkKind::Rewind => operations::ensure_rewindable(&source, &id), + ForkKind::Retry => operations::ensure_retryable(&source, &id), + } + .map_err(workflow_operation_error)?; + let timeline = timeline(state, id).await?; + let entry = match kind { + ForkKind::Retry => timeline.latest(), + ForkKind::Fork | ForkKind::Rewind => timeline.resolve_or_latest(target.as_ref()), + } + .map_err(workflow_operation_error)?; + let resolved = ResolvedForkTarget::of(entry).map_err(workflow_operation_error)?; + let rerun_last = kind == ForkKind::Retry && operations::reruns_last(source.status); + + // A position Petri would refuse is refused before the new run exists. + let position = petri_fork::position(resolved.position.execution, resolved.position.firing); + let store: Arc = state + .petri_projector + .observe_store(Arc::new(SqliteRunStore::new(state.db_pool.clone()))); + petri_fork::check(store.as_ref(), id, position) + .await + .map_err(|err| fork_error(&err))?; + + let new_run_id = RunId::new(); + let storage = Storage::new(state.server_storage_dir()); + let source_run_dir = storage.run_scratch(&id).root().to_path_buf(); + let run_dir = storage.run_scratch(&new_run_id).root().to_path_buf(); + let provenance = (kind == ForkKind::Retry).then(|| run_provenance(headers, &actor)); + operations::persist_forked_run(state.store_ref().as_ref(), &operations::ForkedRunInput { + source: &source, + new_run_id, + run_dir: run_dir.clone(), + checkpoint_sha: resolved.checkpoint_sha.clone(), + provenance, + web_url: state.run_web_url(&new_run_id), + retried_from: (kind == ForkKind::Retry).then_some(id), + }) + .await + .map_err(workflow_operation_error)?; + + let seeded = petri_fork::fork(ForkRequest { + source: id, + fork: new_run_id, + source_run_dir: source_run_dir.join("petri"), + fork_run_dir: run_dir.join("petri"), + store, + records: Arc::new(SqlitePlatformRecords::new(Arc::clone( + &state.stores.run_summaries, + ))), + position, + rerun_last, + settings: source.spec.settings.run.clone(), + }) + .await; + if let Err(err) = seeded { + // The new run's row exists and holds nothing to continue from: it + // is reported failed with the reason, rather than left submitted. + let message = error_util::collect_chain(&err).join(": "); + warn!(source_run_id = %id, new_run_id = %new_run_id, error = %message, "the fork could not be seeded"); + if let Err(record_err) = run_records::lifecycle( + state, + new_run_id, + run_records::failed(FailureReason::WorkflowError, message.clone()), + ) + .await + { + error!(new_run_id = %new_run_id, error = %record_err, "the fork's failure was not recorded"); + } + return Err(fork_error(&err)); + } + + // The fork continues as a run left in flight does: queued in resume + // mode, on the projection its seeded records folded to. + let projection = run_records::require_projection(state, new_run_id).await?; + queue_run(state, new_run_id, &projection, true, actor).await?; + Ok(ForkOutcome { + source_run_id: id, + new_run_id, + target: resolved, + rerun_last, + }) +} + +/// A refused position is the caller's mistake; anything else is the +/// server's. +fn fork_error(err: &ForkError) -> ApiError { + let message = error_util::collect_chain(err).join(": "); + let status = match err { + ForkError::Refused(_) => StatusCode::BAD_REQUEST, + _ => StatusCode::INTERNAL_SERVER_ERROR, + }; + ApiError::new(status, message) +} + +fn workflow_operation_error(err: WorkflowError) -> ApiError { + match err { + WorkflowError::Parse(message) | WorkflowError::Validation(message) => { + ApiError::bad_request(message) + } + WorkflowError::Precondition(message) => ApiError::new(StatusCode::CONFLICT, message), + WorkflowError::RunNotFound(_) => ApiError::not_found("Run not found."), + err => ApiError::new(StatusCode::INTERNAL_SERVER_ERROR, err.to_string()), + } +} diff --git a/lib/apps/fabro-server/src/server/handler/mod.rs b/lib/apps/fabro-server/src/server/handler/mod.rs index 7f91e67ad..a596b049a 100644 --- a/lib/apps/fabro-server/src/server/handler/mod.rs +++ b/lib/apps/fabro-server/src/server/handler/mod.rs @@ -14,6 +14,7 @@ mod environments; pub(in crate::server) mod events; pub(in crate::server) mod graph; pub(in crate::server) mod lifecycle; +mod lineage; mod llm_sse; mod mcp_servers; mod models; @@ -214,6 +215,7 @@ pub(super) fn real_routes() -> Router> { .merge(sandbox::routes()) .merge(sandboxes::routes()) .merge(lifecycle::routes()) + .merge(lineage::routes()) .merge(steer::routes()) .merge(pair::routes()) .merge(petri::routes()) diff --git a/lib/foundation/fabro-client/src/client.rs b/lib/foundation/fabro-client/src/client.rs index 4b0e6a2e4..23d680694 100644 --- a/lib/foundation/fabro-client/src/client.rs +++ b/lib/foundation/fabro-client/src/client.rs @@ -78,6 +78,13 @@ pub struct SessionEventStream { buffered_events: VecDeque, } +/// What a rewind returned: the response, and the status that says whether +/// the source was archived. +pub struct RewindRunResult { + pub status: u16, + pub response: types::RewindResponse, +} + #[derive(Default)] struct ListStoreRunsOptions { parent_id: Option, @@ -1393,6 +1400,74 @@ impl Client { convert_type(response.into_inner()) } + /// The run's checkpoint timeline, and where it was forked from. + pub async fn run_timeline(&self, run_id: &RunId) -> Result { + let response = self + .send_api(|client| async move { + client + .get_run_timeline() + .id(run_id.to_string()) + .send() + .await + }) + .await?; + Ok(response.into_inner()) + } + + /// Fork the run at a checkpoint into a new run, started in resume mode. + pub async fn fork_run( + &self, + run_id: &RunId, + request: types::ForkRequest, + ) -> Result { + let response = self + .send_api(|client| async move { + client + .fork_run() + .id(run_id.to_string()) + .body(request) + .send() + .await + }) + .await?; + Ok(response.into_inner()) + } + + /// Rewind the run to a checkpoint: a fork that archives and supersedes + /// the source. The status says whether the archive succeeded (200) or + /// the new run was made without it (207). + pub async fn rewind_run( + &self, + run_id: &RunId, + request: types::RewindRequest, + ) -> Result { + let response = self + .send_api(|client| async move { + client + .rewind_run() + .id(run_id.to_string()) + .body(request) + .send() + .await + }) + .await?; + let status = response.status().as_u16(); + Ok(RewindRunResult { + status, + response: response.into_inner(), + }) + } + + /// Retry a terminal run from its last checkpoint: the new run. + pub async fn retry_run(&self, run_id: &RunId) -> Result { + let response = self + .send_api( + |client| async move { client.retry_run().id(run_id.to_string()).send().await }, + ) + .await?; + convert_type(response.into_inner()) + } + pub async fn resolve_run(&self, selector: &str) -> Result { let response = self .send_api(|client| async move { diff --git a/lib/foundation/fabro-client/src/lib.rs b/lib/foundation/fabro-client/src/lib.rs index ec0e71231..5625d60dc 100644 --- a/lib/foundation/fabro-client/src/lib.rs +++ b/lib/foundation/fabro-client/src/lib.rs @@ -12,8 +12,8 @@ pub use auth_store::{ AuthEntry, AuthStore, AuthStoreError, DevTokenEntry, LockError, OAuthEntry, StoredSubject, }; pub use client::{ - Client, RunStreamItemStream, RunStreamPage, SessionEventStream, TransportConnector, - apply_bearer_token_auth, + Client, RewindRunResult, RunStreamItemStream, RunStreamPage, SessionEventStream, + TransportConnector, apply_bearer_token_auth, }; pub use credential::{Credential, CredentialFallback}; pub use error::{ diff --git a/lib/packages/fabro-api-client/src/.openapi-generator/FILES b/lib/packages/fabro-api-client/src/.openapi-generator/FILES index 23e9fb7e9..c86d49538 100644 --- a/lib/packages/fabro-api-client/src/.openapi-generator/FILES +++ b/lib/packages/fabro-api-client/src/.openapi-generator/FILES @@ -164,6 +164,9 @@ models/failure-reason.ts models/file-checkpoint.ts models/file-diff.ts models/folder-run-target.ts +models/fork-origin.ts +models/fork-request.ts +models/fork-response.ts models/fork-source-ref.ts models/git-author-settings.ts models/git-context.ts @@ -364,6 +367,8 @@ models/repository-ref.ts models/resolved-automation-git-workflow-source.ts models/review-target-kind.ts models/review-target.ts +models/rewind-request.ts +models/rewind-response.ts models/root-response-urls.ts models/root-response.ts models/run-agent-settings.ts @@ -441,6 +446,7 @@ models/run-stream-item-kind.ts models/run-stream-item.ts models/run-superseded-by-props.ts models/run-target.ts +models/run-timeline-response.ts models/run-timestamps.ts models/run-timing.ts models/run-usage-stage.ts @@ -487,7 +493,6 @@ models/server-sandbox-provider-settings.ts models/server-sandbox-settings.ts models/server-scheduler-settings.ts models/server-settings.ts -models/server-slate-db-settings.ts models/server-storage-settings.ts models/server-web-settings.ts models/session-detail.ts @@ -539,6 +544,7 @@ models/system-repair-run-issue.ts models/system-repair-runs-response.ts models/system-resources-response.ts models/system-run-counts.ts +models/timeline-entry-response.ts models/tls-mode.ts models/todo-list-kind.ts models/todo-list-projection.ts diff --git a/lib/packages/fabro-api-client/src/api/runs-api.ts b/lib/packages/fabro-api-client/src/api/runs-api.ts index c530c2810..1ab4f4805 100644 --- a/lib/packages/fabro-api-client/src/api/runs-api.ts +++ b/lib/packages/fabro-api-client/src/api/runs-api.ts @@ -42,6 +42,10 @@ import type { DenyRunRequest } from '../models'; // @ts-ignore import type { ErrorResponse } from '../models'; // @ts-ignore +import type { ForkRequest } from '../models'; +// @ts-ignore +import type { ForkResponse } from '../models'; +// @ts-ignore import type { LinkRunPullRequestRequest } from '../models'; // @ts-ignore import type { MergeRunPullRequestRequest } from '../models'; @@ -60,12 +64,18 @@ import type { PullRequestResponse } from '../models'; // @ts-ignore import type { RenderWorkflowGraphRequest } from '../models'; // @ts-ignore +import type { RewindRequest } from '../models'; +// @ts-ignore +import type { RewindResponse } from '../models'; +// @ts-ignore import type { Run } from '../models'; // @ts-ignore import type { RunIntent } from '../models'; // @ts-ignore import type { RunManifest } from '../models'; // @ts-ignore +import type { RunTimelineResponse } from '../models'; +// @ts-ignore import type { StartRunRequest } from '../models'; // @ts-ignore import type { UpdateRunParentRequest } from '../models'; @@ -535,6 +545,49 @@ export const RunsApiAxiosParamCreator = function (configuration?: Configuration) options: localVarRequestOptions, }; }, + /** + * Creates a new run from a checkpoint of the source run and starts it. The new run holds the source\'s records up to the checkpoint\'s position and continues from there in a fresh workspace restored to the checkpoint\'s commit. The source run is left untouched. A checkpoint inside a parallel branch cannot be forked at; fork at the parallel stage instead. + * @summary Fork Run + * @param {string} id Unique run identifier (ULID). + * @param {ForkRequest} [forkRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + forkRun: async (id: string, forkRequest?: ForkRequest, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'id' is not null or undefined + assertParamExists('forkRun', 'id', id) + const localVarPath = `/api/v1/runs/{id}/fork` + .replace(`{${"id"}}`, encodeURIComponent(String(id))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication SessionCookie required + + // authentication BearerAuth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + localVarHeaderParameter['Content-Type'] = 'application/json'; + localVarHeaderParameter['Accept'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(forkRequest, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * Returns the stored pull request record for a run plus live GitHub details when available. * @summary Get Run Pull Request @@ -615,6 +668,46 @@ export const RunsApiAxiosParamCreator = function (configuration?: Configuration) options: localVarRequestOptions, }; }, + /** + * Returns the run\'s checkpoints in the order they were recorded, each at its Petri position with the commit it made and the stage it belongs to, and where the run was forked from when it is a fork. + * @summary Get Run Timeline + * @param {string} id Unique run identifier (ULID). + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getRunTimeline: async (id: string, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'id' is not null or undefined + assertParamExists('getRunTimeline', 'id', id) + const localVarPath = `/api/v1/runs/{id}/timeline` + .replace(`{${"id"}}`, encodeURIComponent(String(id))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication SessionCookie required + + // authentication BearerAuth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + localVarHeaderParameter['Accept'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * Links a run under an orchestration parent. Parent links are mutable for all run states, including archived and terminal runs. * @summary Link Run Parent @@ -1070,6 +1163,89 @@ export const RunsApiAxiosParamCreator = function (configuration?: Configuration) options: localVarRequestOptions, }; }, + /** + * Creates a new run from the terminal source run\'s last checkpoint and starts it. When the source failed on a stage, that stage runs again on the files of the stage before it; otherwise the new run continues from the last checkpoint as it stands. The new run records `retried_from` and `fork_source_ref`; the source run is left unchanged. Active and archived runs are not retryable. + * @summary Retry Run + * @param {string} id Unique run identifier (ULID). + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + retryRun: async (id: string, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'id' is not null or undefined + assertParamExists('retryRun', 'id', id) + const localVarPath = `/api/v1/runs/{id}/retry` + .replace(`{${"id"}}`, encodeURIComponent(String(id))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication SessionCookie required + + // authentication BearerAuth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + localVarHeaderParameter['Accept'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * Creates a new run from a checkpoint of a terminal source run and starts it, then archives the source run and records `run.superseded_by` on it. Returns 207 when the new run was created but the source archive step failed. + * @summary Rewind Run + * @param {string} id Unique run identifier (ULID). + * @param {RewindRequest} [rewindRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + rewindRun: async (id: string, rewindRequest?: RewindRequest, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'id' is not null or undefined + assertParamExists('rewindRun', 'id', id) + const localVarPath = `/api/v1/runs/{id}/rewind` + .replace(`{${"id"}}`, encodeURIComponent(String(id))); + // use dummy base URL string because the URL constructor only accepts absolute URLs. + const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); + let baseOptions; + if (configuration) { + baseOptions = configuration.baseOptions; + } + + const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options}; + const localVarHeaderParameter = {} as any; + const localVarQueryParameter = {} as any; + + // authentication SessionCookie required + + // authentication BearerAuth required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + localVarHeaderParameter['Content-Type'] = 'application/json'; + localVarHeaderParameter['Accept'] = 'application/json'; + + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + localVarRequestOptions.data = serializeDataIfNeeded(rewindRequest, localVarRequestOptions, configuration) + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, /** * Validates runtime readiness for a workflow manifest without creating a run. * @summary Validate Workflow Manifest @@ -1555,6 +1731,20 @@ export const RunsApiFp = function(configuration?: Configuration) { const localVarOperationServerBasePath = operationServerMap['RunsApi.denyRun']?.[localVarOperationServerIndex]?.url; return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); }, + /** + * Creates a new run from a checkpoint of the source run and starts it. The new run holds the source\'s records up to the checkpoint\'s position and continues from there in a fresh workspace restored to the checkpoint\'s commit. The source run is left untouched. A checkpoint inside a parallel branch cannot be forked at; fork at the parallel stage instead. + * @summary Fork Run + * @param {string} id Unique run identifier (ULID). + * @param {ForkRequest} [forkRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async forkRun(id: string, forkRequest?: ForkRequest, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.forkRun(id, forkRequest, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['RunsApi.forkRun']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, /** * Returns the stored pull request record for a run plus live GitHub details when available. * @summary Get Run Pull Request @@ -1581,6 +1771,19 @@ export const RunsApiFp = function(configuration?: Configuration) { const localVarOperationServerBasePath = operationServerMap['RunsApi.getRunPullRequestCreation']?.[localVarOperationServerIndex]?.url; return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); }, + /** + * Returns the run\'s checkpoints in the order they were recorded, each at its Petri position with the commit it made and the stage it belongs to, and where the run was forked from when it is a fork. + * @summary Get Run Timeline + * @param {string} id Unique run identifier (ULID). + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getRunTimeline(id: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getRunTimeline(id, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['RunsApi.getRunTimeline']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, /** * Links a run under an orchestration parent. Parent links are mutable for all run states, including archived and terminal runs. * @summary Link Run Parent @@ -1721,6 +1924,33 @@ export const RunsApiFp = function(configuration?: Configuration) { const localVarOperationServerBasePath = operationServerMap['RunsApi.retrieveRunGraphSource']?.[localVarOperationServerIndex]?.url; return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); }, + /** + * Creates a new run from the terminal source run\'s last checkpoint and starts it. When the source failed on a stage, that stage runs again on the files of the stage before it; otherwise the new run continues from the last checkpoint as it stands. The new run records `retried_from` and `fork_source_ref`; the source run is left unchanged. Active and archived runs are not retryable. + * @summary Retry Run + * @param {string} id Unique run identifier (ULID). + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async retryRun(id: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.retryRun(id, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['RunsApi.retryRun']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, + /** + * Creates a new run from a checkpoint of a terminal source run and starts it, then archives the source run and records `run.superseded_by` on it. Returns 207 when the new run was created but the source archive step failed. + * @summary Rewind Run + * @param {string} id Unique run identifier (ULID). + * @param {RewindRequest} [rewindRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async rewindRun(id: string, rewindRequest?: RewindRequest, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.rewindRun(id, rewindRequest, options); + const localVarOperationServerIndex = configuration?.serverIndex ?? 0; + const localVarOperationServerBasePath = operationServerMap['RunsApi.rewindRun']?.[localVarOperationServerIndex]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath); + }, /** * Validates runtime readiness for a workflow manifest without creating a run. * @summary Validate Workflow Manifest @@ -1949,6 +2179,17 @@ export const RunsApiFactory = function (configuration?: Configuration, basePath? denyRun(id: string, denyRunRequest?: DenyRunRequest, options?: RawAxiosRequestConfig): AxiosPromise { return localVarFp.denyRun(id, denyRunRequest, options).then((request) => request(axios, basePath)); }, + /** + * Creates a new run from a checkpoint of the source run and starts it. The new run holds the source\'s records up to the checkpoint\'s position and continues from there in a fresh workspace restored to the checkpoint\'s commit. The source run is left untouched. A checkpoint inside a parallel branch cannot be forked at; fork at the parallel stage instead. + * @summary Fork Run + * @param {string} id Unique run identifier (ULID). + * @param {ForkRequest} [forkRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + forkRun(id: string, forkRequest?: ForkRequest, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.forkRun(id, forkRequest, options).then((request) => request(axios, basePath)); + }, /** * Returns the stored pull request record for a run plus live GitHub details when available. * @summary Get Run Pull Request @@ -1969,6 +2210,16 @@ export const RunsApiFactory = function (configuration?: Configuration, basePath? getRunPullRequestCreation(id: string, options?: RawAxiosRequestConfig): AxiosPromise { return localVarFp.getRunPullRequestCreation(id, options).then((request) => request(axios, basePath)); }, + /** + * Returns the run\'s checkpoints in the order they were recorded, each at its Petri position with the commit it made and the stage it belongs to, and where the run was forked from when it is a fork. + * @summary Get Run Timeline + * @param {string} id Unique run identifier (ULID). + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getRunTimeline(id: string, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.getRunTimeline(id, options).then((request) => request(axios, basePath)); + }, /** * Links a run under an orchestration parent. Parent links are mutable for all run states, including archived and terminal runs. * @summary Link Run Parent @@ -2079,6 +2330,27 @@ export const RunsApiFactory = function (configuration?: Configuration, basePath? retrieveRunGraphSource(id: string, options?: RawAxiosRequestConfig): AxiosPromise { return localVarFp.retrieveRunGraphSource(id, options).then((request) => request(axios, basePath)); }, + /** + * Creates a new run from the terminal source run\'s last checkpoint and starts it. When the source failed on a stage, that stage runs again on the files of the stage before it; otherwise the new run continues from the last checkpoint as it stands. The new run records `retried_from` and `fork_source_ref`; the source run is left unchanged. Active and archived runs are not retryable. + * @summary Retry Run + * @param {string} id Unique run identifier (ULID). + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + retryRun(id: string, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.retryRun(id, options).then((request) => request(axios, basePath)); + }, + /** + * Creates a new run from a checkpoint of a terminal source run and starts it, then archives the source run and records `run.superseded_by` on it. Returns 207 when the new run was created but the source archive step failed. + * @summary Rewind Run + * @param {string} id Unique run identifier (ULID). + * @param {RewindRequest} [rewindRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + rewindRun(id: string, rewindRequest?: RewindRequest, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.rewindRun(id, rewindRequest, options).then((request) => request(axios, basePath)); + }, /** * Validates runtime readiness for a workflow manifest without creating a run. * @summary Validate Workflow Manifest @@ -2292,6 +2564,18 @@ export class RunsApi extends BaseAPI { return RunsApiFp(this.configuration).denyRun(id, denyRunRequest, options).then((request) => request(this.axios, this.basePath)); } + /** + * Creates a new run from a checkpoint of the source run and starts it. The new run holds the source\'s records up to the checkpoint\'s position and continues from there in a fresh workspace restored to the checkpoint\'s commit. The source run is left untouched. A checkpoint inside a parallel branch cannot be forked at; fork at the parallel stage instead. + * @summary Fork Run + * @param {string} id Unique run identifier (ULID). + * @param {ForkRequest} [forkRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + public forkRun(id: string, forkRequest?: ForkRequest, options?: RawAxiosRequestConfig) { + return RunsApiFp(this.configuration).forkRun(id, forkRequest, options).then((request) => request(this.axios, this.basePath)); + } + /** * Returns the stored pull request record for a run plus live GitHub details when available. * @summary Get Run Pull Request @@ -2314,6 +2598,17 @@ export class RunsApi extends BaseAPI { return RunsApiFp(this.configuration).getRunPullRequestCreation(id, options).then((request) => request(this.axios, this.basePath)); } + /** + * Returns the run\'s checkpoints in the order they were recorded, each at its Petri position with the commit it made and the stage it belongs to, and where the run was forked from when it is a fork. + * @summary Get Run Timeline + * @param {string} id Unique run identifier (ULID). + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + public getRunTimeline(id: string, options?: RawAxiosRequestConfig) { + return RunsApiFp(this.configuration).getRunTimeline(id, options).then((request) => request(this.axios, this.basePath)); + } + /** * Links a run under an orchestration parent. Parent links are mutable for all run states, including archived and terminal runs. * @summary Link Run Parent @@ -2434,6 +2729,29 @@ export class RunsApi extends BaseAPI { return RunsApiFp(this.configuration).retrieveRunGraphSource(id, options).then((request) => request(this.axios, this.basePath)); } + /** + * Creates a new run from the terminal source run\'s last checkpoint and starts it. When the source failed on a stage, that stage runs again on the files of the stage before it; otherwise the new run continues from the last checkpoint as it stands. The new run records `retried_from` and `fork_source_ref`; the source run is left unchanged. Active and archived runs are not retryable. + * @summary Retry Run + * @param {string} id Unique run identifier (ULID). + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + public retryRun(id: string, options?: RawAxiosRequestConfig) { + return RunsApiFp(this.configuration).retryRun(id, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * Creates a new run from a checkpoint of a terminal source run and starts it, then archives the source run and records `run.superseded_by` on it. Returns 207 when the new run was created but the source archive step failed. + * @summary Rewind Run + * @param {string} id Unique run identifier (ULID). + * @param {RewindRequest} [rewindRequest] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + public rewindRun(id: string, rewindRequest?: RewindRequest, options?: RawAxiosRequestConfig) { + return RunsApiFp(this.configuration).rewindRun(id, rewindRequest, options).then((request) => request(this.axios, this.basePath)); + } + /** * Validates runtime readiness for a workflow manifest without creating a run. * @summary Validate Workflow Manifest diff --git a/lib/packages/fabro-api-client/src/models/fork-origin.ts b/lib/packages/fabro-api-client/src/models/fork-origin.ts new file mode 100644 index 000000000..d5d43ada8 --- /dev/null +++ b/lib/packages/fabro-api-client/src/models/fork-origin.ts @@ -0,0 +1,25 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Fabro Run API + * HTTP API for managing Fabro workflow run executions. + * + * The version of the OpenAPI document: 0.2.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + + +/** + * Where a forked run came from: the source run, the position (an execution and a firing of its root invocation) the source\'s records were kept up to, and whether that firing runs again in the fork. + */ +export interface ForkOrigin { + 'source_run_id': string; + 'execution': number; + 'firing': number; + 'rerun_last': boolean; +} diff --git a/lib/packages/fabro-api-client/src/models/fork-request.ts b/lib/packages/fabro-api-client/src/models/fork-request.ts new file mode 100644 index 000000000..4f68ca46f --- /dev/null +++ b/lib/packages/fabro-api-client/src/models/fork-request.ts @@ -0,0 +1,25 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Fabro Run API + * HTTP API for managing Fabro workflow run executions. + * + * The version of the OpenAPI document: 0.2.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + + +/** + * Request body for creating a new run from a source run checkpoint. + */ +export interface ForkRequest { + /** + * Optional checkpoint target such as `@2`, `build`, or `build@1`. Defaults to the latest checkpoint. + */ + 'target'?: string | null; +} diff --git a/lib/packages/fabro-api-client/src/models/fork-response.ts b/lib/packages/fabro-api-client/src/models/fork-response.ts new file mode 100644 index 000000000..e1f80328f --- /dev/null +++ b/lib/packages/fabro-api-client/src/models/fork-response.ts @@ -0,0 +1,43 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Fabro Run API + * HTTP API for managing Fabro workflow run executions. + * + * The version of the OpenAPI document: 0.2.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + + +/** + * Response returned after creating and starting a forked run. + */ +export interface ForkResponse { + 'source_run_id': string; + 'new_run_id': string; + /** + * The checkpoint the fork was resolved to, as `@ordinal`. + */ + 'target': string; + /** + * The commit the new run\'s workspace starts on. + */ + 'checkpoint_sha': string; + /** + * The Petri execution of the fork position. + */ + 'execution': number; + /** + * The Petri firing of the fork position. + */ + 'firing': number; + /** + * Whether the position\'s stage runs again in the new run. + */ + 'rerun_last': boolean; +} diff --git a/lib/packages/fabro-api-client/src/models/index.ts b/lib/packages/fabro-api-client/src/models/index.ts index f5ab81f36..a7bdff705 100644 --- a/lib/packages/fabro-api-client/src/models/index.ts +++ b/lib/packages/fabro-api-client/src/models/index.ts @@ -135,6 +135,9 @@ export * from './failure-reason'; export * from './file-checkpoint'; export * from './file-diff'; export * from './folder-run-target'; +export * from './fork-origin'; +export * from './fork-request'; +export * from './fork-response'; export * from './fork-source-ref'; export * from './git-author-settings'; export * from './git-context'; @@ -334,6 +337,8 @@ export * from './repository-ref'; export * from './resolved-automation-git-workflow-source'; export * from './review-target'; export * from './review-target-kind'; +export * from './rewind-request'; +export * from './rewind-response'; export * from './root-response'; export * from './root-response-urls'; export * from './run'; @@ -412,6 +417,7 @@ export * from './run-stream-item'; export * from './run-stream-item-kind'; export * from './run-superseded-by-props'; export * from './run-target'; +export * from './run-timeline-response'; export * from './run-timestamps'; export * from './run-timing'; export * from './run-usage'; @@ -508,6 +514,7 @@ export * from './system-repair-run-issue'; export * from './system-repair-runs-response'; export * from './system-resources-response'; export * from './system-run-counts'; +export * from './timeline-entry-response'; export * from './tls-mode'; export * from './todo-list-kind'; export * from './todo-list-projection'; diff --git a/lib/packages/fabro-api-client/src/models/rewind-request.ts b/lib/packages/fabro-api-client/src/models/rewind-request.ts new file mode 100644 index 000000000..ade0f4f4c --- /dev/null +++ b/lib/packages/fabro-api-client/src/models/rewind-request.ts @@ -0,0 +1,25 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Fabro Run API + * HTTP API for managing Fabro workflow run executions. + * + * The version of the OpenAPI document: 0.2.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + + +/** + * Request body for creating a replacement run from a source run checkpoint. + */ +export interface RewindRequest { + /** + * Optional checkpoint target such as `@2`, `build`, or `build@1`. Defaults to the latest checkpoint. + */ + 'target'?: string | null; +} diff --git a/lib/packages/fabro-api-client/src/models/rewind-response.ts b/lib/packages/fabro-api-client/src/models/rewind-response.ts new file mode 100644 index 000000000..94e626fab --- /dev/null +++ b/lib/packages/fabro-api-client/src/models/rewind-response.ts @@ -0,0 +1,32 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Fabro Run API + * HTTP API for managing Fabro workflow run executions. + * + * The version of the OpenAPI document: 0.2.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + + +/** + * Response returned after rewind creates and starts a new run. + */ +export interface RewindResponse { + 'source_run_id': string; + 'new_run_id': string; + /** + * The checkpoint the rewind was resolved to, as `@ordinal`. + */ + 'target': string; + 'checkpoint_sha': string; + 'execution': number; + 'firing': number; + 'archived': boolean; + 'archive_error'?: string | null; +} diff --git a/lib/packages/fabro-api-client/src/models/run-projection.ts b/lib/packages/fabro-api-client/src/models/run-projection.ts index 8496e8b71..d0209f38c 100644 --- a/lib/packages/fabro-api-client/src/models/run-projection.ts +++ b/lib/packages/fabro-api-client/src/models/run-projection.ts @@ -21,6 +21,9 @@ import type { CheckpointRecord } from './checkpoint-record'; import type { Conclusion } from './conclusion'; // May contain unused imports in some cases // @ts-ignore +import type { ForkOrigin } from './fork-origin'; +// May contain unused imports in some cases +// @ts-ignore import type { GitIdentity } from './git-identity'; // May contain unused imports in some cases // @ts-ignore @@ -86,6 +89,7 @@ export interface RunProjection { * Source run ID when this run was created by manual retry. */ 'retried_from'?: string | null; + 'forked_from'?: ForkOrigin | null; 'git_identity'?: GitIdentity | null; 'pending_interviews': { [key: string]: PendingInterviewRecord; }; /** diff --git a/lib/packages/fabro-api-client/src/models/run-timeline-response.ts b/lib/packages/fabro-api-client/src/models/run-timeline-response.ts new file mode 100644 index 000000000..c8b14a7ea --- /dev/null +++ b/lib/packages/fabro-api-client/src/models/run-timeline-response.ts @@ -0,0 +1,29 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Fabro Run API + * HTTP API for managing Fabro workflow run executions. + * + * The version of the OpenAPI document: 0.2.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +// May contain unused imports in some cases +// @ts-ignore +import type { ForkOrigin } from './fork-origin'; +// May contain unused imports in some cases +// @ts-ignore +import type { TimelineEntryResponse } from './timeline-entry-response'; + +/** + * The run\'s checkpoints in order, and its fork origin when it is a fork. + */ +export interface RunTimelineResponse { + 'entries': Array; + 'forked_from'?: ForkOrigin | null; +} diff --git a/lib/packages/fabro-api-client/src/models/timeline-entry-response.ts b/lib/packages/fabro-api-client/src/models/timeline-entry-response.ts new file mode 100644 index 000000000..9d7d96ed5 --- /dev/null +++ b/lib/packages/fabro-api-client/src/models/timeline-entry-response.ts @@ -0,0 +1,56 @@ +/* tslint:disable */ +/* eslint-disable */ +/** + * Fabro Run API + * HTTP API for managing Fabro workflow run executions. + * + * The version of the OpenAPI document: 0.2.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + + +// May contain unused imports in some cases +// @ts-ignore +import type { DiffSummary } from './diff-summary'; + +/** + * One checkpoint of a run. + */ +export interface TimelineEntryResponse { + /** + * 1-based, in the order the checkpoints were recorded; the `@ordinal` a fork target names. + */ + 'ordinal': number; + /** + * The checkpoint record\'s position among the run\'s platform records. + */ + 'checkpoint_seq': number; + /** + * The Petri execution the checkpoint belongs to. + */ + 'execution': number; + /** + * The Petri firing the checkpoint belongs to. + */ + 'firing': number; + /** + * The attempt of the firing whose files the commit holds, from 1. + */ + 'attempt': number; + /** + * The stage id (`node@visit`) when the projection shows the stage. + */ + 'stage'?: string | null; + 'node_name': string; + 'visit': number; + /** + * The Petri workspace id the commit was made in. + */ + 'workspace'?: string | null; + 'run_commit_sha'?: string | null; + 'diff_summary'?: DiffSummary | null; +}