Skip to content

Record the passive-edge mask, so multi-edge ops can be emitted - #762

Closed
0-jake-0 wants to merge 1 commit into
eligibilityfrom
multi-edge
Closed

Record the passive-edge mask, so multi-edge ops can be emitted#762
0-jake-0 wants to merge 1 commit into
eligibilityfrom
multi-edge

Conversation

@0-jake-0

@0-jake-0 0-jake-0 commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Stacked on #761#760#759. Base is eligibility; this diff is only the top commit. Review order: #759#760#761 → this.

What this changes

NodeInfo records the op's passive-edge mask and exposes edges_in_call_order(). The emission walker consequently handles join and sample instead of refusing them — emission now covers single-edge, multi-edge and passive-edge shapes.

Why

Gap 3, the last of the three #759's spike pinned.

join only needed the receiver-first convention active_ups already keeps. sample needed something the graph did not have. Its data leg is edge 0 and passive, its trigger is edge 1 and active, so the node records:

active_ups  = [tick]
passive_ups = [count]

That pair is identical whichever way round the call was written. The two lists are a partition — each preserves its own order, but the interleaving between them is gone, so count.sample(&tick) and a hypothetical reverse are indistinguishable.

The op's #[op(passive = [..])] mask is exactly the missing bit, and the derive already had it. Recorded on the node now, with edges_in_call_order() walking positions 0..n and taking from whichever list each bit selects.

How it was verified

  • cargo fmt --all
  • cargo lint ✅ — cargo lint-all not run: --all-features cannot build here (aeron needs CMake ≥3.30, box has 3.28; etcd needs protoc). Documented prerequisites, unrelated to this diff; CI is the first real check on that feature set.
  • --all-features blocked as above. Full default suite plus bench,async,csv,cache,augurs,market,ws,redis,postgres,kafka,web,kdb,prometheus,fix,dynamic-graph,tracing — green.
  • New behaviour covered by tests asserting values and tick times

Each shape is proven the same way as the rest of the spike: the expected artifact is a real nitro! block in the test file, so byte-identical output plus a compiling file means the emitted wiring is valid by construction. Parity then runs on values and tick times.

The sample test asserts the reconstructed order explicitly ([data, trigger]) as well as the emitted text, because that is the case where getting it backwards would still produce something that compiles — n0.sample(&n1) is valid Rust that computes the wrong thing.

Notes for the reviewer

Variadic ops stay refused, and correctly so. merge_allMergeN and combineCombineN have hand-written forwarders, so they carry no build name. Emitting them as the n-ary call they do not accept would be worse than failing, since it would produce an artifact that fails at pass 2 in generated code. A test pins the refusal rather than leaving it to chance.

What remains after this: refusals name node indices rather than the call sites #[track_caller] would give; tier-2 captures (func!([thresh] |x| …)) are unbuilt; and the walker is still test-only code rather than a codegen::generate entry point.

Stack note, repeated from #761: this is now four PRs deep on unreviewed work, and #759 is load-bearing for the three above it. If #759 wants changes, the rebases compound. The remaining #726 items are mutually independent, so this is a natural place to stop adding layers.


Generated by Claude Code

Gap 3, the last of the three the emission spike pinned. The walker refused
anything with more than one edge; it now emits `join` as `a.join(&b, f)`
and `sample` as `count.sample(&tick)`.

`join` only needed the receiver-first convention `active_ups` already
keeps. `sample` needed something the graph did not have. Its data leg is
edge 0 and *passive*, its trigger is edge 1 and active, so the node
records `active_ups = [tick]` and `passive_ups = [count]` — and that pair
is identical whichever way round the call was written. The two lists are a
*partition*: each preserves its own order, but the interleaving between
them is gone.

The op's `#[op(passive = [..])]` mask is exactly the missing bit, and the
derive already had it. Recorded on the node now, with
`NodeInfo::edges_in_call_order` walking positions `0..n` and taking from
whichever list each bit selects.

Variadic ops (`merge_all` -> `MergeN`, `combine` -> `CombineN`) stay
refused, and correctly so: their forwarders are hand-written, so they
carry no `build` name, and emitting them as an n-ary call they do not
accept would be worse than failing. A test pins the refusal rather than
leaving it to chance.

Emission now covers the single-edge, multi-edge and passive-edge shapes,
each proven the same way: the expected artifact is a real `nitro!` block
in the test file, so byte-identical output plus a compiling file means the
emitted wiring is valid by construction, and parity is then asserted on
values *and* tick times.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X4eojqRWBJ6uK5v5JDzmkd

0-jake-0 commented Aug 8, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #769, which squashes this whole stack (#759#768) onto main as a single reviewable change.

The passive_mask and edges_in_call_order work is carried forward in full. Closing to keep the queue readable; no content is being dropped.


Generated by Claude Code

@0-jake-0 0-jake-0 closed this Aug 8, 2026
@0-jake-0
0-jake-0 deleted the multi-edge branch August 9, 2026 23:07
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