solverforge-scoring 0.19.10

Incremental constraint scoring for SolverForge
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
# solverforge-scoring WIREFRAME

Zero-erasure incremental constraint scoring infrastructure for SolverForge.

**Location:** `crates/solverforge-scoring/`
**Workspace Release:** `0.19.10`

## Typed relational operator surface

These are module-level low-level exports, not facade-root exports or a completed
migration of the fluent stream families:

- `stream::relational::{Leaf<'a, A>, Concat<'a, L, A>}` provides borrowed row
  views. `RowHandle` is a hidden generational identity bridge.
- `stream::relational::operator::Operator<S>` defines `View<'a>: Copy`,
  owned `Evaluation`, `prepare_evaluation`, `visit_evaluation`, `visit_all`,
  `clear`, `initialize`, `handles`, `resolve`, `visit_provenance`,
  `retract`, and `insert`. Views borrow entities; entities need not be Copy.
  Both notification methods return `RowChanges { removed, inserted }`: a
  contributor retraction can replace a surviving aggregate row.
- `CollectionNode<S, E>::new(extractor, binding)` adapts `CollectionExtract`.
- `FilterNode<O, F>::new(input, predicate)` preserves accepted input identities
  and semantic source indexes. Its predicate takes `(&S, &O::View<'a>)`;
  membership transitions propagate retract/insert deltas without copying rows.
- `MergeNode<L, R>::new(left, right)` unions compatible borrowed row views with
  branch-specific output identities; equal-valued emissions remain separate.
- `ProjectNode<O, F, T>::new(input, mapper)` owns zero or more emissions per
  input row. The mapper takes `(&S, &O::View<'a>)` and returns `Vec<T>`.
  `ProjectView<'a, V, T>` exposes `input: V`, `value: &'a T`, and `emission:
  usize`; payloads need not implement Clone, Copy, Debug, or PartialEq.
  Explanations and provenance retain the complete upstream row.
- `GroupNode<O, F, C, K, A, V, R>::new(input, key, collector)` groups arbitrary
  input rows with exact accumulator retraction tokens. `GroupView<'a, S, O,
  K, A, V, R>` exposes `key: &'a K` and `with_result(callback)` over the
  accumulator-owned result, including non-Clone `CollectedVec<T>` payloads.
  Either join input can be a grouped producer. Contributor changes replace
  aggregate rows on both callbacks and coalesce within one root notification.
- `ExistenceNode<S, L, R, P>::new(left, right, condition, exists)` publishes only
  left rows, using the same compiled index strategies as joins. `exists=true`
  selects semi-join membership and `false` selects anti-join membership. Matching
  links retain multiplicity counts; 1↔2 transitions do not publish row changes.
- `FlattenNode<O, F, T>::new(input, extractor)` emits borrowed child slices
  without cloning children or retaining solution references. The extractor takes
  `(&'a S, O::View<'a>)` and returns `&'a [T]`. `FlattenView<'a, V, T>` exposes
  `input: V`, `value: &'a T`, and `child: usize`; parent row provenance and its
  semantic source indexes are preserved, including for owned projected parents.
- `ComplementNode<S, L, R, P, F, T>::new(targets, results, condition, default)`
  produces a typed target-domain outer relation. Real matches retain target and
  result bindings; unmatched targets own their default payloads. `ComplementView`
  is `Real(Pair<L, R>)` or `Default(ProjectView<'a, L, T>)`. Duplicate target
  rows preserve bag multiplicity, and grouped results have one accumulator owner.
- `JoinNode<S, L, R, P>::new(left, right, condition)` compiles a condition and
  produces `Pair<L::View<'a>, R::View<'a>>`; either input can itself be a join.
  `Pair<L, R>` has public `left` and `right` fields. Depth is recursive rather
  than fixed to a named arity.
- `stream::chain::Chain<S, O, Sc>` is the arity-free fluent continuation:
  `stream::cross::Penta::join` returns one, and `Chain::join` nests further `JoinNode`s with no
  ceiling; `Chain::filter` wraps the tree in an identity-preserving
  `FilterNode`; `Chain::penalize`/`reward` then `ChainBuilder::named` finalize
  into `OperatorTerminal`. The named `Bi`/`Tri`/`Quad`/`Penta` streams are
  ergonomic adapters over the same operator tree, not separate engines.
  The current cross-Penta continuation discards its authored penta filter; apply
  the row filter on the returned `Chain`. Same-source `PentaConstraintStream` is terminal.
- `constraint::relational::OperatorTerminal<S, O, W, Sc>::new(constraint_ref,
  impact, operator, weight, hard)` scores a generic operator; its weight takes
  `(&S, &O::View<'a>)`. It implements incremental scoring, reset, full counts,
  and cold explanations. `ExplainRow` is a hidden analysis bridge.
- `stream::joiner::plan::{CompileCondition, IndexedPlan, ExecutablePlan}`
  separates condition compilation, owned index state, and borrowed-row execution.
  Candidate probes return `Cow<'i, [RowHandle]>`: equality borrows an exact
  bucket; ordered, interval, and explicit scan plans may own candidate lists.
  `candidate_matches` checks residual semantics. Full traversal uses transient
  insertion without retaining equality reverse keys.
- `CompositeEquality<A, B>` and `EqualityWithResidual<E, P, FIRST>` are concrete
  compiled plan types. `ComposePlans`, `EqualityPlan`, `EqualityKeys`,
  `EqualityKind`, and `ResidualKind` supply structural dispatch. All equality
  components form one heterogeneous tuple key, including components separated
  by residual conditions.
- `PlannedCondition`, `Strategy`, and `plan_strategy` report deterministic
  structural strategy. Equality takes precedence over ordered/interval/scan
  conditions; the residual preserves the complete authored relationship.
- `stream::joiner::pair_row(a, a_index, b, b_index)` constructs the
  existing `Concat<Leaf<A>, B>` view using semantic indexes.

New file map: `stream/relational/operator.rs`,
`stream/relational/operator/{analysis,changes,collection,complement,complement_view,
existence,filter,flatten,group,group_view,
join,merge,project,self_join}.rs`,
`stream/joiner/plan.rs`, `stream/joiner/plan/{executable,hash_scan,conjunction,
composite,mixed_equality,ordered_interval}.rs`, `stream/joiner/row_key.rs`,
`stream/relational/index/{hash,ordered,interval}.rs`, and
`constraint/relational/operator_terminal.rs`. Generational direct side-tables
are internal in `stream/relational/handle_map.rs`.

This surface supports collection/filtered/merged/joined, owned projection,
group/complement, existence, and flattened producers. Uniform fluent migration
and compiler-owned derived-consumer sharing are not provided by this low-level
surface. Existing specialized families still exist.
See [Extending typed relational scoring](../../docs/extend-scoring.md) for
execution, ownership, and verification boundaries.

## Dependencies

- `solverforge-core` (path) — Score types, domain traits, descriptors, ConstraintRef, ImpactType
- `thiserror` (workspace) — error derive macros

## File Map

```
src/
├── lib.rs                                          — Crate root; re-exports from all modules
├── api/
│   ├── mod.rs                                      — Re-exports analysis, constraint_set, node_sharing, weight_overrides
│   ├── analysis.rs                                 — ScoreExplanation, ConstraintAnalysis, Indictment, IndictmentMap, DetailedConstraintMatch, etc.
│   ├── node_sharing.rs                             — SharedNodeDiagnostics, SharedNodeId, SharedNodeOperation
│   ├── constraint_set/
│   │   ├── mod.rs                                  — Re-exports ConstraintSet, ConstraintSetChain, OrderedConstraintSetChain, ConstraintSetSource, IncrementalConstraint, IncrementalConstraintSealed, ConstraintMetadata, ConstraintResult
│   │   ├── chain.rs                                — ConstraintSetChain, OrderedConstraintSetChain, and authored-order source spans
│   │   ├── incremental.rs                          — IncrementalConstraint trait, ConstraintSet trait, singleton and tuple impls (0..32)
│   │   └── tests/
│   │       ├── mod.rs                              — Test module declarations
│   │       └── constraint_set.rs                   — ConstraintSet tuple tests
│   ├── weight_overrides.rs                         — ConstraintWeightOverrides, WeightProvider trait
│   └── tests/
│       ├── mod.rs                                  — Test module declarations
│       ├── analysis.rs                             — Analysis type tests
│       └── weight_overrides.rs                     — Weight override tests
├── constraint/
│   ├── mod.rs                                      — Module declarations and selected root re-exports
│   ├── macros.rs                                   — impl_get_matches_nary! macro for detailed match generation
│   ├── shared.rs                                   — compute_hash<T>() utility function
│   ├── list_precedence.rs                          — ListPrecedenceMakespanConstraint<S>; stock incremental list-plus-fixed-precedence makespan scoring with optional fixed owner checks
│   ├── incremental_markers.rs                      — Hidden IncrementalConstraintSealed impls for built-in constraint types
│   ├── incremental.rs                              — IncrementalUniConstraint<S,A,E,F,W,Sc>
│   ├── grouped.rs                                  — grouped::Uni module root, shared node state, terminal scorers, scorer sets, shared set
│   ├── grouped/*.rs                                — scorer.rs, complemented_scorer.rs, scorer_set.rs, shared_set.rs, state.rs, terminal.rs for GroupedNodeState, terminal scorer sets, SharedGroupedConstraintSet, and the one-terminal wrapper
│   ├── balance.rs                                  — BalanceConstraint<S,A,K,E,F,KF,Sc>
│   ├── cross_grouped.rs                            — constraint::cross_grouped::Grouped module root and re-exports
│   ├── cross_grouped/*.rs                          — indexes.rs, scorer.rs, shared_set.rs, state.rs, terminal.rs, updates.rs, view.rs for retained direct cross grouped state
│   ├── cross_complemented_grouped.rs               — constraint::cross_complemented_grouped::ComplementedGrouped module root and internal shared engine re-exports
│   ├── cross_complemented_grouped/*.rs             — builder.rs, indexes.rs, scorer.rs, shared_set.rs, state.rs, terminal.rs, updates.rs, view.rs for retained direct cross grouped complements
│   ├── flattened_bi.rs                             — FlattenedBiConstraint module root and re-exports
│   ├── flattened_bi/*.rs                           — Retained flattened-bi state, incremental callbacks, and debug accessors
│   ├── exists.rs                                   — IncrementalExistsConstraint<S,A,P,B,K,EA,EP,KA,KB,FA,FP,Flatten,W,Sc>, SelfFlatten
│   ├── exists/
│   │   └── key_state.rs                            — Internal hashed/indexed key bookkeeping for existence constraints
│   ├── projected.rs                                — Projected retained scoring-row constraint module root and re-exports
│   ├── projected/uni.rs                            — projected::Uni terminal constraint for retained projected rows
│   ├── projected/bi.rs                             — projected::Bi retained symmetric projected self-join constraint
│   ├── projected/directed_bi.rs                    — projected::DirectedBi retained directed projected self-join state
│   ├── projected/directed_bi_incremental.rs        — IncrementalConstraint callbacks for directed projected self-joins
│   ├── projected/grouped.rs                        — projected::Grouped module root and shared grouped re-exports
│   ├── projected/complemented_grouped.rs           — projected::ComplementedGrouped module root and shared complemented-grouped re-exports
│   ├── projected/grouped/*.rs                      — scorer.rs, shared_set.rs, state.rs, terminal.rs for projected grouped retained state
│   ├── projected/complemented_grouped/*.rs         — indexes.rs, scorer.rs, shared_set.rs, state.rs, terminal.rs, view.rs for projected grouped complements
│   └── tests/
│       ├── mod.rs                                  — Test module declarations
│       ├── cross_bi_incr.rs                        — Fluent cross-bi join tests on the generic operator terminal
│       ├── self_join_bi_fluent.rs                  — Fluent same-collection self-join tests
│       ├── relational/self_join_node.rs            — SelfJoinNode unique-combination and delta tests
│       ├── relational/condition_arity.rs           — Comparison/overlap/composed join conditions and None-key complement
│       ├── relational/derived_right_input.rs       — Filtered and complemented producers as right-hand join inputs
│       ├── relational/join_depth.rs                — Six-binding typed join chain (depth beyond Penta)
│       ├── relational/fluent_deep_chain.rs         — Six- and seven-source fluent chains via Penta::join → Chain
│       ├── grouped.rs                              — constraint::grouped::Uni and shared grouped node tests
│       ├── cross_grouped.rs                        — Shared direct cross grouped node tests
│       ├── balance.rs                              — BalanceConstraint tests
│       ├── complemented.rs                         — Fluent complemented grouped tests
│       ├── cross_complemented_grouped.rs           — Direct cross-join grouped complement tests
│       ├── flattened_bi.rs                         — FlattenedBiConstraint tests
│       ├── exists.rs                               — IncrementalExistsConstraint update tests
│       ├── exists_storage.rs                       — Existence storage selection and parity tests
│       ├── projected.rs                            — Projected constraint test module root
│       ├── projected/*.rs                          — Projected support fixtures, localization, update, grouping, merge, and self-join tests
│       └── repro_unknown.rs                        — Regression fixture coverage for unknown-source behavior
├── director/
│   ├── mod.rs                                      — Re-exports all director types and traits
│   ├── traits.rs                                   — Director<S> trait
│   ├── score_director.rs                           — Re-exports ScoreDirector pieces
│   │   ├── score_director/incremental.rs           — ScoreDirector<S,C> (zero-erasure incremental)
│   │   └── score_director/adapters.rs              — Debug and Director trait impls for ScoreDirector
│   ├── shadow_aware.rs                             — SolvableSolution trait and shadow lifecycle notes
│   └── tests/
│       ├── mod.rs                                  — Test module declarations
│       ├── bench.rs                                — Benchmark test module declarations
│       ├── benchmarks.rs                           — Performance comparison tests
│       ├── fixtures.rs                             — ScoreDirector fixtures
│       ├── fixtures_tests.rs                       — ScoreDirector fixture tests
│       ├── score_director.rs                       — ScoreDirector tests
│       └── shadow.rs                               — Shadow-aware director tests

├── stream/
│   ├── mod.rs                                      — Module declarations and re-exports for all stream types
│   ├── tests.rs                                    — Stream API tests
│   ├── factory.rs                                  — ConstraintFactory<S,Sc>
│   ├── uni_stream.rs                               — Re-exports
│   ├── uni_stream/base.rs                          — UniConstraintStream
│   ├── uni_stream/weighting.rs                     — UniConstraintBuilder and weighting helpers
│   ├── bi_stream.rs                                — BiConstraintStream, BiConstraintBuilder (via macro)
│   ├── tri_stream.rs                               — TriConstraintStream, TriConstraintBuilder (via macro)
│   ├── quad_stream.rs                              — QuadConstraintStream, QuadConstraintBuilder (via macro)
│   ├── penta_stream.rs                             — PentaConstraintStream, PentaConstraintBuilder (via macro)
│   ├── chain_stream.rs                             — Chain/ChainBuilder: arity-free fluent join continuation
│   ├── cross_penta_stream.rs                       — stream::cross::Penta and its Penta::join → Chain
│   ├── grouped_stream.rs                           — Re-exports
│   ├── grouped_stream/base.rs                      — GroupedConstraintStream
│   ├── grouped_stream/weighting.rs                 — GroupedConstraintBuilder
│   ├── grouped_stream/joined.rs                    — GroupedJoinedStream/Builder: group as a join input
│   ├── balance_stream.rs                           — BalanceConstraintStream, BalanceConstraintBuilder
│   ├── complemented_stream.rs                      — ComplementedConstraintStream, ComplementedConstraintBuilder
│   ├── cross_bi_stream.rs                          — Re-exports
│   ├── cross_bi_stream/base.rs                     — stream::cross::Bi
│   ├── cross_bi_stream/scored.rs                   — BiUnaryPlan, BiPredicatePlan, BiScored (first-join execution)
│   ├── cross_bi_stream/view_comparison.rs          — ViewComparisonPlan, ViewOverlapPlan (entity conditions over leaf views)
│   ├── cross_bi_stream/grouped.rs                  — stream::cross::Grouped and builder
│   ├── cross_bi_stream/complemented_grouped.rs     — stream::cross::ComplementedGrouped and builder
│   ├── cross_bi_stream/weighting.rs                — stream::cross::Builder
│   ├── cross_tri_stream.rs                         — Re-exports
│   ├── cross_tri_stream/base.rs                    — stream::cross::Tri and builder
│   ├── flattened_bi_stream.rs                      — Re-exports
│   ├── flattened_bi_stream/base.rs                 — FlattenedBiConstraintStream
│   ├── flattened_bi_stream/builder.rs              — FlattenedBiConstraintBuilder
│   ├── flattened_bi_stream/weighting.rs            — Weighting helpers for flattened streams
│   ├── existence_stream.rs                         — ExistsConstraintStream, ExistsConstraintBuilder, ExistenceMode
│   ├── existence_target.rs                         — ExistenceTarget trait for direct and flattened existence targets
│   ├── projected_stream.rs                         — Projected stream module root and re-exports
│   ├── projected_stream/uni.rs                     — stream::projected::Stream and terminal builder
│   ├── projected_stream/bi.rs                      — stream::projected::Bi and terminal builder
│   ├── projected_stream/directed_bi.rs             — stream::projected::DirectedBi and terminal builder for directed projected self-joins
│   ├── projected_stream/join_target.rs             — ProjectedJoinTarget dispatch for symmetric and directed projected self-joins
│   ├── projected_stream/grouped.rs                 — stream::projected::Grouped, reward, complement, and terminal builder
│   ├── projected_stream/complemented_grouped.rs    — stream::projected::ComplementedGrouped and builder
│   ├── projected_stream/source.rs                  — Projection, projected row coordinates, Source trait
│   ├── projected_stream/source/single.rs           — Single-source `.project(...)` source
│   ├── projected_stream/source/filtered.rs         — Row-level filtered projected source
│   ├── projected_stream/source/merged.rs           — Merged projected sources with source-slot offsets
│   ├── projected_stream/source/joined.rs           — Cross-join `.project(...)` projected source
│   ├── collection_extract.rs                       — CollectionExtract trait, hidden source metadata and FilteredExtract membership adapter, VecExtract wrapper, vec() constructor
│   ├── unassigned.rs                               — Hidden UnassignedEntity hook and `.unassigned()` stream method
│   ├── weighting_support.rs                        — ConstraintWeight, FixedWeight, HardWeight, and dynamic closure-weight adapters
│   ├── join_target.rs                              — JoinTarget/ToViewPlan dispatch for self-join, keyed cross-join, comparison, overlap, composed, and predicate cross-join
│   ├── key_extract.rs                              — KeyExtract trait, EntityKeyAdapter struct
│   ├── arity_stream_macros/
│   │   ├── mod.rs                                  — impl_arity_stream! dispatcher macro
│   │   ├── nary_stream.rs                          — Module declarations for arity stream macros
│   │   └── nary_stream/
│   │       ├── shared.rs                           — Shared arity stream macro helpers
│   │       ├── bi.rs                               — impl_bi_arity_stream! macro
│   │       ├── tri.rs                              — impl_tri_arity_stream! macro
│   │       ├── quad.rs                             — impl_quad_arity_stream! macro
│   │       └── penta.rs                            — impl_penta_arity_stream! macro
│   ├── filter/
│   │   ├── mod.rs                                  — Re-exports filter types
│   │   ├── traits.rs                               — UniFilter, BiFilter, TriFilter, QuadFilter, PentaFilter traits
│   │   ├── wrappers.rs                             — TrueFilter, FnUniFilter, FnBiFilter, FnTriFilter, FnQuadFilter, FnPentaFilter
│   │   ├── adapters.rs                             — UniBiFilter, UniLeftBiFilter, UniPairFilter, and hidden PairFilter adapters
│   │   ├── composition.rs                          — AndUniFilter, AndBiFilter, AndTriFilter, AndQuadFilter, AndPentaFilter
│   │   └── tests/
│   │       ├── mod.rs                              — Test module declarations
│   │       └── filter.rs                           — Filter tests
│   ├── joiner/
│   │   ├── mod.rs                                  — Re-exports all joiner types and functions
│   │   ├── equal.rs                                — EqualJoiner, Symmetric/Directed mode markers, equal(), equal_bi() functions
│   │   ├── comparison.rs                           — LessThan/GreaterThan joiners and factory functions
│   │   ├── filtering.rs                            — FilteringJoiner and filtering() function
│   │   ├── overlapping.rs                          — OverlappingJoiner and overlapping() function
│   │   └── match_condition.rs                      — Joiner trait, AndJoiner, FnJoiner
│   └── collector/
│       ├── mod.rs                                  — Re-exports collector types
│       ├── core.rs                                 — Collector<Input> trait, Accumulator trait
│       ├── count.rs                                — CountCollector, CountAccumulator, count()
│       ├── sum.rs                                  — SumCollector, SumAccumulator, sum()
│       ├── load_balance.rs                         — LoadBalanceCollector, LoadBalanceAccumulator, LoadBalance, load_balance()
│       ├── runs.rs                                 — RunsCollector, RunsAccumulator, Run, Runs, consecutive_runs()
│       ├── indexed_presence.rs                     — IndexedPresenceCollector, IndexedPresenceAccumulator, IndexedPresence, indexed_presence()
│       ├── collect_vec.rs                          — CollectVecCollector, CollectVecAccumulator, CollectedVec, collect_vec()
│       └── tests/
│           ├── mod.rs                              — Test module declarations
│           └── collector.rs                        — Collector tests
```

## Public Re-exports (lib.rs)

```rust
// Root constraint exports keep globally distinct names only.
pub use constraint::{IncrementalUniConstraint, ListPrecedenceMakespanConstraint};

// Short family names are intentionally module-scoped:
// constraint::grouped::Uni
// constraint::cross_grouped::Grouped
// constraint::cross_complemented_grouped::ComplementedGrouped
// constraint::projected::{Uni, Bi, DirectedBi, Grouped, ComplementedGrouped}

// Constraint Set
pub use api::constraint_set::{
    ConstraintMetadata, ConstraintResult, ConstraintSet, IncrementalConstraint,
    IncrementalConstraintSealed,
};
pub use api::node_sharing::{SharedNodeDiagnostics, SharedNodeId, SharedNodeOperation};
pub use api::weight_overrides::{ConstraintWeightOverrides, WeightProvider};

// Score Directors
pub use director::score_director::ScoreDirector;
pub use director::{Director, DirectorScoreState, SolvableSolution};

// Analysis
pub use api::analysis::{
    ConstraintAnalysis, ConstraintJustification, DetailedConstraintEvaluation,
    DetailedConstraintMatch, EntityRef, Indictment, IndictmentMap, ScoreExplanation,
};

// Fluent Stream API
pub use stream::{
    fixed_weight, hard_weight, BiConstraintBuilder, BiConstraintStream, ConstraintFactory,
    FixedWeight, GroupedConstraintBuilder, GroupedConstraintStream, HardWeight,
    Projection, ProjectionSink, UniConstraintBuilder, UniConstraintStream,
};

// Short cross/projected stream names are intentionally module-scoped:
// stream::cross::{Bi, Builder, Grouped, GroupedBuilder, ComplementedGrouped, ComplementedGroupedBuilder, Tri}
// stream::projected::{Stream, Builder, Bi, BiBuilder, DirectedBi, DirectedBiBuilder, Grouped, GroupedBuilder, ComplementedGrouped, ComplementedGroupedBuilder}
```

## Public Traits

### `Director<S: PlanningSolution>` — `Send`

| Method | Signature | Note |
|--------|-----------|------|
| `working_solution` | `fn working_solution(&self) -> &S` | Current solution reference |
| `working_solution_mut` | `fn working_solution_mut(&mut self) -> &mut S` | Mutable solution reference |
| `calculate_score` | `fn calculate_score(&mut self) -> S::Score` | Full score calculation |
| `fresh_score` | `fn fresh_score(&self) -> Option<S::Score>` | Optional scratch score without mutating committed director state; default `None` |
| `solution_descriptor` | `fn solution_descriptor(&self) -> &SolutionDescriptor` | Runtime metadata |
| `clone_working_solution` | `fn clone_working_solution(&self) -> S` | Deep copy |
| `before_variable_changed` | `fn before_variable_changed(&mut self, descriptor_index: usize, entity_index: usize)` | Pre-change notification |
| `after_variable_changed` | `fn after_variable_changed(&mut self, descriptor_index: usize, entity_index: usize)` | Post-change notification |
| `entity_count` | `fn entity_count(&self, descriptor_index: usize) -> Option<usize>` | Count entities by descriptor |
| `total_entity_count` | `fn total_entity_count(&self) -> Option<usize>` | Total across all descriptors |
| `constraint_metadata` | `fn constraint_metadata(&self) -> Vec<ConstraintMetadata<'_>>` | Borrowed constraint metadata views known to this director |
| `constraint_is_hard` | `fn constraint_is_hard(&self, constraint_ref: &ConstraintRef) -> Option<bool>` | Exact identity helper derived from `constraint_metadata()` |
| `is_incremental` | `fn is_incremental(&self) -> bool` | Default: false |
| `snapshot_score_state` | `fn snapshot_score_state(&self) -> DirectorScoreState<S::Score>` | Snapshot committed score state for speculative evaluation |
| `restore_score_state` | `fn restore_score_state(&mut self, state: DirectorScoreState<S::Score>)` | Restore a previously snapshotted committed score state |
| `reset` | `fn reset(&mut self)` | Default: no-op |

### `DirectorScoreState<Sc>`

Committed score-state snapshot used to roll back speculative evaluation. Fields:
`solution_score`, `committed_score`, `initialized`.

### `IncrementalConstraint<S, Sc: Score>` — `IncrementalConstraintSealed + Send + Sync`

| Method | Signature | Note |
|--------|-----------|------|
| `evaluate` | `fn evaluate(&self, solution: &S) -> Sc` | Full recalculation |
| `match_count` | `fn match_count(&self, solution: &S) -> usize` | Number of matches |
| `initialize` | `fn initialize(&mut self, solution: &S) -> Sc` | Initialize state for incremental |
| `on_insert` | `fn on_insert(&mut self, solution: &S, entity_index: usize, descriptor_index: usize) -> Sc` | Incremental insert delta |
| `on_retract` | `fn on_retract(&mut self, solution: &S, entity_index: usize, descriptor_index: usize) -> Sc` | Incremental retract delta |
| `reset` | `fn reset(&mut self)` | Clear incremental state |
| `name` | `fn name(&self) -> &str` | Constraint name; default derives from `constraint_ref()` |
| `is_hard` | `fn is_hard(&self) -> bool` | Default: false |
| `constraint_ref` | `fn constraint_ref(&self) -> &ConstraintRef` | Borrowed package-qualified identity owned by the constraint |
| `get_matches` | `fn get_matches<'a>(&'a self, _solution: &S) -> Vec<DetailedConstraintMatch<'a, Sc>>` | Default: empty |
| `weight` | `fn weight(&self) -> Sc` | Default: Sc::zero() |

### `ConstraintSet<S, Sc: Score>` — `Send + Sync`

| Method | Signature | Note |
|--------|-----------|------|
| `evaluate_all` | `fn evaluate_all(&self, solution: &S) -> Sc` | Sum all constraints |
| `constraint_count` | `fn constraint_count(&self) -> usize` | Number of constraints |
| `constraint_metadata` | `fn constraint_metadata(&self) -> Vec<ConstraintMetadata<'_>>` | Deduplicated borrowed ref/name/hardness metadata |
| `constraint_metadata_entries` | `fn constraint_metadata_entries(&self) -> Vec<ConstraintMetadata<'_>>` | Hidden raw metadata entries in authored order |
| `evaluate_each` | `fn evaluate_each<'a>(&'a self, solution: &S) -> Vec<ConstraintResult<'a, Sc>>` | Per-constraint results |
| `evaluate_detailed` | `fn evaluate_detailed<'a>(&'a self, solution: &S) -> Vec<ConstraintAnalysis<'a, Sc>>` | With match details |
| `initialize_all` | `fn initialize_all(&mut self, solution: &S) -> Sc` | Initialize all for incremental |
| `on_insert_all` | `fn on_insert_all(&mut self, solution: &S, entity_index: usize, descriptor_index: usize) -> Sc` | Incremental insert all |
| `on_retract_all` | `fn on_retract_all(&mut self, solution: &S, entity_index: usize, descriptor_index: usize) -> Sc` | Incremental retract all |
| `reset_all` | `fn reset_all(&mut self)` | Reset all |

Implemented for each `IncrementalConstraint<S, Sc>` as a singleton set and for tuples `()` through `(C0, C1, ..., C31)` where each `Ci: ConstraintSet<S, Sc>`. `ConstraintSetChain<Left, Right>` composes two existing `ConstraintSet` values without erasing either concrete type. `OrderedConstraintSetChain<Left, Right>` uses `ConstraintSetSource::Left` and `ConstraintSetSource::Right { constraint_count, metadata_entry_count }` spans to preserve user-authored result ordering when macro-generated node sharing combines one shared set with surrounding constraints. Raw metadata entries keep one entry per constraint for ordering; the public `constraint_metadata()` view deduplicates repeated full `ConstraintRef`s when hardness agrees and panics when the same full ref has conflicting hard/non-hard metadata. Package-qualified constraints that share a short name remain distinct.

### Shadow Lifecycle on `PlanningSolution`

`PlanningSolution` itself owns the canonical shadow hooks:
- `update_entity_shadows(&mut self, descriptor_index: usize, entity_index: usize)` — default no-op
- `update_all_shadows(&mut self)` — default no-op

### `SolvableSolution` — `: PlanningSolution`

| Method | Signature | Note |
|--------|-----------|------|
| `descriptor` | `fn descriptor() -> SolutionDescriptor` | Static descriptor |
| `entity_count` | `fn entity_count(solution: &Self, descriptor_index: usize) -> usize` | Entity count |

### `WeightProvider<Sc: Score>` — `Send + Sync`

| Method | Signature | Note |
|--------|-----------|------|
| `weight` | `fn weight(&self, name: &str) -> Option<Sc>` | Lookup override weight |
| `weight_or_default` | `fn weight_or_default(&self, name: &str, default: Sc) -> Sc` | Default: uses weight() |

### Filter Traits

All `Send + Sync`:
- `UniFilter<S, A>` — `fn test(&self, solution: &S, a: &A) -> bool`
- `BiFilter<S, A, B>` — `fn test(&self, solution: &S, a: &A, b: &B, a_idx: usize, b_idx: usize) -> bool`
- `TriFilter<S, A, B, C>` — `fn test(&self, solution: &S, a: &A, b: &B, c: &C, a_idx: usize, b_idx: usize, c_idx: usize) -> bool`
- `QuadFilter<S, A, B, C, D>` — `fn test(&self, solution: &S, a: &A, b: &B, c: &C, d: &D, a_idx: usize, b_idx: usize, c_idx: usize, d_idx: usize) -> bool`
- `PentaFilter<S, A, B, C, D, E>` — `fn test(&self, solution: &S, a: &A, b: &B, c: &C, d: &D, e: &E, a_idx: usize, b_idx: usize, c_idx: usize, d_idx: usize, e_idx: usize) -> bool`

Joined filter indexes are semantic source indexes, not builder-local
placeholders. Same-source joins pass canonical entity indexes; cross-bi passes
left and right source indexes; cross-tri passes the A, B, and C source slice
indexes; flattened-bi passes the A source index and the
owning B source index for the flattened row; projected-bi passes each projected
row's primary owner entity index while `RowCoordinate` still owns row
orientation.

### `Joiner<A, B>` — `Send + Sync`

| Method | Signature | Note |
|--------|-----------|------|
| `matches` | `fn matches(&self, a: &A, b: &B) -> bool` | Test if pair matches |
| `and` | `fn and<J>(self, other: J) -> AndJoiner<Self, J>` | Compose joiners |

### Collector Traits

**`Collector<Input>` — `Send + Sync`**

| Associated Type | Bound | Note |
|-----------------|-------|------|
| `Value` | — | Extracted value type |
| `Result` | `Send + Sync` | Borrowed result view type |
| `Accumulator` | `Accumulator<Self::Value, Self::Result>` | Stateful accumulator |

| Method | Signature | Note |
|--------|-----------|------|
| `extract` | `fn extract(&self, input: Input) -> Self::Value` | Extract value from the borrowed stream match |
| `create_accumulator` | `fn create_accumulator(&self) -> Self::Accumulator` | Create fresh accumulator |

`Input` is the stream match shape: unary and projected grouping pass `&A` or
`&Out`, while direct cross-join grouping passes `(&A, &B)`. Stock collectors are
generic over that input shape, so direct cross joins use the same collector API
as unary streams, for example `sum(|(shift, employee): (&Shift, &Employee)| ...)`.

**`Accumulator<V, R>` — `Send + Sync`**

| Associated Type | Bound | Note |
|-----------------|-------|------|
| `Retraction` | `Send + Sync` | Token returned by accumulation and cached for exact retraction |

| Method | Signature | Note |
|--------|-----------|------|
| `accumulate` | `fn accumulate(&mut self, value: V) -> Self::Retraction` | Add owned value and return retraction token |
| `retract` | `fn retract(&mut self, retraction: Self::Retraction)` | Remove exactly the retained value represented by the token |
| `with_result` | `fn with_result<T>(&self, f: impl FnOnce(&R) -> T) -> T` | Expose current result without forcing an owned clone |
| `finish` | `fn finish(&self) -> R where R: Clone` | Convenience owned snapshot for cloneable results |
| `reset` | `fn reset(&mut self)` | Clear state |

Stock collectors include `count()`, `sum()`, `load_balance()`,
`consecutive_runs()`, `indexed_presence()`, and `collect_vec()`.
`collect_vec()` owns mapped values once and exposes them as `CollectedVec<T>`;
`T` does not need `Copy`, `Clone`, or `PartialEq`.

## Public Structs

### Score Directors

**`ScoreDirector<S, C>`** where `S: PlanningSolution`, `C: ConstraintSet<S, S::Score>`
- Primary incremental scoring director. Zero-erasure.
- Public methods: `new()`, `with_descriptor()`, `simple()` (convenience for
  `ScoreDirector<S, ()>`), `simple_zero()` (test helper with empty descriptor),
  `working_solution()`, `working_solution_mut()`, `into_working_solution()`,
  `calculate_score()`, `before_variable_changed()`,
  `after_variable_changed()`, `do_change()`, `get_score()`, `reset()`,
  `clone_working_solution()`, `constraints()`, `constraints_mut()`,
  `constraint_metadata()`, `constraint_count()`, `is_initialized()`,
  `constraint_match_totals()`, and `take_solution()`
- Returns borrowed constraint metadata views from the monomorphized `ConstraintSet` on demand.
- `simple(solution, descriptor, entity_counter)` — creates `ScoreDirector<S, ()>` with empty constraint set
- `simple_zero(solution)` — creates `ScoreDirector<S, ()>` with empty descriptor and zero entity counter
- Implements `Director<S>`

### Constraint Types

All implement `IncrementalConstraint<S, Sc>`.

**`IncrementalUniConstraint<S, A, E, F, W, Sc>`** — Single-collection constraint with filter and weight.

**`OperatorTerminal<S, O, W, Sc>`** — Generic scoring terminal over a concrete operator tree, independent of row arity. Same-collection self-joins finalize here over a `SelfJoinNode`, so bi/tri/quad/penta share one terminal instead of per-arity engines.

**`ListPrecedenceMakespanConstraint<S>`** — Stock incremental
list-plus-fixed-precedence constraint over `HardSoftScore`. `new(...)` binds the
list descriptor, node/owner accessors, durations, and fixed successors;
`with_expected_owner(...)` optionally adds fixed-owner validation.

**`stream::cross::{Bi, Tri}` finalization** — Cross-collection joins finalize onto the generic `constraint::relational::OperatorTerminal`. Each join is a concrete `JoinNode` over a compiled condition plan: the fluent `.join((target, joiner))` carries its own key/condition type, and a row-aware joiner receives the whole borrowed left row. Stateless `evaluate()`, `match_count()`, and `get_matches()` traverse the operator tree directly, so retained analysis works before `initialize()`. Authored bi/tri filters run inside the scored operator, so filters receive the A/B/C source indexes on every path (direct, grouped, and projected) and are applied exactly once across evaluate and retained mutations.

**`constraint::grouped::Uni<S, A, K, E, Fi, KF, C, V, R, Acc, W, Sc>`** where `C: Collector<&A>` — Group-by with collector and weight on `(&K, &R)`.

The grouped engine is split into `GroupedNodeState` plus
`GroupedTerminalScorer` collections. `SharedGroupedConstraintSet` updates the
node once and refreshes all terminal scorers from changed group keys.
`constraint::grouped::Uni` is the one-terminal wrapper around that shared engine.
Additional grouped terminals append through the same fluent
`.penalize(...).named(...)` / `.reward(...).named(...)` finalization chain,
so macro-generated sharing does not use a separate stream construction path.

**`constraint::cross_grouped::Grouped<S, A, B, JK, GK, EA, EB, KA, KB, F, GF, C, V, R, Acc, W, Sc>`** where `C: Collector<(&A, &B)>` — Direct grouped cross-join constraint. It keeps keyed join indexes and collector retraction tokens without projecting joined pairs first.

Direct cross grouped constraints use `constraint::cross_grouped::GroupedNodeState` for join indexes,
match rows, group accumulators, retraction tokens, and changed-key reporting.
`constraint::cross_grouped::SharedGroupedSet` lets multiple terminal scorers consume that
state while preserving independent terminal metadata.

**`constraint::cross_complemented_grouped::ComplementedGrouped<S, A, B, T, JK, GK, EA, EB, ET, KA, KB, F, GF, KT, C, V, R, Acc, D, W, Sc>`** where `C: Collector<(&A, &B)>` — Direct grouped cross-join constraint complemented against a second collection. `constraint::cross_complemented_grouped::ComplementedGroupedNodeState` keeps join indexes, joined-pair collector retraction tokens, complement target indexes, group accumulators, and changed-key reporting; `constraint::cross_complemented_grouped::SharedComplementedGroupedSet` lets multiple terminal scorers consume each complement row using either the retained grouped result or the provided default result.

**`BalanceConstraint<S, A, K, E, F, KF, Sc>`** — Load balancing using sum-of-squared-deviations.

**`ComplementNode<S, L, R, P, F, T>`** (operator layer) — Target-domain left outer join with owned defaults, over a grouped result domain. `group_by().complement()` finalizes into this node inside the shared `OperatorTerminal`.

**`constraint::projected::Uni<S, Out, Src, F, W, Sc>`** — Terminal constraint for scoring retained projected rows one row at a time.

**`constraint::projected::Bi<S, Out, K, Src, F, KF, PF, W, Sc>`** — Symmetric self-join constraint over retained projected rows. Pair ordering is coordinate-stable by `RowCoordinate`; pair-filter indexes are the projected rows' primary owner entity indexes, never retained storage row IDs. It is produced by `stream::projected::Stream::join(equal(...))`.

**`constraint::projected::DirectedBi<S, Out, K, Src, F, KL, KR, PF, W, Sc>`** — Directed self-join constraint over retained projected rows with distinct left and right key extractors. It is produced by `stream::projected::Stream::join(equal_bi(left_key, right_key))`, evaluates oriented pairs where the left key equals the right key, and skips only the same retained row.

**`constraint::projected::Grouped<S, Out, K, Src, F, KF, C, V, R, Acc, W, Sc>`** where `C: Collector<&Out>` — Grouped retained projected rows.

Projected grouped constraints use `constraint::projected::GroupedNodeState` for projected
source state, row outputs, owner indexes, retraction tokens, group accumulators,
and changed-key reporting. `constraint::projected::SharedGroupedSet` shares that
projected grouped state across multiple terminal scorers.

**`constraint::projected::ComplementedGrouped<S, Out, B, K, Src, EB, F, KA, KB, C, V, R, Acc, D, W, Sc>`** where `C: Collector<&Out>` — Projected grouped rows complemented against a second collection, including `join(...).project(...).group_by(...).complement(...)` chains. `constraint::projected::ComplementedGroupedNodeState` retains projected source state, row ownership indexes, complement target rows, group accumulators, and changed-key reporting for shared terminal scorers.

**`FlattenedBiConstraint<S, A, B, C, K, CK, EA, EB, KA, KB, Flatten, CKeyFn, ALookup, F, W, Sc>`** — Cross-collection with nested collection flattening. Filters receive the A source index and the owning B source index for each flattened C row.

**`IncrementalExistsConstraint<S, A, P, B, K, EA, EP, KA, KB, FA, FP, Flatten, W, Sc>`** — Existence/non-existence check over a source-aware direct or flattened collection source. The constraint owns one scoring algorithm and delegates only key bookkeeping to an internal `ExistsKeyState`: exact `usize` keys use indexed `Vec` storage, while all other key types use hashed storage.

**`ExistenceMode`** — `enum { Exists, NotExists }`

#### Shared grouped-state support

The grouped compiler surface deliberately exposes concrete retained-state and
builder carriers so `#[solverforge_constraints]` can append terminals without
erasing types. Unary sharing returns `GroupedConstraintSetBuilder`; direct
cross and projected sharing return their module-specific `GroupedSetBuilder`;
complemented direct/projected sharing returns
`ComplementedGroupedSetBuilder`. The corresponding immutable analysis views
are module-specific `GroupedEvaluationState` and
`ComplementedGroupedEvaluationState` values.

`GroupedStateView<K, R>` / `ComplementedGroupedStateView<K, R>` expose retained
group results to scorers. `GroupedScorerSet<K, R, Sc>` /
`ComplementedGroupedScorerSet<K, R, Sc>` are the concrete tuple-scorer
dispatch contracts, and `ComplementedGroupedTerminalScorer` is the module-local
alias of the common grouped terminal scorer. These support symbols are
module-level compiler bridges (several are `#[doc(hidden)]`); they are not
crate-root modeling APIs and do not introduce runtime node caches.

Shared grouped node states expose `update_count()`, `changed_key_count()`, and
`evaluation_state()` for compiler diagnostics. `GroupedTerminalScorer` exposes
`refresh_all()`, `refresh_changed()`, and `refresh_count()` for the concrete
shared-set dispatch path.

**`SharedNodeId(pub usize)`** — Stable diagnostic identifier for one shared
compiler node.

**`SharedNodeOperation`** — `Grouped`, `ProjectedGrouped`, `CrossGrouped`,
`CrossComplementedGrouped`, or `ProjectedComplementedGrouped`.

**`SharedNodeDiagnostics`** — Public fields `{ id, fingerprint, operation,
terminal_consumers, update_count, changed_key_count }`; construct with `new(...)`.

### Analysis Types

Constraints own their `ConstraintRef` once. Metadata and analysis types borrow that identity so package-qualified constraint names remain intact without cloning `ConstraintRef` in scoring or reporting paths.

**`ConstraintResult<'a, Sc>`** — `{ name: &'a str, score: Sc, match_count: usize, is_hard: bool }`

**`ConstraintMetadata<'a>`** — `{ constraint_ref: &'a ConstraintRef, is_hard: bool }`; `name()` returns the short constraint name, and `full_name()` returns the package-qualified identity used for exact matching.

**`EntityRef`** — Public fields `{ type_name: String, display: String }` plus a private cloned entity snapshot for typed downcast access.
- Methods: `new()`, `with_display()`, `as_entity::<T>()`, `short_type_name()`
- Implements `Hash + Eq` (by display string)

**`ConstraintJustification`** — `{ entities: Vec<EntityRef>, description: String }`.
Construct with `new(entities)` or `with_description(entities, description)`.

**`DetailedConstraintMatch<'a, Sc: Score>`** — `{ constraint_ref: &'a ConstraintRef, score: Sc, justification: ConstraintJustification }`

**`DetailedConstraintEvaluation<'a, Sc: Score>`** — `{ total_score: Sc, match_count: usize, matches: Vec<DetailedConstraintMatch<'a, Sc>> }`

**`ConstraintAnalysis<'a, Sc: Score>`** — `{ constraint_ref: &'a ConstraintRef, weight: Sc, score: Sc, matches: Vec<DetailedConstraintMatch<'a, Sc>>, is_hard: bool }`

**`ScoreExplanation<'a, Sc: Score>`** — `{ score: Sc, constraint_analyses: Vec<ConstraintAnalysis<'a, Sc>> }`
- Methods: `total_match_count()`, `non_zero_constraints()`, `all_matches()`

**`Indictment<'a, Sc: Score>`** — `{ entity: EntityRef, score: Sc, constraint_matches: HashMap<&'a ConstraintRef, Vec<DetailedConstraintMatch<'a, Sc>>> }`
- Methods: `add_match()`, `match_count()`, `violated_constraints()`, `constraint_count()`

**`IndictmentMap<'a, Sc: Score>`** — `{ indictments: HashMap<EntityRef, Indictment<'a, Sc>> }`
- Methods: `from_matches()`, `get()`, `entities()`, `worst_entities()`, `len()`, `is_empty()`

**`ConstraintWeightOverrides<Sc: Score>`** — Runtime map of constraint-name weight overrides.
- Methods: `new()`, `from_pairs()`, `put()`, `remove()`, `get_or_default()`, `get()`, `contains()`, `len()`, `is_empty()`, `clear()`, `into_arc()`

**`FixedWeight<Sc>` / `fixed_weight(score)`** — Public zero-erasure wrapper for custom fixed score weights. Use `penalize(fixed_weight(custom_score))` or `reward(fixed_weight(custom_score))` when `Sc` is user-defined.

**`HardWeight<W>` / `hard_weight(weight)`** — Public zero-erasure wrapper that forces hard constraint metadata while delegating scoring to the wrapped fixed or dynamic weight.

Dynamic closure weights are non-hard metadata by default, even when their score type has a hard level. Wrap with `hard_weight(...)` when analysis metadata must report the constraint as hard.

### Stream Builders (Fluent API)

**`ConstraintFactory<S, Sc: Score>`** — Entry point.
- `new()`, `for_each()` → `UniConstraintStream`
- Generated solution source methods pass `for_each()` hidden descriptor/static source metadata.

**`UniConstraintStream<S, A, E, F, Sc>`** — Single collection stream.
- Operations: `filter()`, `unassigned()` when the entity implements hidden `UnassignedEntity<S>`, `join(target)` (single dispatch via `JoinTarget`), `group_by()`, `balance()`, `project(projection)` → `stream::projected::Stream`, `flattened(flatten)` → `FlattenedCollectionTarget`, `if_exists(target)`, `if_not_exists(target)`, `penalize(weight_or_fn)`, `reward(weight_or_fn)`
- `UniConstraintStream` implements `CollectionExtract` by delegating extraction to its source and applying its accumulated filter through `contains(...)`.
- Stream targets preserve their own source filters when passed to keyed or predicate cross-joins. This lets `.join((ConstraintFactory::new().for_each(source).filter(pred), equal_bi(...)))` keep the right-side source predicate inside the joined stream.
- For the first unary keyed equality cross-join, the left unary filter is preserved through hidden source membership before key extraction and pair creation. Original source indexes and change metadata remain intact across incremental membership transitions. Post-join filters remain joined-row filters. This left-filter pushdown does not apply to comparison, overlap, composed-condition, predicate, or same-source self-join dispatch.
- `join()` dispatch: `equal(|a| key)` → self-join `BiConstraintStream`; `(extractor_b, equal_bi(ka, kb))` → keyed `stream::cross::Bi` (`BiUnaryPlan`); `(extractor_b, less_than/greater_than/…_or_equal(a, b))` → comparison `stream::cross::Bi` (`ViewComparisonPlan`); `(extractor_b, overlapping(sa, ea, sb, eb))` → overlap `stream::cross::Bi` (`ViewOverlapPlan`); `(extractor_b, a.and(b))` → composed `stream::cross::Bi` (operands convert via `ToViewPlan` into one concrete conjunction, `EqualityWithResidual` when equality keys the probe); `(other_stream, |a, b| pred)` → predicate `stream::cross::Bi` (`BiPredicatePlan`, an explicit opposite-input scan — never a synthetic constant equality key)
- `into_parts()` → `(E, F)`, `from_parts(extractor, filter)` → `Self`, `extractor()` → `&E`

**`UniConstraintBuilder<S, A, E, F, W, Sc>`** — `named()` → `IncrementalUniConstraint`

**`Projection<A>`** — Retained projection contract for single-source `.project(...)`. Implementations define `type Out`, `const MAX_EMITS: usize`, and `project(&self, input: &A, sink: &mut impl ProjectionSink<Self::Out>)`. Projection implementations emit bounded scoring rows into the sink; Vec-returning closures are not part of the API. `Out` does not need `Clone`.

The fluent projected stream keeps its concrete source type in the return type:
`SingleSource` for one projection, `FilteredSource` after a projected filter,
`MergedSource` after merging compatible projected streams, and `JoinedSource`
for a projected cross join. `JoinedState` is that source's retained keyed
index. Their constructors are crate-private and they are not root exports;
callers obtain these opaque carriers through fluent operations and continue on
`stream::projected::Stream`.

**`ProjectionSink<Out>`** — Emission sink used by `Projection<A>` implementations. `emit(output)` is the only projection output channel.

**`stream::projected::Stream<S, Out, Src, F, Sc>`** — Scoring rows from one or more source streams. Single-source output type is inferred from the named projection type passed to `project(...)`; keyed cross joins use `stream::cross::Bi::project(|left, right| row)` and emit exactly one scoring row per retained joined pair. Retained rows are cached by `RowCoordinate` and indexed by one or two `RowOwner` values. Single-source projected rows update incrementally from their source owner; joined-pair projected rows update incrementally from either joined source when that source is descriptor-localized. Symmetric projected self-join pair order follows `RowCoordinate` ordering; pair-filter indexes use each row's primary owner entity index, and retained storage row IDs are internal and never semantic. Projected rows can be self-joined by `equal(|row| key)` or by directed `equal_bi(left_key, right_key)` without materialized facts, and projected output rows plus projected self-join keys do not need `Clone`. Raw `for_each` extractors with `ChangeSource::Unknown` can evaluate and initialize projected constraints, but localized incremental callbacks panic because their entity indexes cannot be mapped safely.
- Operations: `filter()`, `merge(other)`, `group_by()`, `join(equal(...))`, `join(equal_bi(...))`, `penalize(weight_or_fn)`, `reward(weight_or_fn)`

**`stream::projected::Builder`** — `named()` → `constraint::projected::Uni`

**`stream::projected::Bi<S, Out, K, Src, F, KF, PF, Sc>`** — Self-join stream over projected rows produced by `stream::projected::Stream::join(equal(...))`.
- Operations: `filter()`, `penalize(weight_or_fn)`, `reward(weight_or_fn)`

**`stream::projected::BiBuilder`** — `named()` → `constraint::projected::Bi`

**`stream::projected::DirectedBi<S, Out, K, Src, F, KL, KR, PF, Sc>`** — Directed self-join stream over projected rows produced by `stream::projected::Stream::join(equal_bi(left_key, right_key))`.
- Operations: `filter()`, `penalize(weight_or_fn)`, `reward(weight_or_fn)`

**`stream::projected::DirectedBiBuilder`** — `named()` → `constraint::projected::DirectedBi`

**`RowCoordinate`** — Hidden support coordinate for projected rows:
`{ primary_owner, secondary_owner, emit_index }`. `primary_owner` is always
present; `secondary_owner` is present for joined-pair rows from
`stream::cross::Bi::project(...)`. It is used to keep projected self-join
orientation stable across sparse row-slot reuse and to dedupe callbacks when
both owners localize to the same descriptor/entity update.

Projection syntax:

```rust
struct AssignmentLoadEntries;

impl Projection<Assignment> for AssignmentLoadEntries {
    type Out = LoadEntry;
    const MAX_EMITS: usize = 4;

    fn project<Sink>(&self, assignment: &Assignment, out: &mut Sink)
    where
        Sink: ProjectionSink<Self::Out>,
    {
        assignment.for_each_load_entry(|entry| out.emit(entry));
    }
}

ConstraintFactory::<Plan, HardSoftScore>::new()
    .for_each(Plan::assignments())
    .project(AssignmentLoadEntries)
```

**`stream::projected::Grouped` / `stream::projected::GroupedBuilder`** — Grouped projected rows using stock collectors such as `sum()`, `count()`, `collect_vec()`, `consecutive_runs()`, and `indexed_presence()`. Grouped retained state uses the same `RowOwner` ownership index as ungrouped projected rows. Collector values do not need `Clone`; retained grouped state stores the projected row once by `RowCoordinate` and caches accumulator retraction tokens for exact retracts. Grouped weights use the canonical `penalize(|key, result| ...)` / `reward(|key, result| ...)` shape. `complement()` and `complement_with_key()` continue to `stream::projected::ComplementedGrouped`; `named()` → `constraint::projected::Grouped`.

**`BiConstraintStream<S, A, K, E, KE, F, Sc>`** — Self-join bi stream (macro-generated).
- Operations: `filter()`, `join()` → `TriConstraintStream`, `penalize(weight_or_fn)`, `reward(weight_or_fn)`
- Low-level constructors: `new_self_join()`, `new_self_join_with_filter()`

**`BiConstraintBuilder<S, A, K, E, KE, F, W, Sc>`** — `named()` → `OperatorTerminal` over a `SelfJoinNode` + `FilterNode` tree

**`TriConstraintStream/Builder`** — Same pattern, tri-arity. `join()` →
`QuadConstraintStream`; low-level constructors are `new_self_join()` and
`new_self_join_with_filter()`.

**`QuadConstraintStream/Builder`** — Same pattern, quad-arity. `join()` →
`PentaConstraintStream`; low-level constructors are `new_self_join()` and
`new_self_join_with_filter()`.

**`PentaConstraintStream/Builder`** — Same pattern, penta-arity. Terminal (no
further joins); low-level constructors are `new_self_join()` and
`new_self_join_with_filter()`.

**`stream::cross::Bi<S, A, B, P, EA, EB, F, Sc>`** — Cross-collection bi stream, generic over its compiled first-relationship plan `P` (`BiUnaryPlan` for a keyed join, `BiPredicatePlan` for a predicate join).
- Operations: `filter()`, `group_by(|left, right| key, collector)` → `stream::cross::Grouped`, `project(|left, right| row)` → `stream::projected::Stream`, `penalize(weight_or_fn)`, `reward(weight_or_fn)`, `flatten_last()` → `FlattenedBiConstraintStream`. The group/project/flatten methods are specialized to the keyed plan, which is where a unary key pair exists to rederive.
- Low-level constructors: `new()`, `new_with_filter()` (keyed)

**`stream::cross::Builder`** — `named()` → `constraint::relational::OperatorTerminal` over a `JoinNode` compiled from the stream's unary key pair

**`stream::cross::Grouped/Builder`** — Direct grouped cross-join stream. `penalize(weight_or_fn)`, `reward(weight_or_fn)`, `named()` → `constraint::cross_grouped::Grouped`. `complement(source, key, default)` → `stream::cross::ComplementedGrouped`. Collectors receive the joined pair shape as `(&A, &B)`.

**`stream::cross::ComplementedGrouped/Builder`** — Direct grouped cross-join complement stream. `penalize(weight_or_fn)`, `reward(weight_or_fn)`, `named()` → `constraint::cross_complemented_grouped::ComplementedGrouped`. Complement defaults are produced from the complement entity and weighted by key plus collector result. Complement sources use the same `CollectionExtract::contains(...)` membership contract as joined sources.

**`GroupedConstraintStream<S, A, K, E, Fi, KF, C, V, R, Acc, Sc>`** — Grouped stream.
- Operations: `penalize(weight_or_fn)`, `reward(weight_or_fn)`, `join((collection, key))` → `GroupedJoinedStream`, `complement()`, `complement_with_key()` → `ComplementedConstraintStream`
- Dynamic weighted operations use one canonical key-aware closure shape: `Fn(&K, &R) -> Sc`.
- `join(...)` relates a group (key + aggregate) to a second collection by the group key; the weight receives `Fn(&K, &R, &B) -> Sc` (group key, aggregate, matched entity) and finalizes to an `OperatorTerminal` over a `JoinNode(GroupNode(FilterNode(source)))` tree.

**`GroupedJoinedStream/Builder`** — Group-as-join-input. `penalize`/`reward` take `Fn(&K, &R, &B) -> Sc`; `named()` → `OperatorTerminal` over a `JoinNode` + `GroupNode` + `FilterNode` tree.

**`GroupedConstraintBuilder<S, A, K, E, Fi, KF, C, V, R, Acc, W, Sc>`** — `named()` → `constraint::grouped::Uni` (weighting terminal; group-as-join-input uses `GroupedJoinedStream`)

**`BalanceConstraintStream/Builder`** — Balance stream. `penalize(weight)`, `reward(weight)`, `named()` → `BalanceConstraint`

**`ComplementedConstraintStream/Builder`** — Complemented stream. `penalize(weight_or_fn)`, `reward(weight_or_fn)`, `named()` → `OperatorTerminal` over a `ComplementNode` + `GroupNode` tree. Dynamic weighted operations receive the real or complemented key as `Fn(&K, &R) -> Sc`.

**`stream::projected::ComplementedGrouped/Builder`** — Projected grouped complement stream. `penalize(weight_or_fn)`, `reward(weight_or_fn)`, `named()` → `constraint::projected::ComplementedGrouped`. Complement defaults are produced from the complement entity and weighted by key plus collector result.

**`FlattenedBiConstraintStream/Builder`** — Flattened bi stream. `filter()`, `penalize(weight_or_fn)`, `reward(weight_or_fn)`, `named()` → `FlattenedBiConstraint`. Low-level filters receive the A source index and the B owner index for the flattened C row.

**`ExistsConstraintStream/ExistsConstraintBuilder`** — Existence stream over source-aware direct or flattened collection targets. `penalize(weight_or_fn)`, `reward(weight_or_fn)`, `named()` → `IncrementalExistsConstraint`. There is no separate public indexed existence stream; storage selection is internal to `IncrementalExistsConstraint`.

### Extractor Types

**`CollectionExtract<S>`** — Trait for extracting an entity slice from the solution. All `E`/`EA`/`EB` type params in streams and constraints are bounded by `CollectionExtract<S, Item = A>` rather than raw `Fn(&S) -> &[A]`, allowing both closure forms.
- Associated type: `type Item` — the entity type yielded.
- Method: `fn extract<'s>(&self, s: &'s S) -> &'s [Self::Item]`
- Method: `fn contains(&self, s: &S, item: &Self::Item) -> bool` — source-level membership predicate; plain extractors default to `true`, while `UniConstraintStream` delegates to its accumulated source filter.
- Blanket impl for `Fn(&S) -> &[A] + Send + Sync` — plain slice closures `|s| s.field.as_slice()` work directly.

**`VecExtract<F>`** — Wraps `Fn(&S) -> &Vec<A>` closures so they satisfy `CollectionExtract<S>`. Construct via `vec(f)`.
- Users can write `|s| &s.field` without `.as_slice()`.

**`vec(f)`** — Free function: `fn vec<S, A, F>(f: F) -> VecExtract<F>`. Use when the extractor closure returns `&Vec<A>`:
```rust
factory.for_each(vec(|s: &Schedule| &s.employees))
// or in a join:
.join((vec(|s: &Schedule| &s.employees), equal_bi(...)))
```

**`SourceExtract<E>`** — Hidden descriptor-aware collection extraction wrapper used by macro-generated solution source methods. It satisfies `CollectionExtract<S>` and preserves source metadata for raw keyed joins.

**`ChangeSource`** — Hidden enum describing whether a stream source can localize descriptor-owned incremental callbacks: `Unknown`, `Static`, or `Descriptor(idx)`. `Descriptor(idx)` owns localized events for that descriptor. `Static` never localizes. `Unknown` is non-localized metadata for raw/manual extraction: it is valid for `evaluate()` and `initialize()`, but localized `on_insert(...)` / `on_retract(...)` callbacks panic because the entity index cannot be safely mapped to a source.

**`source(...)`** — Hidden constructor for descriptor-aware collection extraction used by macro-generated solution source methods. Planning entity collections carry `ChangeSource::Descriptor(idx)`; static fact and list-element collections carry `ChangeSource::Static`. This symbol is not part of the facade stream workflow.

**`FlattenExtract<P>`** — Trait for flattening a parent entity into a child slice for existence filtering. Blanket impl for `Fn(&P) -> &[B] + Send + Sync`; `FlattenVecExtract<F>` adapts `Fn(&P) -> &Vec<B>`.

**`ExistenceTarget<S, A, E, F, Sc>`** — Trait for `.if_exists(...)` / `.if_not_exists(...)` dispatch on `UniConstraintStream`.
- Direct target: `(other_stream, equal_bi(left_key, right_key))`
- Flattened target: `(parent_stream, flatten, equal_bi(left_key, flattened_key))`

**`FlattenedCollectionTarget<S, P, B, EP, FP, Flatten, Sc>`** — Intermediate existence target produced by `UniConstraintStream::flattened(flatten)` for nested collection membership checks.

### Join Support Types

**`JoinTarget<S, A, E, F, Sc>`** — Trait for `.join()` dispatch on `UniConstraintStream`.
- Impl groups: `EqualJoiner<KA, KA, K, Symmetric>` (self-join from `equal(...)`), any `CollectionExtract` target with `EqualJoiner<KA, KB, K, Mode>` (keyed cross-join from `equal_bi(...)`, producing `BiUnaryPlan`), comparison joiners (`LessThanJoiner`, `LessThanOrEqualJoiner`, `GreaterThanJoiner`, `GreaterThanOrEqualJoiner`) and `OverlappingJoiner` (each converting an entity-authored condition to a leaf-view plan), `(EB, AndJoiner<J1, J2>)` (composed conditions via `ToViewPlan`), and `(UniConstraintStream<...>, P)` (predicate cross-join with filtered stream target).

**`ToViewPlan<A, B>`** — Converts an entity-authored condition into the leaf-view plan the operator tree executes. Implemented for `EqualJoiner`, the four comparison joiners, `OverlappingJoiner`, and `AndJoiner<J1, J2>` (composing both operands). Used by the conjunction dispatch; top-level equality/comparison/overlap impls build their plans directly so the equality path keeps producing `BiUnaryPlan`.

**`ViewComparisonPlan<K, KA, KB, const LESS: bool, const INCLUSIVE: bool>`** — Ordered comparison over leaf views. `LESS` selects `left < right` (else `>`); `INCLUSIVE` widens to `<=`/`>=`.

**`ViewOverlapPlan<K, SA, EA, SB, EBd>`** — Interval overlap over leaf views.

**`ProjectedJoinTarget<S, Out, Src, F, Sc>`** — Trait for `.join()` dispatch on `stream::projected::Stream`.
- `equal(|row| key)` dispatches to `stream::projected::Bi` and preserves symmetric coordinate-stable pair ordering.
- `equal_bi(left_key, right_key)` dispatches to `stream::projected::DirectedBi` and preserves left/right key orientation for projected rows of the same output type.

**`KeyExtract<S, A, K>`** — Trait for key extraction. Blanket impl for `Fn(&S, &A, usize) -> K + Send + Sync`. Used as the bound on `KE` type params in nary stream/constraint macros.
- Method: `fn extract(&self, s: &S, a: &A, idx: usize) -> K`

**`EntityKeyAdapter<KA>`** — Wraps `KA: Fn(&A) -> K` as a `KeyExtract`. Used in self-join `JoinTarget` impl to adapt entity-only key functions.
- `new(key_fn: KA)` → `EntityKeyAdapter<KA>`

`PhantomKey<T>` is the zero-sized type carrier used in inferred stream return
types when a key parameter must not inherit `Send`, `Sync`, or `Clone` bounds.
It has no public constructor and is not a crate-root modeling symbol.

### Filter Types

**`TrueFilter`** — Always-true filter. Implements all filter traits.

**`FnUniFilter<F>`**, **`FnBiFilter<F>`**, **`FnTriFilter<F>`**, **`FnQuadFilter<F>`**, **`FnPentaFilter<F>`** — Closure wrappers. Joined-arity wrappers are index-aware and receive the same index arguments as their filter traits.

**`AndUniFilter<F1,F2>`**, **`AndBiFilter<F1,F2>`**, **`AndTriFilter<F1,F2>`**, **`AndQuadFilter<F1,F2>`**, **`AndPentaFilter<F1,F2>`** — Conjunctive composition.

**`UniBiFilter<F, A>`** — Adapts UniFilter to BiFilter (tests both args same predicate).

**`UniLeftBiFilter<F, B>`** — Adapts UniFilter to BiFilter (tests left arg only).

**`UniPairFilter<L, R>`** — Internal adapter that composes the left and right stream membership filters for a predicate join, whose relationship predicate is carried by `BiPredicatePlan` (not re-run as a residual).

### Joiner Types

**`EqualJoiner<Fa, Fb, T, Mode>`** — Join by key equality. `Mode` is `Symmetric` for `equal(...)` and `Directed` for `equal_bi(...)`.
- Factory: `equal(key_fn)`, `equal_bi(left, right)`
- Methods: `key_a()`, `key_b()`, `into_keys()`, `key_extractors()`

**`LessThanJoiner<Fa, Fb, T>`**, **`LessThanOrEqualJoiner<Fa, Fb, T>`**, **`GreaterThanJoiner<Fa, Fb, T>`**, **`GreaterThanOrEqualJoiner<Fa, Fb, T>`** — Comparison joiners.
- Factories: `less_than()`, `less_than_or_equal()`, `greater_than()`, `greater_than_or_equal()`

**`FilteringJoiner<F>`** — Arbitrary predicate joiner.
- Factory: `filtering(predicate)`

**`OverlappingJoiner<Fsa, Fea, Fsb, Feb, T>`** — Interval overlap detection.
- Factory: `overlapping(start_a, end_a, start_b, end_b)`

**`AndJoiner<J1, J2>`** — Composed joiner.

**`FnJoiner<F>`** — Raw function joiner.

### Collector Types

**`CountCollector`** / **`CountAccumulator`** — Counts stream matches. Factory: `count()`

**`SumCollector<T, F>`** / **`SumAccumulator<T>`** — Sums mapped values. Factory: `sum(mapper)`

**`LoadBalanceCollector<K, F, M>`** / **`LoadBalanceAccumulator<K>`** / **`LoadBalance<K>`** — Load balance with unfairness metric.
- Factory: `load_balance(key_fn, metric_fn)`
- `LoadBalance<K>` has `loads()` and `unfairness()` methods.

**`RunsCollector<F>`** / **`RunsAccumulator`** / **`Run`** / **`Runs`** — Consecutive unique `i64` point runs with duplicate item accounting.
- Factory: `consecutive_runs(index_fn)`
- `Run` exposes `start()`, `end()`, `point_count()`, and `item_count()`.
- `Runs` exposes `runs()`, `point_count()`, `item_count()`, `len()`, and `is_empty()`.

**`IndexedPresenceCollector<F>`** / **`IndexedPresenceAccumulator`** / **`IndexedPresence`** — Generic ordinal presence with active and complement runs.
- Factory: `indexed_presence(index_fn)`
- `IndexedPresence` exposes `runs()`, `complement_runs(range)`, `contains(index)`, `count()`, `item_count()`, `is_empty()`, `any_in(range)`, and `count_in(range)`.

**`CollectVecCollector<T, F>`** / **`CollectVecAccumulator<T>`** / **`CollectedVec<T>`** — Retains mapped values once and exposes them through an insertion-order iterable view.
- Factory: `collect_vec(mapper)`
- `CollectedVec<T>` exposes `iter() -> CollectedVecIter<'_, T>`, `len()`,
  `is_empty()`, and `to_vec()` when `T: Clone`. `CollectedVecIter` yields
  `&T` in retained insertion order.

## Architectural Notes

### Zero-Erasure Constraint Pipeline

The entire pipeline from stream builder to constraint evaluation is fully monomorphized:
1. `ConstraintFactory::new().for_each(extractor)` — creates `UniConstraintStream`
2. `.filter(predicate)` — composes filter via `AndUniFilter`
3. `.penalize(weight)` — creates `UniConstraintBuilder`
4. `.named("name")` — produces `IncrementalUniConstraint<S, A, E, impl Fn, W, Sc>`

All closures are stored as concrete generic type parameters. No `Box<dyn Fn>`, no `Arc`. The constraint types carry the full closure types through their generics.

### Incremental Scoring Protocol

Score directors use a retract-then-insert protocol:
1. `before_variable_changed()` → constraint `on_retract()` (remove entity's contribution)
2. Modify the solution
3. `after_variable_changed()` → constraint `on_insert()` (add new contribution)

The `ScoreDirector` delegates to `ConstraintSet::on_retract_all()` / `on_insert_all()`.

### ConstraintSet Tuple Implementation

`ConstraintSet` is implemented for every singleton `IncrementalConstraint<S, Sc>` and for tuples of up to 32 nested `ConstraintSet` elements via a macro. Operations iterate over all nested sets, summing scores and flattening per-constraint raw metadata/results without erasure; the public metadata view is deduplicated after ordering. This is the zero-erasure alternative to `Vec<Box<dyn Constraint>>`.

### Self-Join Operator

Same-collection n-ary streams build a `SelfJoinNode<S, A, O, K, KE, N>` over
their collection source. It buckets input rows by key, emits every increasing
handle tuple of arity `N` per bucket (unique ordered combinations, so a triple
is `i < j < k` for one key), and maintains exact insert/retract deltas over a
retained combo set. The view is `[Leaf<'a, A>; N]` in canonical index order.
The authored filter runs in a `FilterNode` on top, and the terminal is the
shared `OperatorTerminal`. No per-arity incremental engine remains.

### Stream Arity Macros

`impl_bi_arity_stream!`, `impl_tri_arity_stream!`, `impl_quad_arity_stream!`, `impl_penta_arity_stream!` generate the stream and builder structs for each arity level. All four macros live under `arity_stream_macros/nary_stream/`. They share the same field layout and method pattern but differ in the number of entity arguments to filter/weight functions. Each passes an explicit `arity` literal and a canonical position table used to unpack the `[Leaf; N]` self-join view.

### PhantomData Pattern

All types use `PhantomData<(fn() -> S, fn() -> A, ...)>` to avoid inheriting bounds from phantom type parameters.

## Cross-Crate Dependencies

- **From `solverforge-core`:** `Score`, `PlanningSolution`, `ConstraintRef`, `ImpactType`, `SolutionDescriptor`, `EntityDescriptor`, `ProblemFactDescriptor`, `VariableDescriptor`, `EntityCollectionExtractor`