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
use crate;
use crateComposition;
use crateProcessPrecedent;
use crateProviderError;
use crateLiteratureRouteEvidence;
use crate;
use cratePriorExperimentEvidence;
use crateRouteFamily;
use crate;
use crateSuitabilityFinding;
use crate;
/// Source of candidate precursor compounds for a target (AGENTS.md §8).
/// Core ships in-memory/JSON/fixture implementations only — no network
/// access lives in this crate (AGENTS.md §8, §25).
/// One source in a multi-source candidate-generation ensemble (Phase 30,
/// PR 1: `catalog-exact` and `frequency-prior`; later PRs add
/// `thermodynamic`, `prior-experiment`, and `literature-analog`).
/// Deliberately narrower a contract than [`PrecursorCatalog`]:
/// `PrecursorCatalog` answers "what's in the catalog for this target,"
/// unranked; `CandidateGenerator` answers "what does *this specific
/// signal* propose, in its own preferred order" -- [`GeneratedCandidate::rank`]
/// carries that order as a plain ordinal, never a score, so a generator's
/// internal priority can never be read as a success probability
/// (mirrors `SearchPriority`'s own score/priority separation,
/// `src/precursor.rs`). `CandidateGeneratorEnsemble`
/// (`src/candidate_generator.rs`) is the only place multiple generators'
/// outputs are combined, and it does so by implementing
/// [`PrecursorCatalog`] itself -- so an ensemble is a drop-in
/// `Planner::builder` catalog argument, requiring no change to `Planner`/
/// `PlannerBuilder` at all.
/// Source of reaction-energy estimates (AGENTS.md §8). Returning `Ok(None)`
/// means "no data available," which must not by itself reject a plan
/// (AGENTS.md §13, §14 — see `RejectionCode::ThermodynamicDataUnavailable`).
/// Source of process-condition precedent for a target/precursor combination
/// (AGENTS.md §8).
/// Source of route-suitability findings for a target/route-family
/// combination (Phase 15A; AGENTS.md §8 sketches providers as a floor, not
/// an exhaustive list -- Phase 13's `competing_phases` already extended
/// this pattern once). Deliberately narrower than `ProcessEvidenceProvider`:
/// suitability depends only on the target and route family, not on a
/// specific precursor set, since a route family's fitness for a material
/// (e.g. whether high-temperature firing is even reachable before the
/// target decomposes) doesn't change with which precursors were chosen to
/// reach it.
/// Source of reference-only, cross-DOI literature evidence for an exact
/// target/precursor-set/route-family combination (v0.4.0 Integration).
/// Deliberately narrower in spirit than [`ProcessEvidenceProvider`]: this
/// trait's output ([`LiteratureRouteEvidence`]) is never applied to a
/// `ProcessStep`, never converted to a `ConditionPrecedent`, and never
/// passed to `score_plan` -- `Planner` attaches it to `SynthesisPlan` as
/// its own field, structurally isolated from every scoring input. Not
/// gated behind the `literature_corpus` feature: this trait and
/// [`LiteratureRouteEvidence`] are always compiled, so `Planner`'s public
/// API and report schema never change shape depending on which crate
/// features are enabled. The one real implementation
/// (`LiteratureObservationCorpusProvider`, backed by
/// `LiteratureObservationCorpus::cross_doi_comparisons`) lives behind
/// that feature -- same split `ThermodynamicProvider` already has against
/// `MaterialsProjectSnapshotProvider`.
/// Source of reference-only prior-experiment evidence for an exact
/// target/precursor-set/route-family combination (Phase 26). Mirrors
/// [`LiteratureEvidenceProvider`]'s own exact-match contract exactly:
/// this trait's output ([`PriorExperimentEvidence`]) is never applied to
/// a `ProcessStep`, never converted to a `ConditionPrecedent`, and never
/// passed to `score_plan` -- `Planner` attaches it to `SynthesisPlan` as
/// its own field, structurally isolated from every scoring input. Not
/// gated behind any Cargo feature: unlike `LiteratureEvidenceProvider`
/// (whose one real implementation needs the `literature_corpus`
/// feature's corpus loader), [`crate::execution_record::SynthesisExecutionRecord`]
/// itself carries no feature gate, so neither does the one real
/// implementation of this trait
/// ([`crate::prior_experiment_evidence::InMemoryExecutionRecordProvider`]).
/// `Ok(Some(evidence))` implies `evidence.records` is non-empty -- an
/// empty match is `Ok(None)`, never a rendered "0 prior experiments"
/// disclosure. Unlike `LiteratureEvidenceProvider`, `Planner` does not
/// restrict which route families this provider is asked about (see
/// `docs/prior_experiment_evidence.md` for why that restriction doesn't
/// apply here).