Return compact activation summaries instead of discovery schemas
ci / check (push) Failing after 2m40s

Card: SAU-158
This commit is contained in:
SAU-158 implementer
2026-10-26 22:24:43 +00:00
parent a14f051f97
commit 9a8c143856
3 changed files with 221 additions and 13 deletions
+94 -8
View File
@@ -6,6 +6,44 @@ use crate::{
use serde_json::{Value, json};
use std::{collections::BTreeMap, path::PathBuf};
/// Method descriptions plus their reachable local references, without expanding cycles.
fn methods(document: &Value) -> BTreeMap<String, Value> {
fn references(value: &Value, document: &Value, found: &mut BTreeMap<String, Value>) {
match value {
Value::Object(object) => {
if let Some(reference) = object.get("$ref").and_then(Value::as_str)
&& let Some(pointer) = reference.strip_prefix('#')
&& !found.contains_key(reference)
&& let Some(target) = document.pointer(pointer)
{
found.insert(reference.into(), target.clone());
references(target, document, found);
}
for child in object.values() {
references(child, document, found);
}
}
Value::Array(array) => {
for child in array {
references(child, document, found);
}
}
_ => {}
}
}
document["methods"]
.as_array()
.into_iter()
.flatten()
.filter_map(|method| {
let name = method["name"].as_str()?;
let mut found = BTreeMap::new();
references(method, document, &mut found);
Some((name.into(), json!({"method":method,"references":found})))
})
.collect()
}
struct Candidate {
plugin: String,
copy: PathBuf,
@@ -113,7 +151,31 @@ impl Host {
.map(|candidate| candidate.plugin.clone())
.collect();
self.refresh_graph_except(&names).await?;
let previous = self.0.graph.lock().unwrap().plugins.clone();
let mut previous = self.0.graph.lock().unwrap().plugins.clone();
// Startup discovery may not yet have visited a replaced plugin. Discover
// its immutable old copy for the diff, but never prevent repairing it.
for candidate in candidates.iter() {
if let Some(old) = previous.get_mut(&candidate.plugin)
&& old.document.is_null()
&& old.error.is_none()
{
if old.hash == candidate.interface.hash {
old.document = candidate.interface.document.clone();
continue;
}
let directory = self.0.world.plugin_dir(&candidate.plugin);
if let Ok((bytes, hash)) = read_hashed(directory.join("plugin.wasm")).await {
let copy = directory.join(format!("discover-{}.wasm", uuid::Uuid::now_v7()));
if tokio::fs::write(&copy, bytes).await.is_ok()
&& let Ok(interface) =
self.discover_copy(&candidate.plugin, &copy, hash).await
{
*old = interface;
}
let _ = tokio::fs::remove_file(copy).await;
}
}
}
let mut proposed = previous.clone();
for candidate in candidates.iter() {
proposed.insert(candidate.plugin.clone(), candidate.interface.clone());
@@ -148,7 +210,7 @@ impl Host {
);
}
}
self.install_candidates(candidates, previous, proposed)
self.install_candidates(candidates, previous, proposed, params["force"] != true)
.await
}
/// Installs staged files and snapshots all old admissions under the shared write gate.
@@ -157,6 +219,7 @@ impl Host {
candidates: &[Candidate],
previous: BTreeMap<String, Interface>,
proposed: BTreeMap<String, Interface>,
checked: bool,
) -> RpcResult {
let admission = self.0.admission.write().await;
// Every candidate already has a temporary name before any installed file changes.
@@ -183,12 +246,35 @@ impl Host {
.map_err(io_error)?;
}
let mut upgrades = Vec::new();
let mut documents = serde_json::Map::new();
let mut summaries = serde_json::Map::new();
for candidate in candidates {
documents.insert(
candidate.plugin.clone(),
candidate.interface.document.clone(),
);
let old = previous.get(&candidate.plugin);
let before = methods(old.map(|old| &old.document).unwrap_or(&Value::Null));
let after = methods(&candidate.interface.document);
let added: Vec<_> = after
.keys()
.filter(|name| !before.contains_key(*name))
.collect();
let removed: Vec<_> = before
.keys()
.filter(|name| !after.contains_key(*name))
.collect();
let changed: Vec<_> = after
.iter()
.filter(|(name, method)| before.get(*name).is_some_and(|old| old != *method))
.map(|(name, _)| name)
.collect();
summaries.insert(candidate.plugin.clone(), json!({
"plugin": candidate.plugin,
"wasm": format!("/plugins/{}/plugin.wasm", candidate.plugin),
"hash": candidate.interface.hash,
"previous": old.map(|old| &old.hash).filter(|hash| !hash.is_empty()),
"method_count": candidate.interface.document["methods"].as_array().map_or(0, Vec::len),
"added": added,
"removed": removed,
"changed": changed,
"interface_check": if checked { "passed" } else { "skipped" },
}));
if let Some(old) = previous
.get(&candidate.plugin)
.filter(|old| !old.hash.is_empty() && old.hash != candidate.interface.hash)
@@ -216,6 +302,6 @@ impl Host {
for upgrade in upgrades {
self.begin_upgrade(upgrade);
}
Ok(Value::Object(documents))
Ok(Value::Object(summaries))
}
}