feat(cli): three-way unresolved-namespace hint (#241 T3)

A project-namespaced (::) unknown type id now hints by tier: outside
any project the existing "no Aura.toml was found" text stays
byte-identical; inside a data-only project the hint names the missing
node crate and the attach verb (aura nodes new); a loaded node crate
gets no hint (the vocabulary error carries the message).

refs #241
This commit is contained in:
2026-07-12 15:05:56 +02:00
parent 1f6b55a8e1
commit a41705b1c2
2 changed files with 38 additions and 10 deletions
+17 -10
View File
@@ -376,18 +376,25 @@ fn gang_fault_prose(e: &CompileError) -> String {
}
}
/// (#185) When an unresolved type id LOOKS project-namespaced (`::`) but no
/// project was loaded for this invocation (`env.provenance()` is `None`),
/// append a hint pointing at the missing `Aura.toml` — the likeliest cause is
/// "ran outside the project directory", not "genuinely unknown std id". Bare
/// std-vocabulary ids (no `::`) and invocations that DO have a project loaded
/// get no hint.
/// (#185/#241) When an unresolved type id LOOKS project-namespaced (`::`),
/// the likeliest causes are environmental, tiered: outside any project the
/// hint points at the missing `Aura.toml`; inside a data-only project it
/// points at the missing node crate. Bare std ids (no `::`) and invocations
/// with a loaded node crate get no hint (the vocabulary error carries the
/// message).
pub(crate) fn unresolved_namespace_hint(e: &LoadError, env: &crate::project::Env) -> Option<String> {
match e {
LoadError::UnknownNodeType(t) if t.contains("::") && env.provenance().is_none() => Some(
"type id looks project-namespaced but no Aura.toml was found — run inside a project directory"
.to_string(),
),
LoadError::UnknownNodeType(t) if t.contains("::") => match env.provenance() {
None => Some(
"type id looks project-namespaced but no Aura.toml was found — run inside a project directory"
.to_string(),
),
Some(p) if p.namespace.is_none() => Some(
"type id looks project-namespaced but this project binds no node crate — attach one with `aura nodes new <name>`"
.to_string(),
),
Some(_) => None,
},
_ => None,
}
}
+21
View File
@@ -365,6 +365,27 @@ fn nodes_pointer_crate_resolves_and_stamps_its_namespace() {
);
}
/// #241: inside a data-only project, a project-namespaced type id gets the
/// "binds no node crate" hint (not the outside-project "no Aura.toml" text).
#[test]
fn data_only_project_hints_attach_for_namespaced_ids() {
let tmp = std::env::temp_dir().join(format!("aura-dataonly-hint-{}", std::process::id()));
std::fs::create_dir_all(&tmp).unwrap();
std::fs::write(tmp.join("Aura.toml"), "").unwrap();
let bp = tmp.join("bp.json");
std::fs::write(&bp, r#"{"format_version":1,"blueprint":{"name":"x","nodes":[{"primitive":{"type":"nosuch::Node"}}],"edges":[],"input_roles":[{"name":"price","targets":[{"node":0,"slot":0}],"source":"F64"}],"output":[{"node":0,"field":0,"name":"bias"}]}}"#).unwrap();
let out = std::process::Command::new(env!("CARGO_BIN_EXE_aura"))
.args(["run", bp.to_str().unwrap()])
.current_dir(&tmp)
.output()
.expect("spawn aura");
assert_eq!(out.status.code(), Some(2), "stderr: {}", String::from_utf8_lossy(&out.stderr));
let err = String::from_utf8_lossy(&out.stderr);
assert!(err.contains("binds no node crate"), "{err}");
assert!(err.contains("aura nodes new"), "{err}");
std::fs::remove_dir_all(&tmp).ok();
}
#[test]
fn missing_artifact_refuses_with_build_hint() {
// A minimal never-built project: valid cargo metadata, no dylib on disk.