Gunnar

mikronova/funnel-core

funnel-core

The pure workflow-DAG core of the nornir funnel — events, ids, state, topological readiness, history, the Plan-DAG projection, and the kanban-card board — with no warehouse / iceberg / model / GPU coupling.

Extracted from nornir/src/funnel/ so both nornir (the viz funnel + Plan-DAG panels) and ordning-funnel (idea → case graduation) share one idea→plan→node DAG without dragging in nornir's storage stack. Everything here is deterministic: given the same events + query string the output is bit-identical, so every surface is golden-testable with no warehouse, no clock and no GPU.

The shape

A funnel is a fold of an append-only [Event] log into an in-memory [Funnel]:

Event log ──apply──▶ Funnel ──┬─▶ history()          → HistoryItem[]  (intake list)
                              ├─▶ cards_from_funnel() → Card[]         → board() → Board (kanban)
                              ├─▶ query_cards(q)      → Card[]         (YouTrack-style smart query)
                              ├─▶ topo_ready()        → NextStep[]     ("what should I work on next?")
                              ├─▶ simulate_flow()     → FlowSim        (time-step the DAG to done)
                              └─▶ PlanDag::from_funnel → PlanDag       (nodes + edges + topo, viz)

Every projection is pure (&Funnel → value) and BTree-ordered, so the CLI, the gRPC server and the viz all render the same bytes and never disagree.

Benchmarks

v0.1.3 · oden · 32 cores · 2026-07-22

workloadassemble_msassemblies_per_secbuild_mscardscards_per_seccards_scannedcards_scanned_per_seccritical_path_lendepth_nodesedge_birthsedgeselapsed_msevents_per_iterevents_per_sechitsitersnode_birthsnodespass_mspasses_per_secplansreplay_msspeedup_xstepsstream_mstimeline_ms
funnel-core.board_project52006394401626200
funnel-core.funnel_build60.28440012061040017253120400
funnel-core.plan_dag_critical12520092003086200520015.4365
funnel-core.plan_dag_growth59910737280350301602214.74
funnel-core.plan_dag_growth_stream59926835030147437211.41602224.38
funnel-core.plan_dag_whole17.0659920017061005200
funnel-core.smart_query52003.07M8472403500

The board / smart-query projection (cards_from_funnel → board → query_cards) and PlanDag::from_funnel are the hot paths the funnel UI runs on every render; they are benched under bench/ (nornir bench run funnel-core) and, for the Plan-DAG assembly, by nornir's own nornir.plan_dag_assemble arm.

Tests

Unit tests (51)

filetests
src/card.rscard_projection_maps_facets_and_labels, hashtag_mining, smart_query_operators, smart_query_sort, board_groups_into_fixed_lanes, empty_funnel_is_honest_empty_board, cards_from_funnel_includes_task_nodes, card_state_changed_and_assigned_are_reflected_in_the_projection, smart_query_assignee_and_for
src/history.rshistory_derives_kind_status_and_filters
src/mode.rstokens_round_trip_and_are_stable, parse_is_forgiving_and_case_insensitive, mode_gate_maps_to_the_right_lane, default_is_auto
src/plan_dag.rsassembles_mother_and_dependency_edges, topo_order_puts_dependencies_before_dependents, scoping_to_a_plan_keeps_its_mother_and_children, state_json_is_deterministic_and_shaped, empty_funnel_is_an_empty_graph, slack_grades_tension_and_critical_nodes_are_zero_slack, critical_nodes_is_a_superset_of_the_single_critical_path, bottlenecks_rank_by_downstream_reach_not_local_fanout, layers_group_nodes_by_depth_and_width_is_the_peak, critical_path_deps_is_strictly_shorter_than_the_mother_lineage, critical_path_deps_picks_the_longer_dep_branch_ignoring_mother, depths_layer_dependencies_before_dependents, critical_path_is_the_longest_chain, critical_path_picks_the_longer_of_two_branches, state_json_carries_the_dag_vocabulary, diff_classifies_added_removed_and_changed, identical_revision_diffs_to_empty, growth_stamps_first_appearance_of_each_node_and_edge, growth_skips_rejected_events_and_scopes_like_from_funnel, extend_streams_byte_identical_births_to_the_batch_fold, extend_matches_batch_scoped_and_skips_rejected_events
src/state.rsedge_closing_a_cycle_is_rejected, transitive_and_direct_back_edges_are_both_rejected, plan_still_drains_after_a_cycle_edge_is_refused, valid_run_bumps_high_water_and_never_touches_node_status, high_water_is_monotone_not_last_write, unknown_plan_or_node_is_rejected_and_high_water_unchanged, runs_are_retained_in_log_order_with_every_payload_field, duplicate_run_id_is_rejected, the_same_node_id_in_a_second_plan_is_rejected, distinct_ids_still_land_and_same_plan_dup_keeps_its_message, non_seq_ids_are_still_unique_funnel_wide, card_assignment_stays_unambiguous, folding_the_same_log_twice_yields_identical_state, last_write_wins_fields_follow_log_order
src/topo.rsall_pending_reaches_all_done_in_chain_length_waves, a_failed_first_node_stalls_its_dependents

Integration tests (12)

filetests
tests/public_api.rsfull_projection_pipeline_composes_via_reexports, smart_query_operators_compose, topo_ready_and_simulate_flow_via_reexports, plan_dag_projection_via_reexports, wire_tokens_are_stable, dag_wire_tokens_are_stable, idea_public_field_contract, funnel_mode_vocabulary_via_reexports, plan_dag_critical_path_via_reexports, plan_dag_diff_via_reexports, plan_dag_growth_timeline_via_reexports, apply_rejects_invariant_violations

Doc tests (0)

(none)

Dependency graph

workspace dependency graph

License

MIT OR Apache-2.0.

Files

.nornir/
bench/
src/
tests/
.gitignore
Cargo.toml
README.md
bench_history.jsonl
rustfmt.toml

Newest commits

d3a0f062122cMerge v2/n7-a28-small-2026-10-25: every repository = names this repository's real home, with a guard test (A28 L17)2026-10-25
09b0ec276b6eGREEN: every repository = names this repository's home (A28 L17)2026-10-25
f8649b91a384RED: every Cargo.toml names this repository's real home and none names the old forge (A28 L17)2026-10-25
fcc1117423efpublish prep: repository points at the code's real home (codeberg.org/mikronova/funnel-core)2026-10-04
b014814f7424publish prep: `me` from the environment, names out of the fixtures, dev notes out of the package2026-10-04