use std::path::Path;
use std::process::ExitCode;
use anyhow::{bail, Result};
use serde::Serialize;
use crate::output::CommandReport;
use crate::paths::state::StateLayout;
use crate::profile;
use crate::repo::marker as repo_marker;
use crate::state::compiled as compiled_state;
use crate::state::escalation as escalation_state;
use crate::state::machine_presence;
use crate::state::machine_registry as pod_identity;
use crate::state::policy_projection;
use crate::state::runtime::{self as runtime_state, LoadedRuntimeState, RuntimeTextSurface};
use crate::state::session as session_state;
use crate::state::session_gates;
#[derive(Serialize)]
pub struct RuntimeStateExportReport {
command: &'static str,
ok: bool,
path: String,
profile: String,
project_id: String,
locality_id: String,
export: RuntimeStateExportView,
}
#[derive(Serialize)]
struct RuntimeStateExportView {
kind: &'static str,
load_mode: &'static str,
derived: bool,
disposable: bool,
notice: &'static str,
native_workspace_runtime: NativeCloneRuntimeView,
native_clone_runtime: NativeCloneRuntimeView,
sources: RuntimeSourceSurfacesView,
execution_gates: session_gates::ExecutionGatesView,
recovery: runtime_state::RuntimeRecoveryView,
policy_projection: policy_projection::PolicyProjectionView,
machine_presence: machine_presence::MachinePresenceView,
execution_context: machine_presence::MachineExecutionContextView,
takeover_preconditions: machine_presence::MachineTakeoverPreconditionsView,
#[serde(skip_serializing_if = "Option::is_none")]
projection_target: Option<&'static str>,
#[serde(skip_serializing_if = "Option::is_none")]
projection_format: Option<&'static str>,
#[serde(skip_serializing_if = "Option::is_none")]
projection: Option<compiled_state::CompiledStateStore>,
#[serde(skip_serializing_if = "Option::is_none")]
symbolic_projection: Option<compiled_state::SymbolicProjectionView>,
#[serde(skip_serializing_if = "Option::is_none")]
bundle_projection: Option<compiled_state::BundleProjectionView>,
#[serde(skip_serializing_if = "Option::is_none")]
state: Option<runtime_state::RuntimeState>,
}
#[derive(Serialize)]
struct NativeCloneRuntimeView {
path: String,
status: &'static str,
}
#[derive(Serialize)]
struct HandoffExportView {
path: String,
status: &'static str,
}
#[derive(Serialize)]
struct RuntimeSourceSurfacesView {
profile_memory: RuntimeSourceSurfaceView,
project_memory: RuntimeSourceSurfaceView,
locality_memory: RuntimeSourceSurfaceView,
work_stream_memory: RuntimeSourceSurfaceView,
branch_memory: RuntimeSourceSurfaceView,
workspace_memory: RuntimeSourceSurfaceView,
clone_memory: RuntimeSourceSurfaceView,
execution_gates: RuntimeSourceSurfaceView,
handoff: RuntimeSourceSurfaceView,
handoff_export: HandoffExportView,
checkpoint: RuntimeSourceSurfaceView,
working_buffer: RuntimeSourceSurfaceView,
}
#[derive(Serialize)]
struct RuntimeSourceSurfaceView {
kind: &'static str,
path: String,
status: &'static str,
source_role: &'static str,
#[serde(skip_serializing_if = "Option::is_none")]
migrated_from: Option<String>,
}
impl CommandReport for RuntimeStateExportReport {
fn exit_code(&self) -> ExitCode {
ExitCode::SUCCESS
}
fn render_text(&self) {
match (self.export.projection_target, self.export.projection_format) {
(Some(target), Some(format)) => println!(
"Derived `{}` `{}` projection preview for profile `{}` in linked project `{}`.",
format, target, self.profile, self.project_id
),
(Some(target), None) => println!(
"Derived `{}` projection preview for profile `{}` in linked project `{}`.",
target, self.profile, self.project_id
),
(None, _) => println!(
"Derived runtime-state export for profile `{}` in linked project `{}`.",
self.profile, self.project_id
),
};
println!("{}", self.export.notice);
match &self.export.bundle_projection {
Some(bundle) => print!("{}", bundle.markdown),
None => println!(
"{}",
serde_json::to_string_pretty(&self.export)
.expect("runtime-state export serialization")
),
}
}
}
pub fn run(
repo_root: &Path,
explicit_profile: Option<&str>,
projection_target: Option<compiled_state::ProjectionTarget>,
projection_format: Option<compiled_state::ProjectionFormat>,
) -> Result<RuntimeStateExportReport> {
let profile = profile::resolve(explicit_profile)?;
let layout = StateLayout::resolve(repo_root, profile.clone())?;
ensure_profile_exists(&layout, repo_root)?;
let locality_id = ensure_repo_linked(repo_root)?;
let runtime = runtime_state::load_runtime_state(repo_root, &layout, &locality_id)?;
let active_pod_identity = pod_identity::resolve_active_identity(&layout)?;
let policy_sources = policy_projection::read_policy_sources(
&layout,
&locality_id,
active_pod_identity.as_ref(),
)?;
let session_context = policy_projection::load_session_context(&layout)?;
let escalation_entries = escalation_state::load_for_layout(&layout)?;
let escalation_view = escalation_state::build_view(&layout, &escalation_entries);
let tracked_session = session_state::load_for_layout(&layout)?;
let now_epoch_s = session_state::now_epoch_s()?;
let session_lifecycle = tracked_session
.as_ref()
.map(|state| session_state::lifecycle_projection(state, now_epoch_s, None, None));
let machine_identity = pod_identity::resolve_machine_identity_view(&layout)?;
let machine_presence = machine_presence::resolve_machine_presence_view(&layout)?;
let execution_context = machine_presence::build_execution_context_view(
&layout,
Some(&locality_id),
&machine_identity,
&machine_presence,
session_lifecycle.as_ref(),
);
let takeover_preconditions = machine_presence::build_takeover_preconditions_view(
&execution_context,
session_lifecycle.as_ref(),
escalation_view.blocking_count > 0,
escalation_view.blocking_count,
true,
);
if projection_target.is_none() && projection_format.is_some() {
bail!("`--projection-format` requires `--projection-target`");
}
let projection_format = projection_target
.map(|_| projection_format.unwrap_or(compiled_state::ProjectionFormat::Narrative));
let projection = match (projection_target, projection_format) {
(Some(target), Some(compiled_state::ProjectionFormat::Narrative)) => Some((
target,
compiled_state::preview_for_target_with_cache(&layout, &runtime, target)?.value,
)),
_ => None,
};
let symbolic_projection = match (projection_target, projection_format) {
(Some(target), Some(compiled_state::ProjectionFormat::Symbolic)) => Some((
target,
compiled_state::preview_symbolic_for_target_with_cache(&layout, &runtime, target)?
.value,
)),
_ => None,
};
let bundle_projection = match (projection_target, projection_format) {
(Some(target), Some(compiled_state::ProjectionFormat::Bundle)) => Some((
target,
compiled_state::preview_bundle_for_target_with_cache(&layout, &runtime, target)?.value,
)),
_ => None,
};
let projection_kind =
if projection.is_some() || symbolic_projection.is_some() || bundle_projection.is_some() {
"compiled_projection_preview"
} else {
"debug_dump"
};
let projection_notice = if projection.is_some()
|| symbolic_projection.is_some()
|| bundle_projection.is_some()
{
"Derived projection preview only. This output is disposable and must not be treated as authored project truth."
} else {
"Derived debug export only. This dump is disposable and must not be treated as authored project truth."
};
let projection_target_label = projection_target.map(compiled_state::ProjectionTarget::as_str);
let projection_format_label = projection_format.map(compiled_state::ProjectionFormat::as_str);
let compiled_projection = projection
.as_ref()
.map(|(_, projection)| projection.clone());
let symbolic_projection = symbolic_projection
.as_ref()
.map(|(_, projection)| projection.clone());
let bundle_projection = bundle_projection
.as_ref()
.map(|(_, projection)| projection.clone());
let native_clone_runtime_status = match runtime_state::native_handoff_exists(&layout)? {
true => "loaded",
false => "missing",
};
let sources = build_sources_view(&layout, &runtime);
let recovery = runtime_state::recovery_view(&runtime.recovery);
let policy_projection =
policy_projection::build_view(&policy_sources, session_context, &escalation_view);
let runtime_state = if compiled_projection.is_none()
&& symbolic_projection.is_none()
&& bundle_projection.is_none()
{
Some(runtime.state)
} else {
None
};
Ok(RuntimeStateExportReport {
command: "runtime-state-export",
ok: true,
path: repo_root.display().to_string(),
profile: profile.to_string(),
project_id: locality_id.clone(),
locality_id,
export: RuntimeStateExportView {
kind: projection_kind,
load_mode: "native_canonical",
derived: true,
disposable: true,
notice: projection_notice,
native_workspace_runtime: NativeCloneRuntimeView {
path: layout.state_db_path().display().to_string(),
status: native_clone_runtime_status,
},
native_clone_runtime: NativeCloneRuntimeView {
path: layout.state_db_path().display().to_string(),
status: native_clone_runtime_status,
},
sources,
execution_gates: runtime.execution_gates.view.clone(),
recovery,
policy_projection,
machine_presence,
execution_context,
takeover_preconditions,
projection_target: projection_target_label,
projection_format: projection_format_label,
projection: compiled_projection,
symbolic_projection,
bundle_projection,
state: runtime_state,
},
})
}
fn build_sources_view(
layout: &StateLayout,
runtime: &LoadedRuntimeState,
) -> RuntimeSourceSurfacesView {
let export_path = layout.handoff_path();
let export_status = if export_path.is_file() {
"present"
} else {
"absent"
};
RuntimeSourceSurfacesView {
profile_memory: surface_view(&runtime.sources.profile_memory),
project_memory: surface_view(&runtime.sources.locality_memory),
locality_memory: surface_view(&runtime.sources.locality_memory),
work_stream_memory: surface_view(&runtime.sources.branch_memory),
branch_memory: surface_view(&runtime.sources.branch_memory),
workspace_memory: surface_view(&runtime.sources.clone_memory),
clone_memory: surface_view(&runtime.sources.clone_memory),
execution_gates: surface_view(&runtime.sources.execution_gates),
handoff: surface_view(&runtime.sources.handoff),
handoff_export: HandoffExportView {
path: export_path.display().to_string(),
status: export_status,
},
checkpoint: recovery_surface_view(&runtime.recovery.sources.checkpoint),
working_buffer: recovery_surface_view(&runtime.recovery.sources.working_buffer),
}
}
fn surface_view(surface: &RuntimeTextSurface) -> RuntimeSourceSurfaceView {
RuntimeSourceSurfaceView {
kind: surface.kind,
path: surface.path.display().to_string(),
status: surface.status.as_str(),
source_role: if surface.status.is_loaded_native() {
"native_runtime_projection"
} else {
"authored_adapter"
},
migrated_from: surface.migrated_from.clone(),
}
}
fn recovery_surface_view(
surface: &runtime_state::RuntimeRecoverySurface,
) -> RuntimeSourceSurfaceView {
RuntimeSourceSurfaceView {
kind: surface.kind,
path: surface.path.display().to_string(),
status: surface.status,
source_role: "workspace_local_recovery",
migrated_from: None,
}
}
fn ensure_profile_exists(layout: &StateLayout, repo_root: &Path) -> Result<()> {
let profile_root = layout.profile_root();
if profile_root.is_dir() {
return Ok(());
}
bail!(
"profile `{}` does not exist at {}; bootstrap it with `ccd attach --path {}` before using `ccd runtime-state export`",
layout.profile(),
profile_root.display(),
repo_root.display()
)
}
fn ensure_repo_linked(repo_root: &Path) -> Result<String> {
let Some(marker) = repo_marker::load(repo_root)? else {
bail!(
"repo is not linked: {} is missing; run `ccd attach --path {}` or `ccd link --path {}` first",
repo_root.join(repo_marker::MARKER_FILE).display(),
repo_root.display(),
repo_root.display()
)
};
Ok(marker.locality_id)
}