feat(engine): composite doc field — authoring, serde, identity, model emit (refs #125)

Composite gains doc: Option<String> (the prose twin of name, a C23 debug
symbol): with_doc/doc() on Composite, a doc knob on GraphBuilder, a
Tier-1 additive-optional CompositeData field (no format-version bump;
absent-field documents keep their exact bytes), identity-stripped like
the name. The graph model emits an optional trailing doc fragment in
both scopes; json_str is hardened for free text (\n/\t/\r named,
control chars as \u00XX — the doc is the first multi-line value through
it). e2e: a hand-authored doc'd blueprint renders with the doc embedded;
the doc moves the content id but never the identity id.

refs #125
This commit is contained in:
2026-07-11 19:37:45 +02:00
parent c8b5acf6f0
commit 953d04a774
6 changed files with 198 additions and 11 deletions
+73
View File
@@ -967,6 +967,79 @@ fn graph_build_gang_op_survives_render_round_trip() {
);
}
/// Property (#125, the render seam a hand-authored blueprint document actually
/// drives): a composite's `doc` field is not merely a `model_to_json` unit-level
/// concept — `aura graph <file>` on a real on-disk blueprint envelope that
/// carries `"doc":"..."` embeds that exact text in the page's inlined
/// `window.AURA_MODEL` JSON. The graph_model.rs golden pins the JSON fragment in
/// isolation; this proves the doc survives the actual file-load -> render CLI
/// pipeline a consumer runs, not just the library function called directly.
#[test]
fn graph_render_embeds_the_hand_authored_composite_doc() {
let out = std::process::Command::new(BIN)
.arg("graph")
.arg(fixture("doc_blueprint.json"))
.output()
.expect("spawn aura graph <blueprint>");
assert_eq!(
out.status.code(),
Some(0),
"render exit: {:?}\nstderr: {}",
out.status,
String::from_utf8_lossy(&out.stderr)
);
let html = String::from_utf8(out.stdout).expect("utf-8 stdout");
assert!(
html.contains(r#","doc":"why this graph: single SMA smoke test""#),
"rendered page does not embed the composite doc: {html}"
);
}
/// Property (#125, doc-blind identity at the CLI seam): a blueprint document
/// carrying a `doc` and its doc-less twin are the SAME topology by the identity
/// projection (`--identity-id` agrees) but DIFFERENT documents by the canonical
/// projection (`--content-id` differs) — mirroring the established renamed-twin
/// pattern (`graph_introspect_identity_id_bridges_renamed_op_scripts`) for the
/// new field. `blueprint_serde.rs`'s `doc_round_trips_canonically_and_is_
/// identity_blind` pins this at the library level; this drives the real binary
/// over a real file, the boundary a regression in the CLI's own dispatch could
/// still break even with the library invariant intact.
#[test]
fn graph_doc_field_moves_content_id_but_not_identity_id() {
let doced = fixture("doc_blueprint.json");
let doceless_text = std::fs::read_to_string(&doced)
.expect("read fixture")
.replace(r#""doc":"why this graph: single SMA smoke test","#, "");
let dir = temp_cwd("doc-field-ids");
let doceless = dir.join("doc_blueprint_no_doc.json");
std::fs::write(&doceless, &doceless_text).expect("write doc-less twin");
// `--identity-id` has no FILE slot of its own (main.rs `GraphIntrospectCmd`):
// combine it with `--content-id <FILE>` (both id flags share one build) and
// read the two printed lines, content id first (mirrors
// `graph_introspect_content_and_identity_id_combine`).
let (doced_ids, _e, ok1) =
run_in(&dir, &["graph", "introspect", "--content-id", &doced, "--identity-id"]);
let (doceless_ids, _e2, ok2) = run_in(
&dir,
&["graph", "introspect", "--content-id", doceless.to_str().unwrap(), "--identity-id"],
);
assert_eq!(ok1, Some(0), "doc'd id pair exits 0: {doced_ids}");
assert_eq!(ok2, Some(0), "doc-less id pair exits 0: {doceless_ids}");
let doced_lines: Vec<&str> = doced_ids.lines().collect();
let doceless_lines: Vec<&str> = doceless_ids.lines().collect();
assert_eq!(doced_lines.len(), 2, "content id then identity id: {doced_ids:?}");
assert_eq!(doceless_lines.len(), 2, "content id then identity id: {doceless_ids:?}");
assert_ne!(
doced_lines[0], doceless_lines[0],
"the doc is canonical-byte-bearing (content id moves)"
);
assert_eq!(
doced_lines[1], doceless_lines[1],
"the identity projection is doc-blind (identity id agrees)"
);
}
/// The `gang` op's arity refusal (fewer than two members) reads as prose at the
/// binary seam, attributed to its op index, never the raw `GangArity` Debug name.
#[test]