Skip to content

fix(macros): fan-out aggregate re-tagging is per-scope, not top-level-only#87

Merged
mostlymaxi merged 1 commit into
mainfrom
claude/fanout-agg-retag
Jul 18, 2026
Merged

fix(macros): fan-out aggregate re-tagging is per-scope, not top-level-only#87
mostlymaxi merged 1 commit into
mainfrom
claude/fanout-agg-retag

Conversation

@mostlymaxi

Copy link
Copy Markdown
Owner

What

The pass that re-tags Arg::Ref(fan_task)Arg::FanAgg (the kind-aware array-renormalizing expression) only ran over the top-level workflow body. An if-arm body is its own analysis scope (a synthesized sub-workflow), so a consumer of a fan_out binding inside an arm silently got the plain parameter ref — exactly the double-encoding the FanAgg doc describes:

#[workflow]
fn wf() {
    let cnt = decide("hello".to_string());
    if cnt > 3 {
        let b = make_list().fan_out(|x| caps(x, "!".to_string()));
        summarize(b);   // ← emitted {{tasks.b.outputs.parameters.return}} — double-encoded
    }
}

Hook args had the same hole at every scope (they lower through arg_value, which already handles FanAgg, but were never re-tagged).

How

  • Extracted the pass into retag_fan_aggs(): covers task args and hook args, returns the scope's fan task set. Run once per scope — the top-level body in analyze_workflow, and every arm body in synth_if. Scope-local is correct by construction: a Ref can only name a task in its own node list (outer bindings enter an arm as captured inputs).
  • Rejected (spanned compile error) a fan_out binding as a workflow's — or value-if arm's — return value: the outputs.parameters.return bubble is a plain valueFrom.parameter copy of the raw aggregate, whose elements are still individually JSON-encoded, so the parent would read a double-encoded array. Consuming the aggregate in-workflow and returning that is the supported shape. (An alternative is a kind-aware valueFrom.expression re-norm on the bubble — happy to explore that as a follow-up if you'd rather support the shape than reject it; rejecting seemed safer without live-cluster verification.)

Testing

  • New smoke bin + golden (pipeline_fanout_if) pins the in-arm consumer carrying the same nil-safe FanAgg re-norm expr as top level (composes with the v3.6 nil-safe lookup from 7fd2d88).
  • Two new trybuild fixtures pin the return rejections (top-level and arm).
  • cargo test --workspace + clippy clean. (The pre-existing host_nested_in_macro.stderr note-ordering drift on rustc 1.97 is untouched here; it's fixed on the claude/cargo-athena-analysis-de968h branch.)

🤖 Generated with Claude Code

https://claude.ai/code/session_01AzynLVNz1o2QJY14PkTGCq


Generated by Claude Code

…-only

The pass that re-tags Arg::Ref(fan_task) -> Arg::FanAgg (the kind-aware
array-renormalizing expr) ran only over the top-level workflow body.
An if-arm body is its own analysis scope (a synthesized sub-workflow),
so a consumer of a fan_out binding INSIDE an arm silently got the plain
parameter ref — exactly the double-encoding the FanAgg doc describes.
Hook args had the same hole at every scope (they reuse arg_value, which
handles FanAgg, but were never re-tagged).

- Extract the pass into retag_fan_aggs(): covers task args AND hook
  args, returns the scope's fan task set; run it per scope — the
  top-level body in analyze_workflow and every arm body in synth_if.
- Reject a fan_out binding as a workflow's (or value-if arm's) return
  with a spanned error: the outputs.parameters.return bubble is a plain
  valueFrom.parameter copy of the raw aggregate, whose elements are
  still individually JSON-encoded — the parent would read a
  double-encoded array. Consuming the aggregate in-workflow and
  returning that is the supported (and correct) shape.
- New smoke bin + golden (pipeline_fanout_if) pins the in-arm consumer
  carrying the same nil-safe FanAgg re-norm expr as top level; two new
  UI fixtures pin the return rejections.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AzynLVNz1o2QJY14PkTGCq
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants