From 73ad87b08aaf6f9ffe085c0da78bec093403d208 Mon Sep 17 00:00:00 2001 From: claude Date: Fri, 24 Jul 2026 16:02:45 +0200 Subject: [PATCH] fix(aura-research): process.ref hint states the tagged { content_id } shape MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit campaign introspect (--block std::process_ref and --unwired) described the slot as "content id of a process document" — a plausible bare-string reading the validator rejects; the required {"content_id":"..."} tagging appeared in no introspection output (binary-only M1 fieldtest finding). The SlotKind::ContentRef label and the open-slot hint now lead with the tagged { content_id } shape. Deliberately narrower than the issue's suggestion of mirroring std::strategy's { content_id } | { identity_id } form: validate_campaign refuses an identity_id process ref (DocFault::ProcessRefMustBeContentId) and a pinned test forbids advertising it, so the hint states the one accepted key only (C29 honesty; correction minuted on the issue). RED-first: the extended process_ref_describe_advertises_content_id_only assertion failed against the old label before the fix. closes #329 --- crates/aura-cli/tests/research_docs.rs | 4 ++-- crates/aura-research/src/lib.rs | 20 +++++++++++++++----- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/crates/aura-cli/tests/research_docs.rs b/crates/aura-cli/tests/research_docs.rs index 06e6b73..ab1c2ef 100644 --- a/crates/aura-cli/tests/research_docs.rs +++ b/crates/aura-cli/tests/research_docs.rs @@ -996,7 +996,7 @@ fn campaign_introspect_unwired_reports_the_spec_example_slots() { write_doc(&dir, "draft.campaign.json", draft); let (out, code) = run_code_in(&dir, &["campaign", "introspect", "--unwired", "draft.campaign.json"]); assert_eq!(code, Some(0)); - assert!(out.contains("open slot: process.ref (required, content id of a process document)")); + assert!(out.contains("open slot: process.ref (required, { content_id }: content id of a process document)")); assert!(out.contains("open slot: strategies[0].axes.slow (axis declared empty — a P1 axis is a non-empty finite set)")); } @@ -1022,7 +1022,7 @@ fn campaign_introspect_unwired_reports_placeholder_refs() { "strategy `ref: null` placeholder not enumerated: {out}" ); assert!( - out.contains("open slot: process.ref (required, content id of a process document)"), + out.contains("open slot: process.ref (required, { content_id }: content id of a process document)"), "process `ref: {{}}` placeholder not enumerated: {out}" ); } diff --git a/crates/aura-research/src/lib.rs b/crates/aura-research/src/lib.rs index 58a9723..34f000f 100644 --- a/crates/aura-research/src/lib.rs +++ b/crates/aura-research/src/lib.rs @@ -1102,7 +1102,7 @@ pub fn slot_kind_label(kind: SlotKind) -> &'static str { SlotKind::Strings => "list of strings", SlotKind::Windows => "list of { from_ms, to_ms }", SlotKind::Ref => "one of { content_id } | { identity_id }", - SlotKind::ContentRef => "content id of a process document", + SlotKind::ContentRef => "{ content_id }: content id of a process document", SlotKind::Axes => "map: param name -> { kind, values }", SlotKind::EmitKinds => "list of: family_table | selection_report", SlotKind::TapKinds => "list of: equity | exposure | r_equity | net_r_equity", @@ -1301,8 +1301,10 @@ pub fn open_slots_campaign(text: &str) -> Result, DocError> { } if ref_slot_is_open(v.get("process").and_then(|p| p.get("ref"))) { // deliberately narrower than the strategy-ref hint: validate_campaign - // refuses an identity_id process ref, so the guide must not offer it - slots.push(open("process.ref", "required, content id of a process document")); + // refuses an identity_id process ref, so the guide must not offer it. + // The tagged { content_id } shape is stated explicitly (#329) so the + // hint cannot be misread as accepting a bare id string. + slots.push(open("process.ref", "required, { content_id }: content id of a process document")); } if v.get("seed").and_then(|s| s.as_u64()).is_none() { slots.push(open("seed", "required, non-negative integer")); @@ -2374,6 +2376,14 @@ mod tests { label.contains("content"), "process_ref describe must still name the content-id form: {label}" ); + // #329: the label must state the TAGGED shape ({ content_id: ... }) the + // parser actually requires, not read like a bare id string — mirroring + // the `{ content_id } | { identity_id }` bracket notation std::strategy + // already uses, narrowed to the one key process.ref accepts. + assert!( + label.contains("{ content_id }"), + "process_ref describe must state the tagged {{ content_id }} shape, not a bare id: {label}" + ); let strategy = describe_block("std::strategy").expect("strategy describable"); let strat_ref = @@ -2430,7 +2440,7 @@ mod tests { "presentation": { "persist_taps": [], "emit": [] } }"#; let slots = open_slots_campaign(draft).unwrap(); assert!(slots.iter().any(|s| s.path == "process.ref" - && s.hint == "required, content id of a process document")); + && s.hint == "required, { content_id }: content id of a process document")); assert!(slots.iter().any(|s| s.path == "strategies[0].axes.slow" && s.hint == "axis declared empty — a P1 axis is a non-empty finite set")); @@ -2466,7 +2476,7 @@ mod tests { // ...and the `{}` process ref reports the narrower content-id-only hint. assert!( slots.iter().any(|s| s.path == "process.ref" - && s.hint == "required, content id of a process document"), + && s.hint == "required, { content_id }: content id of a process document"), "process `ref: {{}}` placeholder not reported as an open slot: {slots:?}" ); }