mirror of
https://github.com/ModernRelay/omnigraph.git
synced 2026-06-27 02:39:38 +02:00
feat(cli): no-default-graph errors list candidate graphs (RFC-011 D7) (#245)
When a server/cluster scope resolves with no --graph and no default_graph, the CLI auto-uses a sole graph (cluster) or errors listing the candidate graph ids (cluster catalog; multi-graph server via best-effort GET /graphs), never a silent pick. GraphClient::resolve becomes async; flat/single-graph servers and happy paths are unaffected.
This commit is contained in:
parent
b395757e21
commit
1bc0ea6b51
10 changed files with 262 additions and 62 deletions
|
|
@ -28,7 +28,7 @@ mod store;
|
|||
use store::{ClusterStore, StateLockGuard, StateSnapshot};
|
||||
pub use types::*;
|
||||
use types::*;
|
||||
pub use serve::{ServingGraph, ServingPolicy, ServingQuery, ServingSnapshot, cluster_root_for_graph_uri, read_serving_snapshot, read_serving_snapshot_from_storage, resolve_graph_storage_uri};
|
||||
pub use serve::{ServingGraph, ServingPolicy, ServingQuery, ServingSnapshot, cluster_graph_ids, cluster_root_for_graph_uri, read_serving_snapshot, read_serving_snapshot_from_storage, resolve_graph_storage_uri};
|
||||
use config::{QueriesDecl, observe_declared_graphs, validate_cluster_header, future_field_diagnostics, initial_import_state, observe_live_graph, preview_schema_migration, state_resource_digests, graph_address, policy_address, query_address, schema_address, load_desired, normalize_policy_target, parse_cluster_config, resolve_config_path, resolve_query_decls, validate_id, validate_query_source};
|
||||
use diff::{FailedGraphOrigin, ResourceKind, append_policy_binding_changes, approved_resources, classify_changes, compute_approvals, compute_blast_radius, demote_dependents_of_failed_graphs, diff_resources, resource_kind};
|
||||
use sweep::{mark_approvals_consumed, record_approval_consumed, sweep_recovery_sidecars, tombstone_graph_subtree, warn_pending_recovery_sidecars};
|
||||
|
|
|
|||
|
|
@ -112,28 +112,13 @@ pub async fn cluster_root_for_graph_uri(graph_uri: &str) -> Option<String> {
|
|||
/// `cluster` is a config directory or a storage-root URI (`s3://…`, config-free),
|
||||
/// mirroring the server's `--cluster` dispatch.
|
||||
pub async fn resolve_graph_storage_uri(cluster: &str, graph_id: &str) -> Result<String, Diagnostic> {
|
||||
let backend = if cluster.contains("://") {
|
||||
ClusterStore::for_storage_root(cluster)?
|
||||
} else {
|
||||
ClusterStore::for_config_dir(Path::new(cluster))
|
||||
};
|
||||
let backend = open_cluster_backend(cluster)?;
|
||||
let mut observations = backend.observations();
|
||||
let snapshot = backend.read_state(&mut observations).await?;
|
||||
let state = snapshot.state.ok_or_else(|| {
|
||||
Diagnostic::error(
|
||||
"cluster_state_missing",
|
||||
CLUSTER_STATE_FILE,
|
||||
format!("cluster `{cluster}` has no applied state; run `cluster apply` first"),
|
||||
)
|
||||
})?;
|
||||
let state = snapshot.state.ok_or_else(|| missing_state_diagnostic(cluster))?;
|
||||
let address = format!("graph.{graph_id}");
|
||||
if !state.applied_revision.resources.contains_key(&address) {
|
||||
let applied: Vec<&str> = state
|
||||
.applied_revision
|
||||
.resources
|
||||
.keys()
|
||||
.filter_map(|a| a.strip_prefix("graph."))
|
||||
.collect();
|
||||
let applied = applied_graph_ids(&state);
|
||||
return Err(Diagnostic::error(
|
||||
"graph_not_applied",
|
||||
address,
|
||||
|
|
@ -147,6 +132,46 @@ pub async fn resolve_graph_storage_uri(cluster: &str, graph_id: &str) -> Result<
|
|||
Ok(backend.graph_root(graph_id))
|
||||
}
|
||||
|
||||
/// List the graph ids applied in a cluster's served state (sorted). Reads the
|
||||
/// ledger only — no catalog validation — like `resolve_graph_storage_uri`, so
|
||||
/// it works on a degraded cluster. Used to enumerate candidates when no
|
||||
/// `--graph` is selected (RFC-011 Decision 7).
|
||||
pub async fn cluster_graph_ids(cluster: &str) -> Result<Vec<String>, Diagnostic> {
|
||||
let backend = open_cluster_backend(cluster)?;
|
||||
let mut observations = backend.observations();
|
||||
let snapshot = backend.read_state(&mut observations).await?;
|
||||
let state = snapshot.state.ok_or_else(|| missing_state_diagnostic(cluster))?;
|
||||
Ok(applied_graph_ids(&state))
|
||||
}
|
||||
|
||||
fn open_cluster_backend(cluster: &str) -> Result<ClusterStore, Diagnostic> {
|
||||
if cluster.contains("://") {
|
||||
ClusterStore::for_storage_root(cluster)
|
||||
} else {
|
||||
Ok(ClusterStore::for_config_dir(Path::new(cluster)))
|
||||
}
|
||||
}
|
||||
|
||||
fn missing_state_diagnostic(cluster: &str) -> Diagnostic {
|
||||
Diagnostic::error(
|
||||
"cluster_state_missing",
|
||||
CLUSTER_STATE_FILE,
|
||||
format!("cluster `{cluster}` has no applied state; run `cluster apply` first"),
|
||||
)
|
||||
}
|
||||
|
||||
fn applied_graph_ids(state: &crate::types::ClusterState) -> Vec<String> {
|
||||
let mut ids: Vec<String> = state
|
||||
.applied_revision
|
||||
.resources
|
||||
.keys()
|
||||
.filter_map(|a| a.strip_prefix("graph."))
|
||||
.map(str::to_string)
|
||||
.collect();
|
||||
ids.sort();
|
||||
ids
|
||||
}
|
||||
|
||||
/// Split `<root>/graphs/<id>.omni` → `<root>`, gating on the exact cluster
|
||||
/// graph-layout shape (a single `<id>` segment, no nested path). `None` for
|
||||
/// anything else — no I/O is done for non-cluster-shaped URIs.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue