feat(engine+cli): topology-identity hash — --identity-id beside --content-id
Engine (aura-engine/blueprint_serde): blueprint_to_json factored into build_doc/serialize_doc (byte-preserving — canonical golden unchanged) and blueprint_identity_json added: the canonical document with every non-load-bearing debug symbol (C23) blanked (composite name, instance names, bound-param names, role names, output re-export names) while everything load-bearing survives (type ids, node order, edges, role targets/order, output pairs/order, bound pos/kind/value — openness stays identity-bearing). Re-exported from lib.rs. Six property tests: renamed twins (equal identity, unequal canonical), open-vs-bound, bound-value, edge-swap, nested-composite interior names, role/output renames — the last two are additive beyond the plan and cover the recursion and role/output arms the planned four did not. CLI (aura-cli): graph introspect gains --identity-id, a sibling of --content-id through the same shared content_id SHA-256 primitive; composite_from_str factored out of build_from_str (fault strings byte-identical). The exactly-one introspect dispatch is DELIBERATELY relaxed: the two id flags form one group and may combine — one build, both ids, one per line, content id first. In-crate cross-path twin test (Rust sma_signal vs op-script twin: distinct content ids, one identity id) beside the existing cross-surface pins; four e2e tests incl. the previously uncovered count!=1 usage exit-2 path. topology_hash, the blueprint store, reproduce, and every --content-id byte stay untouched (spec acceptance 3; all existing pins green). Verification: cargo build clean; cargo test --workspace 884 passed / 0 failed (873 baseline + 11 new); clippy -D warnings clean; doc build 0 warnings. The implement-loop's Task-4 spec-compliance block was a plan-byte count mismatch only (plan under-counted pre-existing blueprint_serde tests 6-vs-7 and did not anticipate the two sibling-accepted extra tests); gates re-run by hand, all green. closes #171, refs #180
This commit is contained in:
@@ -6,7 +6,10 @@
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use aura_engine::{blueprint_to_json, replay, BindOpError, GraphSession, Op, OpError, Scalar, ScalarKind};
|
||||
use aura_engine::{
|
||||
blueprint_identity_json, blueprint_to_json, replay, BindOpError, Composite, GraphSession, Op,
|
||||
OpError, Scalar, ScalarKind,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
|
||||
// `std_vocabulary` is now only reached through `crate::project::Env::resolve` in
|
||||
@@ -106,24 +109,28 @@ fn format_op_error(e: &OpError) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a JSON op-list document, replay it through `std_vocabulary`, and return
|
||||
/// the emitted #155 blueprint JSON — or a `op N (kind): cause` message (a per-op
|
||||
/// fault, attributed by the retained op-kind list) / `finalize: cause` (a
|
||||
/// holistic fault past the last op).
|
||||
pub fn build_from_str(doc: &str, env: &crate::project::Env) -> Result<String, String> {
|
||||
/// Parse a JSON op-list document and replay it through the env's vocabulary into
|
||||
/// a built `Composite` — or a `op N (kind): cause` message (a per-op fault,
|
||||
/// attributed by the retained op-kind list) / `finalize: cause` (a holistic fault
|
||||
/// past the last op).
|
||||
fn composite_from_str(doc: &str, env: &crate::project::Env) -> Result<Composite, String> {
|
||||
let docs: Vec<OpDoc> = serde_json::from_str(doc).map_err(|e| format!("invalid op-list: {e}"))?;
|
||||
let labels: Vec<&'static str> = docs.iter().map(OpDoc::kind_label).collect();
|
||||
let ops: Vec<Op> = docs.into_iter().map(Op::from).collect();
|
||||
match replay("graph", ops, &|t| env.resolve(t)) {
|
||||
Ok(composite) => blueprint_to_json(&composite).map_err(|e| format!("serialize error: {e:?}")),
|
||||
Err((idx, err)) => {
|
||||
let cause = format_op_error(&err);
|
||||
match labels.get(idx) {
|
||||
Some(kind) => Err(format!("op {idx} ({kind}): {cause}")),
|
||||
None => Err(format!("finalize: {cause}")),
|
||||
}
|
||||
replay("graph", ops, &|t| env.resolve(t)).map_err(|(idx, err)| {
|
||||
let cause = format_op_error(&err);
|
||||
match labels.get(idx) {
|
||||
Some(kind) => format!("op {idx} ({kind}): {cause}"),
|
||||
None => format!("finalize: {cause}"),
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse a JSON op-list document, replay it through the env's vocabulary, and
|
||||
/// return the emitted #155 blueprint JSON — fault shapes as in `composite_from_str`.
|
||||
pub fn build_from_str(doc: &str, env: &crate::project::Env) -> Result<String, String> {
|
||||
let composite = composite_from_str(doc, env)?;
|
||||
blueprint_to_json(&composite).map_err(|e| format!("serialize error: {e:?}"))
|
||||
}
|
||||
|
||||
/// `aura graph build`: read the op-list document from stdin, build, and print the
|
||||
@@ -184,16 +191,18 @@ pub fn introspect_unwired(doc: &str, env: &crate::project::Env) -> Result<String
|
||||
}
|
||||
|
||||
/// `aura graph introspect`: dispatch the read-only queries. Exactly one of
|
||||
/// `--vocabulary` / `--node <T>` / `--unwired` / `--content-id` must be set;
|
||||
/// zero or more than one is the usage error (exit 2).
|
||||
/// `--vocabulary` / `--node <T>` / `--unwired` / the id group must be set; zero
|
||||
/// or more than one is the usage error (exit 2). The id group is `--content-id`
|
||||
/// and/or `--identity-id` — the two id flags may combine (one build, both ids,
|
||||
/// content id first).
|
||||
pub fn introspect_cmd(cmd: crate::GraphIntrospectCmd, env: &crate::project::Env) {
|
||||
let count = cmd.vocabulary as usize
|
||||
+ cmd.node.is_some() as usize
|
||||
+ cmd.unwired as usize
|
||||
+ cmd.content_id as usize;
|
||||
+ (cmd.content_id || cmd.identity_id) as usize;
|
||||
if count != 1 {
|
||||
eprintln!(
|
||||
"aura: Usage: aura graph introspect --vocabulary | --node <T> | --unwired | --content-id"
|
||||
"aura: Usage: aura graph introspect --vocabulary | --node <T> | --unwired | --content-id | --identity-id (the two id flags may be combined)"
|
||||
);
|
||||
std::process::exit(2);
|
||||
}
|
||||
@@ -224,22 +233,43 @@ pub fn introspect_cmd(cmd: crate::GraphIntrospectCmd, env: &crate::project::Env)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// --content-id: the content id (#158) of the op-list's canonical blueprint:
|
||||
// SHA256 (hex) of the same `blueprint_to_json` bytes `graph build` emits, via
|
||||
// the one shared `crate::content_id` primitive `topology_hash` also uses — so
|
||||
// this surface and hashing a `graph build` output agree by construction.
|
||||
// --content-id / --identity-id (combinable): one build of the op-list, then
|
||||
// each requested id on its own line, content id first. The content id (#158)
|
||||
// is the SHA256 of the same `blueprint_to_json` bytes `graph build` emits;
|
||||
// the identity id (#171) the SHA256 of the debug-name-blind
|
||||
// `blueprint_identity_json` form — both via the one shared
|
||||
// `crate::content_id` primitive `topology_hash` also uses, so all surfaces
|
||||
// agree by construction.
|
||||
use std::io::Read;
|
||||
let mut doc = String::new();
|
||||
if let Err(e) = std::io::stdin().read_to_string(&mut doc) {
|
||||
eprintln!("aura: reading stdin: {e}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
match build_from_str(&doc, env) {
|
||||
Ok(json) => println!("{}", crate::content_id(&json)),
|
||||
let composite = match composite_from_str(&doc, env) {
|
||||
Ok(c) => c,
|
||||
Err(m) => {
|
||||
eprintln!("aura: {m}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
if cmd.content_id {
|
||||
match blueprint_to_json(&composite) {
|
||||
Ok(json) => println!("{}", crate::content_id(&json)),
|
||||
Err(e) => {
|
||||
eprintln!("aura: serialize error: {e:?}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
if cmd.identity_id {
|
||||
match blueprint_identity_json(&composite) {
|
||||
Ok(json) => println!("{}", crate::content_id(&json)),
|
||||
Err(e) => {
|
||||
eprintln!("aura: serialize error: {e:?}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user