use std::path::Path;
use std::sync::Arc;
use memstead_base::engine::error::SchemaSourceDiagnostic;
use memstead_base::engine::lifecycle::bump_backend_schema_pin;
use memstead_base::engine::{SchemaResolver, load_workspace_schemas};
use memstead_base::workspace_store::WorkspaceStoreAdapter;
use memstead_base::{BootError, Engine, EngineError, FileWorkspaceStore};
#[derive(Debug, serde::Serialize)]
pub struct BelowBootSetSchema {
pub mem: String,
pub schema_pin: String,
pub config_updated: bool,
pub conformance_checked: bool,
}
fn below_boot_schema_catalogue(
workspace_root: &Path,
) -> Result<Vec<Arc<memstead_schema::Schema>>, EngineError> {
let fixed_dir = workspace_root.join(".memstead").join("schemas");
let mut catalogue = load_workspace_schemas(Some(fixed_dir.as_path()))?;
use memstead_base::schema_source::SchemaSource as _;
match crate::mem_repo_schemas::GitBranchSchemaSource::for_workspace(workspace_root)
.read_schemas()
{
Ok(schemas) => catalogue.extend(schemas),
Err(e) => {
tracing::warn!(
"below-boot repair: could not read schemas from `__MEMSTEAD:schemas/` at {}: {e}; \
resolving against the folder dir and built-ins only",
workspace_root.display()
);
}
}
catalogue.extend(
memstead_schema::builtins::load_builtin_schemas()
.map_err(|e| EngineError::SchemaResolverInit(e.to_string()))?,
);
Ok(catalogue)
}
pub fn set_mem_schema_below_boot(
workspace_root: &Path,
mem: &str,
target: &memstead_schema::SchemaRef,
) -> Result<BelowBootSetSchema, BootError> {
let mut workspace = crate::workspace_store::load_workspace_description(workspace_root)?;
let mount_idx = workspace
.mounts
.iter()
.position(|m| m.mem == mem)
.ok_or_else(|| BootError::Engine(EngineError::UnknownMem(mem.to_string())))?;
let catalogue = below_boot_schema_catalogue(workspace_root).map_err(BootError::Engine)?;
SchemaResolver::new(&catalogue)
.resolve(target)
.map_err(|_sources| {
BootError::Engine(
EngineError::SchemaNotFound {
mem: mem.to_string(),
pin: target.as_display(),
sources: SchemaSourceDiagnostic::for_failed_pin(
&target.name,
&target.version,
&catalogue,
),
install_hint: None,
}
.with_schema_install_probe(Some(workspace_root)),
)
})?;
let backend = crate::storage::instantiate_full_backend(&workspace.mounts[mount_idx])
.map_err(|e| BootError::Engine(EngineError::Mem(e.to_string())))?;
let config_updated = bump_backend_schema_pin(backend.as_ref(), target)
.map_err(BootError::Engine)?
.is_some();
workspace.mounts[mount_idx].schema = Some(target.clone());
workspace.mounts[mount_idx].migration_target = None;
if matches!(
memstead_base::detect_layout(workspace_root),
memstead_base::Layout::New
) {
FileWorkspaceStore::new().save_state(workspace_root, &workspace)?;
}
Ok(BelowBootSetSchema {
mem: mem.to_string(),
schema_pin: target.as_display(),
config_updated,
conformance_checked: false,
})
}
pub fn install_schema_below_boot(
workspace_root: &Path,
name: &str,
version: &str,
files: &[(String, Vec<u8>)],
) -> Result<String, BootError> {
Engine::validate_schema_package(name, version, files).map_err(BootError::Engine)?;
let gitdir = crate::workspace_store::load_workspace_description(workspace_root)?
.mounts
.iter()
.find_map(|m| match &m.storage {
memstead_base::MountStorage::GitBranch { gitdir, .. } => Some(gitdir.clone()),
_ => None,
})
.unwrap_or_else(|| workspace_root.join("mem-repo").join(".git"));
if !gitdir.exists() {
return Err(BootError::Engine(EngineError::Mem(format!(
"schema install requires a mem-repo workspace — no git-branch mount and no \
mem-repo gitdir at {}",
gitdir.display()
))));
}
let files = memstead_schema::loader::with_format_marker(files.to_vec());
let outcome =
crate::storage_memstead::write_schema_to_memstead_ref(&gitdir, name, version, &files)
.map_err(|e| {
BootError::Engine(EngineError::Mem(format!(
"schema install onto `__MEMSTEAD:schemas/{name}@{version}` failed: {e}"
)))
})?;
Ok(outcome.commit_sha)
}