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]>
128 lines
3.5 KiB
Rust
128 lines
3.5 KiB
Rust
//! Canonical topology builders: instantiate a [`TopologyGraph`] of any
|
|
//! [`TopologyKind`] from a list of roles.
|
|
//!
|
|
//! This is the "template catalog" — it turns a kind + roles into a runnable
|
|
//! graph, and it is the candidate generator for the evolution/search layer.
|
|
//! Node ids are `n0..nk`; `roles[i]` is the role of `ni`.
|
|
|
|
use crate::graph::{Edge, EdgeKind, Node, TopologyGraph};
|
|
use crate::kind::TopologyKind;
|
|
use crate::TopologyError;
|
|
|
|
fn id(i: usize) -> String {
|
|
format!("n{i}")
|
|
}
|
|
|
|
fn star(n: usize, kind: EdgeKind) -> Vec<Edge> {
|
|
(1..n)
|
|
.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,
|
|
})
|
|
.collect();
|
|
if close && n > 1 {
|
|
edges.push(Edge {
|
|
from: id(n - 1),
|
|
to: id(0),
|
|
kind: EdgeKind::PipesTo,
|
|
});
|
|
}
|
|
edges
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
/// Build a canonical graph of `kind` over `roles` (node `ni` plays `roles[i]`).
|
|
pub fn build(kind: TopologyKind, roles: &[&str]) -> Result<TopologyGraph, TopologyError> {
|
|
if roles.is_empty() {
|
|
return Err(TopologyError::Empty);
|
|
}
|
|
let n = roles.len();
|
|
let nodes: Vec<Node> = roles
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(i, r)| Node::new(id(i), *r))
|
|
.collect();
|
|
|
|
use TopologyKind::*;
|
|
let edges = match kind {
|
|
Hierarchical => star(n, EdgeKind::DelegatesTo),
|
|
HubSpoke | StarMoe => star(n, EdgeKind::RoutesTo),
|
|
Market => star(n, EdgeKind::BidsTo),
|
|
Pipeline => chain(n, false),
|
|
Ring => chain(n, true),
|
|
Mesh => complete(n, EdgeKind::PeersWith),
|
|
Blackboard => complete(n, EdgeKind::ReadsWrites),
|
|
// Peer/parallel kinds wire structure at run time, not via edges.
|
|
Flat | Holacratic | Swarm | Debate => Vec::new(),
|
|
};
|
|
|
|
TopologyGraph::new(kind, nodes, edges)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::classify;
|
|
|
|
#[test]
|
|
fn builds_every_kind() {
|
|
let roles = ["coordinator", "researcher", "writer"];
|
|
for kind in TopologyKind::ALL {
|
|
let g = build(kind, &roles).expect("build");
|
|
assert_eq!(g.order(), 3);
|
|
assert_eq!(g.kind, kind);
|
|
g.validate().expect("valid");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn canonical_shapes_classify_back() {
|
|
let roles = ["a", "b", "c", "d"];
|
|
assert_eq!(
|
|
classify(&build(TopologyKind::Hierarchical, &roles).unwrap()).primary,
|
|
TopologyKind::HubSpoke // a 1→(n-1) star reads as hub/star structurally
|
|
);
|
|
assert_eq!(
|
|
classify(&build(TopologyKind::Pipeline, &roles).unwrap()).primary,
|
|
TopologyKind::Pipeline
|
|
);
|
|
assert_eq!(
|
|
classify(&build(TopologyKind::Ring, &roles).unwrap()).primary,
|
|
TopologyKind::Ring
|
|
);
|
|
assert_eq!(
|
|
classify(&build(TopologyKind::Mesh, &roles).unwrap()).primary,
|
|
TopologyKind::Mesh
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn empty_roles_error() {
|
|
assert_eq!(build(TopologyKind::Flat, &[]), Err(TopologyError::Empty));
|
|
}
|
|
}
|