CI: remove k8s stages, fix the Docker-level pipeline green
ci / gates (push) Successful in 5s
ci / frontend (push) Successful in 23s
ci / rust (push) Failing after 27s
ci / e2e (push) Has been skipped

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:
Omar Sobh
2026-06-26 18:15:31 -07:00
co-authored by Claude Opus 4.8
parent a36b2c87ac
commit 3554a3aaf2
47 changed files with 1264 additions and 446 deletions
+20 -4
View File
@@ -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
+48 -9
View File
@@ -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);
+2 -1
View File
@@ -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);