CI: remove k8s stages, fix the Docker-level pipeline green
Survey + fixes so the pipeline passes at the Docker level (no k8s).
- Remove k8s: drop the `sandbox-k8s` job (kind/Calico/--features k8s-tests) and the
"Helm chart lints" gate step. release.yml was already k8s-clean.
- Rust job:
- `cargo fmt --all` — fix pre-existing formatting drift (fmt --check was failing).
- clippy -D warnings: fix 3 lib warnings (cm-brain sort_by_key→Reverse, cm-api
fleet.rs doc list indentation, node_rules map_or→is_none_or).
- Regenerate the .sqlx offline cache (was missing the cm-runtime run_loop test
query → offline compile failed). DB-backed tests use testcontainers at runtime.
- Set SQLX_OFFLINE=true on the rust + e2e jobs so query! macros compile against
the committed cache deterministically (no DB needed at compile time).
- Frontend job:
- Fix the 1 ESLint error (useAgentTelemetry: no setState-synchronously-in-effect;
tag the slice with agentId + derive null on mismatch).
- Fix 2 stale panel-params tests (`terminal` is a valid app id now; assert the
current APP_IDS + use a genuinely-unknown id for the reject case).
Verified locally: fmt clean, clippy --all-targets -D warnings clean (offline),
frontend lint 0 errors, tsc clean, 86/86 frontend tests pass, build OK.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
a36b2c87ac
commit
3554a3aaf2
@@ -15,16 +15,28 @@ fn id(i: usize) -> String {
|
||||
|
||||
fn star(n: usize, kind: EdgeKind) -> Vec<Edge> {
|
||||
(1..n)
|
||||
.map(|i| Edge { from: id(0), to: id(i), kind })
|
||||
.map(|i| Edge {
|
||||
from: id(0),
|
||||
to: id(i),
|
||||
kind,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn chain(n: usize, close: bool) -> Vec<Edge> {
|
||||
let mut edges: Vec<Edge> = (0..n.saturating_sub(1))
|
||||
.map(|i| Edge { from: id(i), to: id(i + 1), kind: EdgeKind::PipesTo })
|
||||
.map(|i| Edge {
|
||||
from: id(i),
|
||||
to: id(i + 1),
|
||||
kind: EdgeKind::PipesTo,
|
||||
})
|
||||
.collect();
|
||||
if close && n > 1 {
|
||||
edges.push(Edge { from: id(n - 1), to: id(0), kind: EdgeKind::PipesTo });
|
||||
edges.push(Edge {
|
||||
from: id(n - 1),
|
||||
to: id(0),
|
||||
kind: EdgeKind::PipesTo,
|
||||
});
|
||||
}
|
||||
edges
|
||||
}
|
||||
@@ -33,7 +45,11 @@ fn complete(n: usize, kind: EdgeKind) -> Vec<Edge> {
|
||||
let mut edges = Vec::new();
|
||||
for i in 0..n {
|
||||
for j in (i + 1)..n {
|
||||
edges.push(Edge { from: id(i), to: id(j), kind });
|
||||
edges.push(Edge {
|
||||
from: id(i),
|
||||
to: id(j),
|
||||
kind,
|
||||
});
|
||||
}
|
||||
}
|
||||
edges
|
||||
|
||||
@@ -66,7 +66,11 @@ pub fn metrics(g: &TopologyGraph) -> GraphMetrics {
|
||||
} else {
|
||||
undirected_edges as f64 / (n as f64 * (n as f64 - 1.0) / 2.0)
|
||||
};
|
||||
let avg_degree = if n == 0 { 0.0 } else { sum_deg as f64 / n as f64 };
|
||||
let avg_degree = if n == 0 {
|
||||
0.0
|
||||
} else {
|
||||
sum_deg as f64 / n as f64
|
||||
};
|
||||
let hub_dominance = if sum_deg == 0 {
|
||||
0.0
|
||||
} else {
|
||||
@@ -92,7 +96,11 @@ pub fn metrics(g: &TopologyGraph) -> GraphMetrics {
|
||||
let possible = k * (k - 1) / 2;
|
||||
clustering_sum += links as f64 / possible as f64;
|
||||
}
|
||||
let clustering = if n == 0 { 0.0 } else { clustering_sum / n as f64 };
|
||||
let clustering = if n == 0 {
|
||||
0.0
|
||||
} else {
|
||||
clustering_sum / n as f64
|
||||
};
|
||||
|
||||
let (components, diameter) = components_and_diameter(&adj);
|
||||
|
||||
@@ -188,13 +196,21 @@ pub fn classify(g: &TopologyGraph) -> Classification {
|
||||
let is_tree = connected && m.undirected_edges + 1 == n && n >= 2;
|
||||
let is_path = is_tree && deg1 == 2 && deg2 == n.saturating_sub(2);
|
||||
let is_star = is_tree && n >= 3 && m.max_degree == n - 1;
|
||||
let is_cycle = connected && n >= 3 && degrees.iter().all(|&d| d == 2) && m.undirected_edges == n;
|
||||
let is_cycle =
|
||||
connected && n >= 3 && degrees.iter().all(|&d| d == 2) && m.undirected_edges == n;
|
||||
|
||||
let swarm_fit = (1.0 - (m.density - 0.45).abs() * 2.0).clamp(0.0, 1.0) * 0.7;
|
||||
|
||||
let scores: [(TopologyKind, f64); 12] = [
|
||||
(Mesh, m.density),
|
||||
(Flat, if m.undirected_edges == 0 { 1.0 } else { (1.0 - m.density) * 0.4 }),
|
||||
(
|
||||
Flat,
|
||||
if m.undirected_edges == 0 {
|
||||
1.0
|
||||
} else {
|
||||
(1.0 - m.density) * 0.4
|
||||
},
|
||||
),
|
||||
(Pipeline, if is_path { 0.95 } else { 0.0 }),
|
||||
(Ring, if is_cycle { 0.95 } else { 0.0 }),
|
||||
(HubSpoke, if is_star { 0.90 } else { m.hub_dominance * 0.5 }),
|
||||
@@ -251,26 +267,45 @@ mod tests {
|
||||
let g = graph(
|
||||
TopologyKind::Hierarchical,
|
||||
&["r", "a", "b", "a1", "a2", "b1", "b2"],
|
||||
&[("r", "a"), ("r", "b"), ("a", "a1"), ("a", "a2"), ("b", "b1"), ("b", "b2")],
|
||||
&[
|
||||
("r", "a"),
|
||||
("r", "b"),
|
||||
("a", "a1"),
|
||||
("a", "a2"),
|
||||
("b", "b1"),
|
||||
("b", "b2"),
|
||||
],
|
||||
);
|
||||
assert_eq!(classify(&g).primary, TopologyKind::Hierarchical);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn line_is_pipeline() {
|
||||
let g = graph(TopologyKind::Pipeline, &["a", "b", "c", "d"], &[("a", "b"), ("b", "c"), ("c", "d")]);
|
||||
let g = graph(
|
||||
TopologyKind::Pipeline,
|
||||
&["a", "b", "c", "d"],
|
||||
&[("a", "b"), ("b", "c"), ("c", "d")],
|
||||
);
|
||||
assert_eq!(classify(&g).primary, TopologyKind::Pipeline);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cycle_is_ring() {
|
||||
let g = graph(TopologyKind::Ring, &["a", "b", "c", "d"], &[("a", "b"), ("b", "c"), ("c", "d"), ("d", "a")]);
|
||||
let g = graph(
|
||||
TopologyKind::Ring,
|
||||
&["a", "b", "c", "d"],
|
||||
&[("a", "b"), ("b", "c"), ("c", "d"), ("d", "a")],
|
||||
);
|
||||
assert_eq!(classify(&g).primary, TopologyKind::Ring);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn star_is_hub_spoke() {
|
||||
let g = graph(TopologyKind::HubSpoke, &["h", "s1", "s2", "s3", "s4"], &[("h", "s1"), ("h", "s2"), ("h", "s3"), ("h", "s4")]);
|
||||
let g = graph(
|
||||
TopologyKind::HubSpoke,
|
||||
&["h", "s1", "s2", "s3", "s4"],
|
||||
&[("h", "s1"), ("h", "s2"), ("h", "s3"), ("h", "s4")],
|
||||
);
|
||||
assert_eq!(classify(&g).primary, TopologyKind::HubSpoke);
|
||||
}
|
||||
|
||||
@@ -297,7 +332,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn metrics_are_sane_for_a_path() {
|
||||
let g = graph(TopologyKind::Pipeline, &["a", "b", "c"], &[("a", "b"), ("b", "c")]);
|
||||
let g = graph(
|
||||
TopologyKind::Pipeline,
|
||||
&["a", "b", "c"],
|
||||
&[("a", "b"), ("b", "c")],
|
||||
);
|
||||
let m = metrics(&g);
|
||||
assert_eq!(m.order, 3);
|
||||
assert_eq!(m.undirected_edges, 2);
|
||||
|
||||
@@ -132,7 +132,8 @@ impl TopologyGraph {
|
||||
.collect();
|
||||
let mut adj = vec![HashSet::new(); self.nodes.len()];
|
||||
for e in &self.edges {
|
||||
if let (Some(&a), Some(&b)) = (index_of.get(e.from.as_str()), index_of.get(e.to.as_str()))
|
||||
if let (Some(&a), Some(&b)) =
|
||||
(index_of.get(e.from.as_str()), index_of.get(e.to.as_str()))
|
||||
{
|
||||
if a != b {
|
||||
adj[a].insert(b);
|
||||
|
||||
Reference in New Issue
Block a user