rename compiler NanoError and fix cluster config warnings

This commit is contained in:
aaltshuler 2026-06-17 23:44:24 +03:00
parent 5243c048aa
commit 4590c91f9d
17 changed files with 499 additions and 333 deletions

View file

@ -160,7 +160,7 @@ pub async fn plan_config_dir(config_dir: impl AsRef<Path>) -> PlanOutput {
// Plan is read-only: pending sidecars are reported, never acted on
// (RFC-004 open question 3 keeps read-only commands warn-only).
warn_pending_recovery_sidecars(&desired.config_dir, &mut diagnostics);
warn_pending_recovery_sidecars(&backend, &mut diagnostics).await;
let mut prior_resources = BTreeMap::new();
let mut prior_state: Option<ClusterState> = None;
@ -1260,7 +1260,7 @@ pub async fn status_config_dir(config_dir: impl AsRef<Path>) -> StatusOutput {
backend
.observe_lock(&mut observations, &mut diagnostics)
.await;
warn_pending_recovery_sidecars(&parsed.config_dir, &mut diagnostics);
warn_pending_recovery_sidecars(&backend, &mut diagnostics).await;
let mut resource_digests = BTreeMap::new();
let mut resource_statuses = BTreeMap::new();

View file

@ -321,6 +321,32 @@ impl ClusterStore {
// ---- recovery sidecars ----
pub(crate) async fn list_recovery_sidecar_locations(
&self,
diagnostics: &mut Vec<Diagnostic>,
) -> Vec<String> {
let dir_uri = self.uri(CLUSTER_RECOVERIES_DIR);
let mut uris = match self.adapter.list_dir(&dir_uri).await {
Ok(uris) => uris,
Err(err) => {
diagnostics.push(Diagnostic::warning(
"recovery_sidecar_read_error",
CLUSTER_RECOVERIES_DIR,
format!("could not list '{CLUSTER_RECOVERIES_DIR}': {err}"),
));
return Vec::new();
}
};
uris.retain(|uri| uri.ends_with(".json"));
uris.sort();
uris.into_iter()
.map(|uri| match uri.rsplit('/').next() {
Some(name) => format!("{}/{name}", self.display(CLUSTER_RECOVERIES_DIR)),
None => uri,
})
.collect()
}
pub(crate) async fn list_recovery_sidecars(
&self,
diagnostics: &mut Vec<Diagnostic>,

View file

@ -427,21 +427,14 @@ pub(crate) async fn mark_approvals_consumed(backend: &ClusterStore, approval_ids
}
/// Read-only commands report pending sidecars without acting on them.
pub(crate) fn warn_pending_recovery_sidecars(config_dir: &Path, diagnostics: &mut Vec<Diagnostic>) {
let recoveries_dir = config_dir.join(CLUSTER_RECOVERIES_DIR);
let Ok(entries) = fs::read_dir(&recoveries_dir) else {
return;
};
let mut names: Vec<String> = entries
.flatten()
.filter(|entry| entry.path().extension().is_some_and(|ext| ext == "json"))
.map(|entry| entry.file_name().to_string_lossy().into_owned())
.collect();
names.sort();
for name in names {
pub(crate) async fn warn_pending_recovery_sidecars(
backend: &ClusterStore,
diagnostics: &mut Vec<Diagnostic>,
) {
for location in backend.list_recovery_sidecar_locations(diagnostics).await {
diagnostics.push(Diagnostic::warning(
"cluster_recovery_pending",
format!("{CLUSTER_RECOVERIES_DIR}/{name}"),
location,
"a recovery sidecar from an interrupted apply is pending; the next state-mutating command will classify it",
));
}

View file

@ -3375,6 +3375,67 @@ policies:
);
}
#[tokio::test]
async fn read_only_commands_warn_on_pending_recovery_sidecar_in_storage_root() {
let dir = fixture();
let storage = tempfile::tempdir().unwrap();
let storage_path = storage.path().to_string_lossy().to_string();
let mut config = fs::read_to_string(dir.path().join(CLUSTER_CONFIG_FILE)).unwrap();
config = config.replace(
"version: 1\n",
&format!("version: 1\nstorage: {storage_path}\n"),
);
fs::write(dir.path().join(CLUSTER_CONFIG_FILE), config).unwrap();
let desired = validate_config_dir(dir.path());
assert!(desired.ok, "{:?}", desired.diagnostics);
let schema_digest = desired
.resource_digests
.get("schema.knowledge")
.unwrap()
.clone();
let graph_composite = graph_digest(
"knowledge",
Some(&schema_digest),
Some(&BTreeMap::new()),
None,
None,
);
write_state_resources(
storage.path(),
&[
("graph.knowledge", graph_composite.as_str()),
("schema.knowledge", schema_digest.as_str()),
],
);
write_create_sidecar(storage.path(), "knowledge", "irrelevant", "01STORAGE");
let status = status_config_dir(dir.path()).await;
assert!(status.ok, "{:?}", status.diagnostics);
assert!(
status
.diagnostics
.iter()
.any(|diagnostic| diagnostic.code == "cluster_recovery_pending"
&& diagnostic.path.contains("01STORAGE.json")),
"{:?}",
status.diagnostics
);
let plan = plan_config_dir(dir.path()).await;
assert!(plan.ok, "{:?}", plan.diagnostics);
assert!(
plan.diagnostics
.iter()
.any(|diagnostic| diagnostic.code == "cluster_recovery_pending"
&& diagnostic.path.contains("01STORAGE.json")),
"{:?}",
plan.diagnostics
);
assert!(!dir.path().join(CLUSTER_RECOVERIES_DIR).exists());
}
#[tokio::test]
async fn plan_annotates_apply_dispositions() {
let dir = fixture();