KiThe 0.3.6

A numerical suite for chemical kinetics and thermodynamics, combustion, heat and mass transfer,chemical engeneering. Work in progress. Advices and contributions will be appreciated
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
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
# Thermodynamics DB and Aggregator Audit

## Scope

This checklist covers the thermodynamic/transport database layer and the
`SubsData` property aggregator:

- `DBhandlers.rs` and `DBhandlers/**`
- `thermo_lib_api.rs` and `prelude.rs`
- `User_substances.rs`, `User_substances2.rs`, and
  `User_substances_presets.rs`
- `User_substances_error.rs` and the corresponding tests
- `dG_dS.rs` only where it consumes or mutates `SubsData` caches
- `library_manager2.rs` only where it implements the internal thermodynamics
  write/drop workflow tracked by P2.5

The following areas were deliberately out of scope for the original database
and aggregator audit:

- `ChemEquilibrium/**` and both chemical-equilibrium calculation branches
- phase/solution thermodynamic modelling, which is now covered by the dedicated
  audit and execution plan at the end of this document

## Current Data Flow

1. `ThermoRepository` loads and validates local JSON libraries once per
   explicit repository lifecycle; `ThermoData` is a lightweight query handle
   over the shared immutable catalogs.
2. `SubsData` accepts substance names, library preferences, explicit lookup
   instructions, temperature, pressure, and units.
3. `search_substances()` resolves raw records deterministically into canonical
   per-property `SubstanceSearchState` values with provenance and calculators.
4. Calculation methods publish numeric values, closures, symbolic expressions,
   composition, molar mass, diffusion data, and derived-property caches behind
   controlled mutation and read-only readiness accessors.

The original public-mutation, non-deterministic lookup, partial-publication,
and repeated-library-loading problems are resolved. Remaining closeout work is
concentrated in legacy Gibbs/entropy consumers, NIST/CEA fitting failure paths,
property-report consistency, and deterministic offline test coverage.

## Priority Definitions

- **P0**: may panic on user/library data, return physically invalid values,
  silently lose failures, or select the wrong database record.
- **P1**: architectural changes required to make state, errors, I/O, and lookup
  behaviour explicit and reliably maintainable.
- **P2**: performance, API clarity, deterministic output, and separation of
  computation from presentation.
- **P3**: long-term test infrastructure, documentation, benchmarks, and module
  cleanup.

# P0: Correctness and Failure Containment

P0 must be completed before changing the public API or optimizing the hot read
paths. Every item requires regression tests that fail against the old
behaviour.

## P0.1 Remove panics from library and record parsing

- [x] Introduce a typed `ThermoLibraryError` (or equivalent) covering file
  open/read failures, JSON decoding, malformed records, unsupported libraries,
  and schema validation.
  - Replace the infallible `ThermoData::new()` I/O path with `try_new()` or an
    injected repository constructor.
  - Do not silently replace a failed elements-library load with an empty map.
  - Keep a convenience constructor only if its failure policy is explicit.
- [x] Make NASA composition deserialization fully fallible.
  - Remove `unwrap()` calls from `NASAdata::deserialize_composition`.
  - Reject missing element names, missing coefficients, malformed numbers, and
    duplicate malformed entries through `serde::de::Error::custom`.
- [x] Validate the complete NIST interval schema before indexing it.
  - Cover `NISTdata::extract_coefficients`, `NistInput::extract_coefficients`,
    `NistInput::pretty_print`, and `NISTdata::get_coefficients`.
  - Validate required `T` and `Cp` fields, equal interval counts, two bounds per
    interval, minimum coefficient count, ordered finite bounds, and finite
    coefficients.
- [x] Replace user-facing panics in calculator factories.
  - `thermo_api::create_thermal_by_name`
  - `transport_api::create_transport_by_name`
  - `SubsData::what_handler_to_use`
  - Return an `UnsupportedLibrary` error containing the requested name.
- [x] Remove panic paths from `ThermoData::remove_by_name()` and
  `ThermoData::create_substance_document()`.
  - Missing substances and line-read errors must be typed results.

Required tests:

- [x] Corrupt and missing library files return typed errors without panicking.
- [x] Malformed NASA composition strings return serde errors.
- [x] Missing, uneven, short, unordered, and non-finite NIST intervals are
  rejected before calculator construction.
- [x] Unknown calculator/library names return `UnsupportedLibrary`.
- [x] Missing remove targets and malformed document input remain recoverable.

Completion criterion: no production parsing, library-loading, or calculator
factory path in scope contains `panic!`, `assert!`, `unwrap()`, or `expect()`.

## P0.2 Enforce finite physical inputs and outputs

- [x] Add shared validation helpers for finite values, finite positive values,
  ranges, and compatible vector lengths.
  - Transport density/temperature/pressure/molar-mass helpers now share a
    local finite-value validation layer, and transport prerequisites validate
    collision diameter plus the other scalar inputs before derived formulas run.
- [x] Transport scalar inputs reject `NaN`, infinities, and non-positive
  values before derived calculations.
- [x] Pair diffusion calculations reject non-finite temperature, pressure, and
  mass state before numerical evaluation.
- [x] Apply finite-positive validation to temperature, pressure, density, molar
  mass, collision diameter, heat capacity, viscosity inputs, and every divisor
  used by transport/diffusion formulas.
- [x] Reject `NaN`, positive/negative infinity, zero where physically invalid,
  and negative values before derived calculations.
- [x] Validate calculated scalar outputs and report `NonFiniteOutput` with the
  property and substance name instead of caching `NaN`/`Inf`.
- [x] Make `PairDiffusion` and `MultiSubstanceDiffusion` construction and
  calculation fallible.
  - Validate `T`, `P`, `M`, `sigma`, and `e_k`.
  - Validate consistency between `substance_names` and the substance map.
  - Constructor-level rejection now happens for invalid thermodynamic state;
    derived coefficients are still checked again before use.
- [x] Align TRANSPORT trait and inherent APIs.
  - The density fallback is now centralized and consistent across numeric,
    closure, and symbolic transport paths.
  - `ideal_gas()` and `ideal_gas_sym()` now validate their thermodynamic state
    before publishing density results.

Required tests:

- [x] Table-driven `NaN`, `+Inf`, `-Inf`, zero, and negative input tests for
  thermo, transport, and diffusion APIs.
- [x] Table-driven finite-input validation covers transport scalar helpers.
- [x] Pair diffusion rejects non-finite state before computation.
- [x] No public calculation path returns or caches a non-finite value as a
  successful result.
- [x] Numeric, closure, and symbolic transport paths apply the same prerequisite
  and density rules.
- [x] Diffusion rejects inconsistent maps and invalid pair parameters without
  indexing panics.

Completion criterion: successful numeric results are finite by contract, and
invalid physical inputs cannot enter derived caches.

## P0.3 Make database lookup deterministic and property-specific

- [x] Replace `HashMap` iteration as the effective library-priority order in
  `SubsData::search_substances()` with an ordered lookup plan.
  - Priority, permitted, and element-based lookup now traverse libraries in a
    stable sorted order instead of relying on hash iteration order.
- [x] Track thermo and transport lookup independently.
  - Finding thermodynamic data must not suppress permitted transport fallback.
  - Finding transport data must not suppress permitted thermodynamic fallback.
- [x] Stop overwriting successful same-priority results according to randomized
  map iteration order.
- [x] Preserve explicit lookup provenance as `LibraryPriority::Explicit` rather
  than converting it to ordinary priority lookup.
- [x] Ensure a failed explicit lookup updates only the requested property and
  does not erase an independently found property.
- [x] Apply the same deterministic rule to element-library lookup in
  `populate_element_search_results()`.
- [x] Replace production `assert!`/`unwrap()` in permitted-library traversal
  with typed internal-invariant errors.

Required tests:

- [x] Repeated searches produce identical sources and results across runs.
- [x] Thermo-found/transport-fallback and transport-found/thermo-fallback
  scenarios both work.
- [x] Two matching libraries at the same priority resolve by documented order.
- [x] Explicit lookup success/failure preserves the other property category.
- [x] Element lookup has deterministic provenance.

Completion criterion: lookup results depend only on the declared ordered
policy, never on hash iteration or the success of an unrelated property search.

## P0.4 Stop reporting partial failure as unconditional success

- [x] Replace batch methods that log per-substance errors and then return
  `Ok(())` with a fail-fast policy that only publishes a rebuilt cache after
  the full loop succeeds.
- [x] Apply the policy consistently to numeric, closure, and symbolic thermo
  maps in `User_substances.rs`.
- [x] Apply the same policy to transport maps in `User_substances2.rs`.
- [x] Apply it to NIST fallback/network lookup: an all-failed request must not
  be reported as success.
- [x] Keep the error logger as presentation/diagnostic support, not as a
  replacement for the return contract.

Required tests:

- [x] One success plus one failure returns the first error and leaves the old
  cache untouched.
- [x] All-failed batches cannot return an empty successful result.
- [x] Fail-fast mode publishes no partially rebuilt cache.
- [ ] Best-effort mode identifies every failed substance and property.

Completion criterion: callers can distinguish success from failure without
inspecting logs, and a failed batch never replaces a previously valid cache
with a partially rebuilt one.

## P0.5 Invalidate stale aggregator state on input mutation

- [x] Define the dependency graph for substances, lookup instructions,
  temperature, pressure, units, phases, and derived caches.
  - The invalidation helpers now document the cache dependency graph directly
    in `User_substances.rs`, so the policy lives next to the implementation.
- [x] Make `set_substances()` invalidate search results and every dependent
  thermo, transport, composition, molar-mass, diffusion, and derived cache.
- [x] Add controlled setters for all other inputs and apply the same targeted
  invalidation policy.
  - `set_library_priority`, `set_multiple_library_priorities`,
    `set_explicis_searh_instructions`, `set_T`, `set_P`, and `set_M` now
    invalidate the caches that depend on them.
- [x] Prevent public field mutation from bypassing invalidation (completed
  structurally in P1.2).
  - `SubsData` mutation-critical fields are now crate-private and can only be
    updated through controlled setters inside the module.
- [x] Build replacement maps locally and publish them only after the selected
  batch policy completes.

Required tests:

- [x] Mutation-path invariant test covering substances, T, P, units, library
  policy, and explicit instructions.
- [x] No cache can retain entries for removed substances.
- [x] Failed recomputation does not mix old and new values.

Completion criterion: after every supported mutation, all readable caches are
either valid for the current inputs or explicitly absent.

## P0.6 Remove residual production panic paths

- [x] Refactor public calculation paths in `dG_dS.rs` to consume typed
  readiness accessors and return `SubsDataResult` instead of unwrapping phase,
  numeric, symbolic, and closure caches.
  - Gibbs/entropy/Helmholtz read paths now degrade to empty maps for incomplete
    calculator state and are covered by a regression test; the full typed error
    propagation still remains to be finished. The error boundary around
    aggregate calculation failures now preserves nested sources for the main
    thermo/NIST paths, so the remaining work is narrower than before.
  - Gas-correction calculations no longer index concentration vectors directly;
    short concentration vectors now degrade safely with a warning instead of a
    panic.
  - The `PhaseOrSolution` and `OnePhase` facades now propagate typed
    calculation failures instead of silently discarding them.
- [x] Replace `thermo.get_composition().unwrap()` in
  `SubsData::calculate_elem_composition_and_molar_mass_local()` with propagated
  calculator context and preserve the fallback formula parser only for a valid
  `Ok(None)` result.
  - The composition/molar-mass path now uses a dedicated helper that prefers
    calculator-provided composition, falls back to formula parsing only when
    needed, and leaves no partial state behind on failure.
- [x] Make NIST LM fitting treat missing/non-finite solver results and poor
  termination as `NISTError::FittingError`; remove production `assert!` and
  `unwrap()` from `fitting_adjacent_weighted()` and `fitting_non_adjacent()`.
  The LM branches now validate published `R^2`, coefficient count, and
  finiteness before any coefficients are published. A dedicated regression test
  covers the new typed error gate, and the empty-input/empty-interval
  guardrails remain in place for the local fitting helpers.
- [x] Make CEA adjacent fitting validate symbolic expressions, the complete LM
  coefficient map, and `R²` before publishing `coeff_Visc`/`coeff_Lambda`.
  The adjacent fit now works on temporary clones and only publishes the
  fitted coefficients after all validation gates pass.
- [ ] Add regression tests proving each path returns a typed error and leaves
  the previously published state unchanged when its prerequisite or solver
  result is absent. The NIST empty-vector and empty-selection regressions are
  now covered, and the adjacent/non-adjacent NIST fit failure paths now verify
  that previously published coefficients survive an error return. The remaining
  work is the broader typed-error sweep.

Completion criterion: the scoped production paths contain no panic triggered
by user, library, calculator, or optimizer state.

# P1: Canonical State, Repository, and Error Architecture

## P1.1 Introduce a shared immutable library repository

- [x] Separate library loading from `ThermoData` query operations.
- [x] Represent loaded catalogs with an immutable repository that can be shared
  through `Arc` or an explicit application-owned handle.
- [x] Avoid opening and deserializing the same JSON files for every
  `SubsData::new()`.
- [x] Preserve an explicit reload path for development/custom-library workflows;
  do not hide mutable reload behaviour behind a process-global cache.
- [x] Inject repository paths/data in tests instead of relying on the working
  directory and installed local files.
- [x] Store library capabilities and aliases in one registry instead of
  inferring the handler from the first JSON record (for example, by looking for
  a `Cp` field).

Required tests:

- [x] Two aggregators share one immutable repository without sharing mutable
  calculation state.
- [x] Custom temporary repositories load independently.
- [x] Reload is explicit and old handles remain internally consistent.
- [x] Empty libraries and mixed/invalid schemas return typed errors.

## P1.2 Give `SubsData` one controlled state boundary

- [x] Make assignment and derived fields private where external mutation can
  break invariants.
- [x] Split the model conceptually into:
  - user query/configuration,
  - immutable catalog handle,
  - per-property lookup results with provenance,
  - numeric/symbolic/closure derived caches,
  - diagnostics.
  - The canonical typed boundary is now in place: `search_states` carries the lookup contract, the derived caches have explicit readiness states, and the public API is read-only by default.
  - Several long-form tests now query `SubstanceSearchState` and derived readiness helpers directly instead of reading compatibility snapshots first.
  - Remaining work is narrower now: keep shrinking compatibility reads and remove the last direct legacy-map dependencies from internal helpers and tests.
- [x] Replace `HashMap<WhatIsFound, Option<SearchResult>>` with a typed
  per-property state such as `NotSearched`, `Found`, `Missing`, or `Failed`.
  - `search_states` has been introduced as the canonical layer; the legacy
    compatibility view still remains for now.
  - The main lookup queries now read from `search_states`, so `search_results`
    is increasingly treated as a compatibility/export view instead of the
    authoritative state.
  - The element-search path and typed lookup summary helpers now also read
    from `search_states`, so fewer code paths depend on the legacy map.
  - `get_substance_result()` and `get_all_results()` now reconstruct their
    compatibility snapshots from `search_states` instead of returning the
    stored legacy map directly.
  - Calculator access helpers now resolve mutable `SearchResult` instances from
    `search_states`, so lookup operations no longer depend on the legacy map.
- [x] Decide how canonical calculator data is represented inside `SubsData`.
  - `search_states` is the authoritative lookup layer.
  - `search_results` remains a compatibility/export snapshot only.
  - `SearchResult` now behaves as a read-only payload: provenance JSON and the
    attached calculator live together inside the canonical found record instead
    of as parallel public mutable fields.
- [x] Expose read-only getters and narrow mutation methods that centralize
  invalidation.
  - Public read access now goes through typed getters on `SearchResult`,
    `SubstanceSearchState`, and `SubsData` instead of direct field mutation.
  - The read-only surface now covers config, repository, lookup state, and the
    major derived caches.
- [x] Represent "not calculated" explicitly instead of initializing values and
  closures to valid-looking zero/identity placeholders.
  - The lookup layer now exposes explicit pending/resolved/missing/failed
    helpers, and the numeric caches now have explicit `NotCalculated`/`Ready`
    read-only accessors instead of forcing callers to infer readiness from map
    shape.
  - Transport calculation no longer routes dummy CEA placeholder values through
    the solver; the branch selection is now explicit.
  - The first production consumer has already switched to the explicit
    readiness accessor, so the migration is now real rather than purely
    theoretical.
  - Gibbs/entropy consumers now also read `dH` and `dS` through the explicit
    readiness state instead of reaching into the nested cache directly.
  - Closure caches now also have typed readiness accessors, so tests and
    consumers can inspect function availability without pattern matching on the
    raw nested map shape.
  - Thermo and transport regression tests now assert readiness explicitly
    instead of only peeking into nested `Option` maps.
  - Temperature invalidation now has a dedicated regression test that proves
    numeric and transport caches return to `NotCalculated` while reusable thermo
    closures remain explicitly `Ready`.

Required tests:

- [ ] Compile-time/API tests demonstrate that callers cannot mutate caches
  directly.
- [ ] State-transition tests cover new, searched, partially found, calculated,
  invalidated, and failed states.
- [ ] Search provenance survives calculation and export.

## P1.3 Make clone semantics truthful and cheap

- [x] Audit `SubsData::clone()` and remove silent cache reconstruction.
  - The current implementation cannot clone boxed closures faithfully.
  - It silently ignores closure recreation failures.
  - Transport closure state can be lost or rebuilt into the wrong map.
- [x] Prefer `Arc<dyn Fn... + Send + Sync>` for shareable compiled closures.
  - CEA transport, transport property, and pair-diffusion closures now clone by
    sharing the compiled callable instead of recreating a placeholder.
- [x] Apply the same rule to NASA/NIST/CEA/TRANSPORT calculators and diffusion
  objects; a clone must not replace a working closure with a zero placeholder.
  - NASA/NIST clones still rebuild from coefficients, while the transport and
    diffusion calculators now preserve active callable state directly.
- [x] If semantic cloning remains impossible, remove `Clone` and provide an
  explicit fallible rebuild operation.
  - The remaining scoped calculators can now clone truthfully, so the fallback
    path is not needed here.

Required tests:

- [x] Original and clone produce equivalent numeric and closure results.
- [x] Clone does not perform file I/O or silently recalculate state.
- [x] A clone failure is explicit rather than swallowed.

## P1.4 Consolidate typed errors without losing source context

- [ ] Define error boundaries for repository I/O/schema, lookup, calculator
  construction, physical validation, network access, and batch aggregation.
- [x] Preserve `source()` chains instead of converting network/serde failures
  into generic `NoCoefficientsFound` values.
  - Thermo, NIST, and transport wrappers now expose the underlying source
    chain instead of flattening it into an ambiguous lookup-style outcome.
- [x] Preserve those concrete sources through the aggregator boundary.
  `SubsDataError::NistRetrievalFailed` and `SubsDataError::CalculationFailed`
  now retain an optional nested source so callers can inspect the original
  calculator or NIST error when one is available.
- [x] Remove duplicated error-message mapping between format errors,
  `ThermoError`/`TransportError`, `SubsDataError`, and `LogErrorType` where a
  structured source can be retained.
  - The owned and borrowed conversions for thermo, transport, and `SubsDataError`
    now share local helpers instead of carrying duplicated `match` blocks.
- [x] Fix the most misleading user-facing message paths, especially NIST
  retrieval failures that were reported as missing coefficients.
- [x] Replace `Box<dyn Error>` in public file APIs with stable typed errors.
  - No in-scope `DBhandlers`/`User_substances` public file API currently uses
    `Box<dyn Error>`; the remaining occurrences are in `ChemEquilibrium`, which
    was intentionally excluded from this thermodynamics audit.

Required tests:

- [x] Error variants retain path, library, substance, property, and underlying
  source where applicable.
- [x] Display strings are format-specific and actionable.
- [x] Error conversion does not turn infrastructure failure into data absence.
- [x] Aggregator-level NIST fallback and calculation failures expose their
  original typed error through `source()` instead of only formatted text.
  Constructor-level coverage is in place; add one end-to-end regression for the
  full fallback path before closing this fully.

## P1.5 Harden the NIST network boundary

  - [x] Inject/configure the HTTP client with finite connect/request timeouts and
    a stable user agent.
  - [x] Define retry behaviour explicitly; do not retry schema/4xx failures.
  - [x] Bound accepted response sizes and validate content before parsing.
- [x] Separate offline deterministic tests from live NIST integration tests.
    Parser integration tests are isolated, and the live NIST suites are now
    marked `#[ignore]` instead of running by default.
  - [x] Return a batch report for multi-substance fetching and fail truthfully if
    every request fails.
  - [x] Remove direct URL/data printing from the network and parser core.

Required tests:

- [x] Mocked timeout, HTTP error, malformed HTML, missing coefficients, partial
  success, and complete failure.
- [x] Ordinary CI does not require network access.
- [x] Optional live tests are explicitly ignored.

# P2: Performance, API, and Maintainability

## P2.1 Remove repeated large clones and redundant scans

- [ ] Profile before changing algorithms and record representative catalog sizes.
- [x] Pass borrowed records through lookup instead of cloning full JSON values,
  search-result maps, and allowed-library vectors.
- [x] Replace explicit-instruction membership scans with a set-based lookup in
  `search_substances()`.
- [x] Make `ThermoData::search_libs()` return matching entries rather than
  allocating empty-string placeholders.
- [x] Rework `search_libs_for_subs()` to avoid cloning whole maps and duplicate
  index vectors.
- [x] Use sets for library comparison instead of repeated `Vec::contains()`
  scans.
- [x] Iterate directly over unique diffusion pairs rather than entering all
  `n * n` combinations and skipping half.
- [x] Capture immutable transport parameters once in closures instead of cloning
  complete parameter structs on each evaluation.
- [ ] Keep symbolic expressions and closures shared where their ownership model
  permits it.

Required measurements/tests:

- [ ] Criterion benchmarks for cold/warm repository load, 100/1000-substance
  lookup, numeric/closure/symbolic map construction, and diffusion-pair setup.
- [ ] Allocation counts or profiler evidence accompany clone-removal changes.
- [ ] Behavioural equivalence tests protect every optimized path.

## P2.2 Make computation pure by default

- [ ] Remove unconditional `println!`, `print!`, and `Table::printstd()` calls
  from search, parsing, calculation, and network methods.
  Remaining core output includes NASA/NIST fitting quality tables, CEA fitting
  coefficients/`R²`, and a Gibbs calculation diagnostic in `dG_dS.rs`.
- [x] Return structured reports/tables/data from core methods.
- [x] Keep explicit `print_*` adapters or caller-owned logging for interactive
  workflows.
- [x] Ensure pretty-print methods never mutate or rebuild calculation state.

Required tests:

- [x] Pure report methods return deterministic data suitable for CLI, GUI, and
  library callers.
- [x] Printing adapters format the same report without changing state.

## P2.3 Normalize the public API and naming

- [x] Introduce typed library identifiers/capabilities and centralize aliases;
  remove divergent string matching and the `Aramco_transpot` typo.
- [x] Separate property identity from representation.
  - Prefer `PropertyKind::Cp` plus numeric/closure/symbolic accessors over
    variants such as `Cp`, `Cp_fun`, and `Cp_sym` in one enum.
- [x] Return `Option<&T>` rather than `Option<&Box<T>>`.
- [x] Accept `AsRef<Path>` for file APIs and typed units internally.
- [x] Correct API typos such as `hasmap`, `explicit_search_insructions`, and
  `set_explicis_searh_instructions`, with temporary deprecation aliases only if
  downstream compatibility requires them.
- [x] Preserve conventional scientific notation (`T`, `P`, `Cp`, `Lambda`)
  where it improves domain readability; naming cleanup must not erase accepted
  scientific symbols.
- [x] Define a narrow facade for common workflows: configure query, resolve
  records, calculate requested properties, inspect a typed report.

Required tests:

- [x] Compile-oriented API examples cover the primary workflows.
- [x] Deprecated aliases, if retained, forward exactly to canonical methods.

## P2.4 Validate fitting and representation equivalence

- [ ] Validate fitting input lengths, finiteness, interval ordering, rank, and
  solver output before publishing fitted coefficients.
  NASA now follows this contract; NIST and CEA LM branches still need equivalent
  typed solver-result validation.
- [x] Test continuity and boundary ownership between adjacent temperature
  intervals.
- [x] Decide whether adjacent interval fitting is allowed to drift to a
  different coefficient vector when both intervals start from the same
  coefficients, or whether exact coefficient preservation is part of the
  contract. Identical adjacent NASA-7 vectors are now an idempotent case:
  all adjacent fitting paths preserve the vector exactly and only calculate
  quality diagnostics. For genuinely different ranges, coefficient drift is
  allowed and the contract is expressed in property-space fitting errors.
- [x] Strengthen NASA adjacent LM fitting and regression coverage.
  Sequential LM and stacked simultaneous LM are checked against real NASA
  records with independent `Cp`, `dH`, and `dS` error bounds. Stacked residual
  blocks make the simultaneous LM formulation more direct and idiomatic; the
  pre-existing property-wise reports remain the final acceptance check.
  Unsuccessful RustedSciThe fits become typed errors.
  Fitting acceptance is based on `reports_for_coefficients` comparing raw
  property samples with the published coefficient vector; finite coefficients
  or a good aggregate LM `R²` are necessary diagnostics, not quality proof.
- [x] Define one shared property-space report contract for NASA and NIST.
  NASA and NIST now use the same shared helper for property-wise fitting
  diagnostics and finite/length validation. CEA still needs the same treatment
  before the entire thermodynamic fitting surface is fully unified.
  - Reject mismatched/empty sample vectors without `assert_eq!`.
  - Use one documented relative-error formula, including a near-zero reference
    policy, for maximum error and significant-point classification.
  - Make the documented significant residual threshold match the implementation.
  - Correct the NASA `diff / y^2` significant-residual calculation and clarify
    whether `l2_norm` is absolute, relative, or RMS.
- [ ] Route NIST and CEA fitted outputs through the same property-wise
  acceptance principle already used by NASA before publishing coefficients.
  - NIST now has a shared `validate_property_reports` helper plus regression
    tests for the acceptance boundary, but the helper is still not wired as a
    hard publish gate because several legacy fitting fixtures intentionally
    expect the older permissive behavior.
  - CEA still needs the same property-wise report layer.
- [ ] Cross-check numeric, closure, and symbolic implementations over the same
  valid domain for NASA, NIST, CEA, TRANSPORT, and diffusion formulas.
- [ ] Define tolerances from the formulas/data precision rather than weakening
  assertions after failures.

## P2.5 Scenario presets and substance write workflows

- [x] Add a thermodynamics `prelude` module for the core read-only workflow
  imports that preset code and application code reuse most often.
- [x] Add typed presets over `SubsData` for the common application scenarios:
  - `StirredTank` should request only thermochemistry (`Cp`, `dH`, `dS`, `M`).
  - `PlugFlowGas` should request thermochemistry plus diffusion and thermal
    conductivity.
  - `PlugFlowGas` variants that do not need diffusion should request
    thermochemistry plus thermal conductivity only.
  - `PlugFlowCond` should request thermochemistry plus thermal conductivity
    without diffusion or viscosity.
  - Each preset should also carry library preference, temperature interval, and
    any other required lookup policy in one structured object.
  - The first typed preset scaffold now lives in `User_substances_presets.rs`
    and reuses the existing `SubsData` setters plus the read-only prelude.
  - The preset layer now has a typed `ThermoLookupPolicy` and
    `ThermoOutputPolicy`, plus preview/summary helpers for GUI and examples.
- [x] Define the contract for fitting drift between adjacent ranges before more
  fixtures are rewritten around the current implementation. Exact preservation
  applies to identical input vectors; approximation of different vectors is
  validated by `Cp`, `dH`, and `dS` errors rather than coefficient proximity.
- [x] Design a typed JSON write workflow for new substances.
  - Write the substance in the correct format-specific library representation
    (`NASA`, `NIST`, `Aramco`, etc.).
  - Update the address library that stores library-substance pairs.
  - Update the element-composition library.
  - [x] Append a machine-readable journal entry that records which canonical
    names were written and to which files.
  - [x] Add an internal crate-only upsert/drop path that updates one substance
    in one library and syncs the derived address and element catalogs.
- [x] Add roundtrip tests for the write path and for the preset constructors.
- [x] Complete the thermodynamics prelude for preset-oriented application code
  by re-exporting `ThermoLookupPolicy`, `ThermoOutputPolicy`,
  `ThermoPresetPreviewReport`, and `ThermoRequestedData`.

# P3: Test Infrastructure, Documentation, and Cleanup

## P3.1 Build a deterministic test matrix

- [ ] Add property-based/fuzz tests for malformed JSON, NASA composition text,
  NIST interval shapes, units, and finite-domain inputs.
- [x] Add repository story tests using temporary fixture directories.
- [x] Add an end-to-end report regression covering explicit lookup and
  priority fallback.
- [ ] Add end-to-end aggregator tests for explicit lookup, priority fallback,
  mixed thermo/transport sources, invalidation, partial batches, and export.
- [ ] Replace tests that only print values or discard errors with assertions on
  contracts and invariants.
- [x] Keep live network tests outside the default test suite.
  Replace live-data fitting fixtures in `NISTdata_tests.rs` and
  `NISTdata_fitting.rs` with checked-in/local JSON records; retain only a small
  explicitly ignored live smoke-test group.

## P3.2 Document supported contracts and units

- [x] Document every supported library format, required fields, unit system,
  valid temperature range, and transport prerequisites.
- [x] Add examples for local-library lookup, explicit mixed-source lookup,
  numeric values, closures, symbolic expressions, and diffusion coefficients.
- [x] Document best-effort versus fail-fast batch semantics.
- [x] Document repository loading/reload and offline NIST behaviour.

## P3.3 Remove dead or misleading module surface

- [x] Either implement real facades in `thermo_properties_api.rs` and
  `transport_properties_api.rs` or remove the empty public modules.
- [x] Remove `User_substances3.rs` if it remains an empty implementation block.
- [x] Remove unused imports and stale commented code from the thermo surface.
- [x] Remove duplicated aliases from the thermodynamics surface.
- [x] Remove misleading zero/default calculator state.
- [x] Move filenames and module names toward Rust conventions where this can be
  done without obscuring domain notation.
  - Conventional snake_case module aliases are now available for new code:
    `chem_equilibrium`, `db_handlers`, `user_substances`,
    `user_substances2`, `user_phase_or_solution`, and
    `user_phase_or_solution2`.
  - Legacy module names remain as compatibility shims so the broad public
    surface does not break in one shot.

# Linear Execution Plan

To avoid repeatedly rewriting the same lookup and cache functions, execute the
work in this order:

1. **Pass 1: parsing and numeric safety**
   Complete P0.1 and P0.2. Do not redesign `SubsData` yet.
2. **Pass 2: lookup correctness and batch truthfulness**
   Complete P0.3 and P0.4 with deterministic per-property reports.
3. **Pass 3: mutation invariants**
   Complete P0.5 and its full mutation-path test matrix.
4. **Pass 4: repository and canonical state**
   Complete P1.1 and P1.2 together; this is the only planned structural rewrite
   of `SubsData`.
5. **Pass 5: clone, errors, and network boundary**
   Complete P1.3-P1.5 after the canonical ownership model exists.
6. **Pass 6: measured optimization and API cleanup**
   Complete P2 only against the stabilized contracts and benchmark baseline.
7. **Pass 7: documentation and final debt removal**
   Complete P3 and remove compatibility scaffolding that is no longer needed.

## Residual Closeout Plan

This plan supersedes repeated broad re-audits. Work linearly and do not reopen
the completed repository or canonical `SubsData` redesign unless a regression
demonstrates a concrete contract violation.

1. **Closeout pass 1: panic containment**
   Complete P0.6 in `dG_dS.rs`, `User_substances2.rs`,
   `NISTdata_fitting.rs`, and `CEAdata.rs`, with state-preservation error tests.
2. **Closeout pass 2: fitting and report contract**
   Normalize NASA/NIST property reports, then apply typed LM result validation
   and property-wise acceptance to NIST and CEA.
3. **Closeout pass 3: deterministic tests and pure core**
   Replace default live NIST tests with fixtures and move all fitting/Gibbs
   output behind explicit `print_*` adapters.
4. **Closeout pass 4: API/test completeness**
   Finish typed error boundaries, best-effort batch reporting, compile-time
   cache privacy tests, provenance-through-export coverage, and prelude exports.
5. **Optional measured pass: performance only**
   Add benchmarks and make further clone/allocation changes only when profiling
   demonstrates a material cost.

## Final Completion Criteria

- [ ] No scoped production path panics on user, file, network, or library data.
- [ ] Every successful numeric result is finite and valid for its declared
  physical contract.
- [ ] Lookup is deterministic, property-specific, and preserves provenance.
- [ ] Partial failures are visible in returned typed reports.
- [ ] Input mutation cannot expose stale derived data.
- [ ] Library files are loaded once per explicit repository lifecycle, not once
  per aggregator.
- [ ] Clone behaviour is semantically faithful or deliberately unavailable.
- [ ] Core computations do not print or mutate unrelated state.
- [ ] Default tests are deterministic and offline.
- [ ] Benchmarks demonstrate that performance changes address measured costs.

# Dedicated Phase/Solution Architecture Audit

## Scope and current assessment

This audit covers `User_PhaseOrSolution.rs`, `User_PhaseOrSolution2.rs`, and
their direct equilibrium-facing contract. It does not propose a rewrite of the
equilibrium solvers themselves, but the adapter between the phase model and the
solvers must migrate with the data model.

The existing seven focused tests pass, but they mainly prove that the current
surface can be called. They do not protect component identity, deterministic
matrix/vector alignment, clone equivalence, failure atomicity, or a substance
appearing in more than one phase. Those are the central correctness contracts
for this module.

The intended canonical model is:

- `PhaseSystemSpec`: validated user input and lookup policy.
- `PhaseSpec`: typed phase id, explicit thermodynamic/activity model, and an
  ordered component list.
- `ResolvedPhaseSystem`: resolved `SubsData` records plus one immutable
  `SystemLayout` shared by every numeric, symbolic, and solver-facing path.
- `SystemLayout`: the sole ordered list of `(phase_id, substance_id)` component
  identities, phase ranges, and reverse indices used by vectors and matrices.
- Typed composition and result snapshots instead of nested
  `HashMap<Option<String>, (Option<f64>, Option<Vec<_>>)>` values.

`OnePhase` should become a convenience constructor or thin newtype over the
same canonical phase-system engine. It should not maintain a second copy of all
thermodynamic and equation-building algorithms.

## Phase/Solution P0: correctness before redesign

### P0.1 Preserve phase-component identity and deterministic order

- [x] Introduce typed `PhaseId` and `PhaseComponentId` (phase plus substance).
  A substance present in gas and liquid phases is two solver components even
  though both components share the same molecular composition.
- [x] Replace the canonical `HashMap<Option<String>, SubsData>` iteration order
  with an ordered phase/component layout. A `Vec` plus lookup indices is enough;
  no new container dependency is required.
- [x] Make `SubstancesContainer::get_all_substances()` distinguish between a
  unique catalog view and an ordered solver-component view. The current
  deduplication is invalid for multiphase equilibrium.
  - `get_all_substances()` now stays a deduplicated catalog view, while
    `get_ordered_component_labels()` and `get_ordered_substances()` expose the
    solver-facing ordered layout without flattening phase identity.
- [x] Rewrite both `indexed_moles_variables()` implementations from the shared
  layout.
- [x] Fix the current multiphase name collision produced by `format!("n{}", i + j)`.
  - [x] Fix the assignment that currently writes
    `self.vec_of_n_vars = self.vec_of_n_vars.clone()` instead of publishing the
    newly constructed vector.
  - [x] Make variable names stable under different `HashMap` insertion orders.
- [x] Rewrite both `create_full_map_of_mole_numbers()` implementations so mole
  vectors follow the declared component order, never `HashMap::values()` order.
- [x] Build the element-composition matrix, Gibbs/entropy vectors, symbolic
  variables, initial composition, and result labels from the same
  `SystemLayout`.
  - Matrix assembly and result-label generation now follow the canonical
    ordered layout instead of separate ad hoc phase iteration paths.

Required tests:

- [x] The same substance in two phases produces two distinct component ids,
  variables, matrix rows, and phase amounts.
- [x] Shuffling phase-map insertion order produces an identical layout and
  identical equations.
- [x] Mole vectors have the exact declared component order, not merely the
  expected length.
- [x] All generated variable names are globally unique for multiple uneven
  phase sizes.

### P0.2 Remove false-success clone and panic paths

- [x] Remove the manual `Clone` implementations that replace working `dG_fun`
  and `dS_fun` closures with functions returning `0.0`.
- [x] Store shareable compiled functions as `Arc<dyn Fn...>`, or
  remove `Clone` and provide an explicit fallible rebuild. A clone must never
  be numerically different while looking ready.
- [x] Make `SubstancePhaseMapping::new()` fallible instead of asserting on
  user-provided vector lengths.
- [x] Remove production `unwrap()` calls from:
  - `calculate_elem_composition_and_molar_mass()`;
  - `indexed_moles_variables()`;
  - `calculate_Lagrange_equations_sym()` and
    `calculate_Lagrange_equations_fun2()`;
  - both `calculate_Lagrange_equations_fun()` closure constructors;
  - both `create_full_map_of_mole_numbers()` implementations.
- [x] Validate all matrix dimensions, component lookups, and closure inputs at
  construction time. Solver closures cannot return typed errors, so an invalid
  closure must never be published.
  - `calculate_Lagrange_equations_sym()` and `calculate_Lagrange_equations_fun()`
    now reject mismatched component counts and missing Gibbs data before a
    closure is published; the runtime closure guards against wrong lambda/vector
    lengths by returning an empty result instead of panicking.
- [x] Remove direct `println!` diagnostics from equation construction and
  retain the complete context in typed errors.

Required tests:

- [x] Original and clone return equivalent numeric, symbolic, and compiled
  Gibbs/entropy values.
- [ ] Missing Gibbs data, wrong matrix dimensions, missing phases, and wrong
  lambda/composition lengths return typed errors without partial publication.
  - [x] The pure Lagrange adapter rejects missing Gibbs functions and invalid
    matrix dimensions before it returns a closure; malformed runtime solver
    vectors return the established empty-result signal rather than panicking.
  - [x] Symbolic wrong-matrix and numeric missing-Gibbs regressions now also
    prove that the existing cache family and revision remain unchanged after
    rejection.
- [ ] No public constructor or mutation path panics on malformed input.
  - [x] Public cache views no longer expose an indexing operator that panics for
    an absent phase; callers use the explicit optional `get()` contract.
  - [x] Public typed constructors and pure Gibbs/entropy evaluation facades are
    covered by an unwind regression test for malformed input. Internal legacy
    mutation adapters remain a separate migration concern.

### P0.3 Validate compositions and phase definitions

- [x] Replace nested `Option` tuples with a typed `PhaseComposition` containing
  an ordered amount vector and an explicit total amount.
  - [x] Numeric one-phase and multi-phase read paths now use a typed
    `PhaseComposition` wrapper instead of passing the raw tuple around as the
    primary internal representation.
  - [x] The typed composition now has a fallible numeric constructor, so
    invalid phase totals and component vectors fail before entering the
    calculation paths.
- [x] Require finite non-negative component amounts and a finite positive phase
  total where logarithmic activity terms are evaluated.
  - [x] Numeric phase-composition paths now validate finite, non-negative,
    summed phase totals before Gibbs/entropy evaluation.
- [x] Validate finite positive temperature and pressure together with an exact
  phase/component composition request before evaluating any phase.
  - `ThermoEvaluationConditions` now makes the physical inputs explicit, and
    `evaluate_gibbs`/`evaluate_entropy` reject missing, extra, or wrongly sized
    phase compositions before a lower-level calculator is invoked.
  - The read-only evaluation paths now run against local `SubsData` snapshots,
    so both successful and failed evaluations leave the wrapper and its
    published result caches unchanged.
- [x] Define and validate whether `Np` must equal the sum of component amounts;
  do not silently accept contradictory values.
  - [x] The numeric composition constructor now enforces total/component sum
    agreement instead of letting contradictory values reach the solver.
- [x] Reject unknown, missing, and extra phases in numeric requests; reject
  empty/duplicate typed phase specifications and non-finite `T`/`P` before a
  phase calculator runs.
- [x] Validate finite positive reference temperature `Tm` at the symbolic and
  numeric Lagrange adapter boundary.
- [x] Stop silently assigning `Phases::Gas` when a named multiphase entry is
  missing from `physiscal_nature_of_phase_or_solution`.
  - `SubstancesContainer::MultiPhase` now requires a complete physical-state
    map. Only the explicit `SinglePhase` compatibility form defaults to an
    ideal-gas `PhaseSpec`.
- [x] Make all multi-phase operations transactional: failure in a later phase
  must not leave earlier `SubsData` instances or derived caches updated.
  - [x] Phase-local preparation and symbolic/closure construction now use one
    commit-on-success payload barrier. A failure after any earlier temporary
    phase update leaves the visible payload map, derived property caches, and
    cache shape unchanged.
  - [x] Numeric Gibbs/entropy publication now validates the request, canonical
    phase/component cache shape, and finite values before replacing any cache
    family or context; rejected publication retains the previous revision and
    cache state.
  - [x] Symbolic and closure builders now validate exact canonical
    phase/component coverage before commit; raw cache-map insertion is
    restricted to test fixtures and cannot create a production bundle for an
    unknown phase.
  - [x] Typed multi-phase resolution now installs payloads, canonical layout,
    empty cache bundles, and lookup provenance through one consuming
    `ResolvedPhaseSystem -> PhaseSystem` transition rather than exposing a
    payload-only intermediate state.
  - [x] The single-phase factory now consumes the same resolved payload through
    one validated install transition; it no longer publishes data, phase spec,
    and provenance through separate mutations.
  - [x] `OnePhase` rejects a multi-phase resolved payload before any state is
    published, preventing accidental loss of phase-qualified components.
  - [x] Symbolic Gibbs substitutions now publish a new cache revision, so a
    snapshot cannot look current after its expressions have changed.
  - [x] `calculate_lagrange_equations_fun2()` now stages Gibbs closure
    construction, validates equation assembly, and only then publishes the
    refreshed payload/cache pair. A matrix-layout failure preserves the prior
    cache shape and revision.

Required tests:

- [x] Negative, NaN, infinite, inconsistent-total, missing-phase, extra-phase,
  and duplicate-component cases fail with actionable typed errors.
  - [x] `Tm` is covered directly for zero, `NaN`, and both infinities in the
    symbolic and numeric Lagrange adapters.
- [x] A failure in the second phase leaves the complete system state unchanged.
- [ ] Zero mole fractions follow one explicitly documented boundary policy and
  never accidentally publish NaN.
  - [x] Typed numeric Gibbs/entropy requests reject a zero component amount
    whenever the raw calculator would apply its ideal-gas `ln(n_i / Np)`
    correction. The check runs before calculator preparation; zero condensed
    components remain valid, and absent phase metadata continues to mean gas
    to match the legacy calculator contract.
  - [ ] Defer migration of the remaining raw Gibbs/entropy closures to a
    fallible or guarded evaluation contract until the ChemEquilibrium facade
    is migrated. A bare `Fn(...) -> f64` cannot surface the same typed domain
    error at invocation time, so it remains a compatibility-only API and must
    not become the primary public numeric evaluation path.

### Deferred until the ChemEquilibrium migration

These items depend on changing equilibrium consumers, not on further changes
to the phase-data engine. Keep them out of independent phase-system passes.

- [ ] Migrate equilibrium consumers from `ThermodynamicsCalculatorTrait` to
  `PhaseLayoutAccess`, `PhasePropertyEvaluator`,
  `PhaseSymbolicPropertyBuilder`, and `PhaseEquilibriumAssembly`.
- [ ] Replace equilibrium closure input `Option<Vec<f64>>` with a borrowed
  typed composition view aligned to `SystemLayout`.
- [ ] Migrate legacy raw Gibbs/entropy `Fn(...) -> f64` closures together with
  the equilibrium facade. Do not add another parallel phase-cache format
  before this migration: equilibrium consumers still require the infallible
  callback signature.
  - Return to this item only after the equilibrium facade accepts the narrow
    typed composition API and can propagate `SubsDataResult` at callback
    invocation time.
- [ ] Remove `CustomSubstance`/`enum_dispatch` once no equilibrium-facing
  consumer requires its broad legacy surface.
- [ ] Add equilibrium integration story-tests for `PhaseEvaluationRequest`,
  cache revision invalidation, and the same substance represented in two
  phases.

## Phase/Solution P1: canonical domain model

### P1.1 Separate phase identity, physical state, and activity model

- [x] Add a typed `PhaseSpec` with `PhaseId`, ordered components, and a
  `PhaseModel`.
  - `SubstanceSystemSpec` now stores ordered `PhaseSpec` values as its canonical
    input. `SubstancesContainer` and `with_phase_natures()` are retained only as
    a fallible adapter for existing callers.
- [x] Keep physical state (`Gas`, `Liquid`, `Solid`, `Condensed`) separate from
  the thermodynamic/activity model.
  - [x] Route physical state into local record selection through one typed
    `ThermoRecordQuery`, rather than requiring callers to encode a library key
    such as `Fe(L)` by hand.
  - [x] Make suffix handling library-aware: `NASA_cond`/NIST interpret their
    documented `(g)/(l)/(s)` and NASA-condensed allotrope conventions, while
    `nuig_thermo` parenthesized chemistry labels remain literal identifiers.
  - [x] Preserve the selected source record key in `SearchResult` provenance
    and reject ambiguous state requests (for example solid `Fe` with several
    NASA-condensed polymorphs) rather than selecting an arbitrary record.
  - [x] Keep `PhaseSpec` physical state as phase/NIST context, not an implicit
    local-library filter. State-aware lookup is an explicit `SubsData` opt-in
    and its bulk setter remains transactional when a caller chooses it.
  - [ ] Extend the catalog format with explicit state metadata when upstream
    libraries provide it. Key conventions remain a compatibility bridge, not a
    universal chemical-identity parser.
- [x] Define the supported models explicitly, starting with the models the code
  really implements: `IdealGas` and `PureCondensed`.
- [x] Do not call a container a "solution" until an explicit solution model is
  selected. The current `PhaseModel` deliberately has no solution variant:
  `Phases::Liquid` or `Phases::Solid` resolve only as `PureCondensed`, not as an
  implicit ideal or non-ideal solution.
- [x] Require legacy multi-phase state maps to match declared phase names
  exactly: both missing states and states for unknown phases are typed errors.
- [ ] Decide the first real solution contract (`IdealSolution` and its standard
  state, or a deliberately unsupported typed variant) before exposing it in
  the public builder.

### P1.2 Use one engine for one or many phases

- [x] Replace duplicated `OnePhase` and `PhaseOrSolution` algorithm/state fields
  with one `PhaseSystem` implementation.
  - Both public facades are now thin views over the same crate-private engine.
    The only remaining duplicated surface is deliberately compatibility-facing
    and is tracked separately under the deferred `CustomSubstance` and
    equilibrium migration items below.
- [x] Move the canonical typed metadata (`PhaseSpec` and resolved
  `SystemLayout`) into `PhaseSystem`, so both wrappers now read one common
  declaration/layout owner after typed resolution.
  - [x] Move canonical phase-keyed `SubsData` storage into `PhaseSystem`.
    `OnePhase` no longer owns a separate `SubsData` field and `PhaseOrSolution`
    no longer owns a separate raw phase map; both facades now read the same
    engine-owned payload through read-only views.
  - [x] Replace direct consumer/test access with `subs_data_view()` and
    `phase_data_view()`. Crate-private replacement and transition helpers
    invalidate metadata and derived caches after the final payload change.
  - [x] Keep `PhaseSystem` crate-private so external code cannot mutate cache
    maps or raw payloads behind the facade invariants.
  - [x] Route pure Gibbs and entropy evaluation through `PhaseSystem`, so the
    one-phase facade no longer carries a second property-evaluation path.
  - [x] Move shared payload preparation, NIST fallback, numeric result
    publication, symbolic/closure cache construction, and symbolic `T`/`P`
    substitution into `PhaseSystem`. `OnePhase` and `PhaseOrSolution` now
    delegate these operations instead of maintaining competing implementations.
  - [x] Centralize layout-derived element matrices, symbolic mole variables,
    and sparse mole-number normalization in `PhaseSystem`.
    - Element/molar-mass assembly now evaluates cloned phase payloads for both
      facades, instead of mutating only the one-phase payload cache.
    - Both facades now use the same `Np{phase}` / `n{phase}_{component}` names
      and generate solver vectors exclusively in `SystemLayout` order.
    - The one-phase path no longer derives vectors from `HashMap::values()`.
  - [x] Introduce `MoleNumberSnapshot` as the typed result of sparse mole
    normalization. It carries the `SystemLayout`, normalized named amounts,
    ordered phase vectors, and cross-phase substance totals; the former tuple
    of maps now exists only as an explicit compatibility projection.
  - [x] Introduce `PhaseEvaluationRequest` as the atomic numeric-evaluation
    boundary. Temperature, pressure, and phase compositions now travel
    together through the pure evaluator and numeric cache provenance, instead
    of being independently threaded through parallel method parameters.
  - [x] Replace anonymous symbolic mole-variable bookkeeping with the named
    `PhaseSymbolicLayout` snapshot. The former four-part tuple is now projected
    only for the broad compatibility trait; engine code and tests consume named
    vectors/maps generated from the canonical `SystemLayout`.
  - [x] Keep `PhaseSystem` as the sole owner of `SystemLayout`. The symbolic
    snapshot stores only derived variable data; it no longer duplicates a
    second layout/provenance copy that could diverge from `resolved_layout`.
- [x] Introduce a shared read-only `PhaseDataView` and route pure numeric
  Gibbs/entropy evaluation through it for both wrappers.
  - The bridge preserves the same ordered layout for one and many phases and
    evaluates only cloned `SubsData` payloads, so a read-only evaluation cannot
    publish a partial result.
  - Crate-private replacement/mutation compensators now invalidate phase specs,
    layouts, and all caches before exposing legacy raw payload edits. Their
    regression tests cover both one- and multi-phase paths.
- [x] Retain `OnePhase` as a one-entry facade over `PhaseSystem` storage and
  cache state. Remaining method-level duplication is tracked by the parent
  engine-consolidation item above.
- [ ] Reduce or remove `CustomSubstance`/`enum_dispatch` once both variants use
  the same engine.
  - [x] `CustomSubstance` now implements the narrow preparation, symbolic, and
    equilibrium roles by delegation. New consumers no longer need the broad
    enum-dispatch trait; removing the enum itself remains deferred until the
    equilibrium migration.
- [ ] Split the oversized `ThermodynamicsCalculatorTrait` into narrow roles:
  resolved data access, phase-property evaluation, layout/composition access,
  and equilibrium equation assembly.
  - [x] Introduce `PhaseLayoutAccess` and `PhasePropertyEvaluator` for both
    facades. `CustomSubstance::system_layout()` now consumes the layout role,
    while the old enum-dispatch trait remains a temporary compatibility adapter
    for legacy equilibrium code.
  - [x] Introduce `PhaseDataPreparation`, `PhaseSymbolicPropertyBuilder`, and
    `PhaseEquilibriumAssembly` for both facades. New solver-facing code can
    depend on one capability at a time; `ThermodynamicsCalculatorTrait` stays
    intact only as a compatibility adapter until its legacy consumers migrate.
    Its preparation, symbolic, and Lagrange methods now delegate through those
    narrow roles instead of maintaining a second direct `PhaseSystem` route.
- [ ] Move equilibrium-only Lagrange equation builders out of the data
  acquisition facade and into an equilibrium adapter module.
  - [x] Symbolic Gibbs-minimisation assembly now lives in
    `phase_equilibrium_adapter.rs`. It is a pure operation over
    `SystemLayout`, a borrowed Gibbs view, and the component-by-element matrix.
    The adapter uses global component indices, preventing the former second
    phase matrix-column reuse bug.
  - [x] Numeric closure assembly now also lives in
    `phase_equilibrium_adapter.rs` and uses the global `SystemLayout` index for
    each component. This fixes the former multi-phase bug where every phase
    reused matrix columns from zero.
  - [x] Move the remaining facade orchestration into `PhaseSystem`: symbolic
    builder dispatch, closure-map conversion, and the `calculate_*_fun2`
    workflow now share one engine path for one and many phases. The adapter
    remains pure and receives only layout, property views, matrices, and
    explicit legacy closure maps.
  - [ ] Replace the compatibility closure input `Option<Vec<f64>>` with a
    borrowed typed composition view once the equilibrium solver facade itself
    can accept that narrower contract.
- [x] Make read-only methods take `&self`; methods such as
  `get_all_substances()` and `extract_SubstancesContainer()` do not need
  `&mut self`.

### P1.3 Replace parallel public caches with typed snapshots

- [x] Make phase-system fields private and expose invariant-preserving builders,
  mutation methods, and borrowed views.
  - `OnePhase` and `PhaseOrSolution` cache fields are now private or
    crate-private and are read through explicit accessor/view methods.
    Layout revision is also hidden behind accessors, so callers no longer need
    direct field access for the visible contract. Test code now reaches cache
    state only through debug-only mutation hooks or read-only views.
  - Single-phase raw mutable cache accessors are now crate-private and are
    reachable only through explicitly named debug hooks; external callers must
    use validated publication APIs.
  - Bulk raw `replace_dG*`/`replace_dS*` fixture helpers are now compiled only
    for tests. Production builds retain no unvalidated cache-replacement path.
- [x] Replace parallel `dG`, `dG_fun`, `dG_sym`, `dS`, `dS_fun`, and `dS_sym`
  maps with typed result snapshots aligned to `SystemLayout` and carrying their
  thermodynamic conditions.
  - [x] PhaseSystem now stores per-phase Gibbs and entropy property families,
    each grouping numeric, closure, and symbolic representations. The result
    families stay aligned inside one phase-scoped record rather than six
    independently named maps.
  - [x] Snapshot construction is now centralized through shared typed helpers
    for single-phase and multi-phase views, and the public facade now exposes a
    typed `ThermoResultSnapshot` aligned to `SystemLayout` and explicit
    temperature/pressure metadata. The remaining work is to reduce the
    underlying parallel caches themselves, not just their public packaging.
  - [x] Cache write paths now also go through shared revision-aware helpers
    instead of repeating raw `field = value; bump_revision()` sequences.
  - [x] Numeric Gibbs and entropy caches now retain a typed request context
    (temperature, pressure, phase compositions, and configuration revision).
    Consumers can ask whether a cache is valid for an exact new request instead
    of trusting the mere presence of values.
  - [x] The symbolic Lagrange read path now borrows the cache view instead of
    cloning the full `dG_sym` map before evaluation.
  - [x] The `calculate_Lagrange_equations_fun2()` helpers now consume the
    canonical cached Gibbs-function view instead of rebuilding function maps
    from `SubsData` again.
- [x] Prefer pure `evaluate_*`/`build_*` methods that return results, and tag
  published cache products with the inputs/layout revision that makes them
  valid.
  - [x] `OnePhase` and `PhaseOrSolution` now expose `evaluate_gibbs()` and
    `evaluate_entropy()` paths which return values without publishing a
    `PhaseSystem` cache. The legacy calculation methods explicitly publish the
    returned values afterwards.
  - [x] `SubsData` derived builders now run on a cloned working copy, so the
    public one-phase Gibbs/entropy paths no longer publish cache state while
    computing a result. The remaining `&mut self` cache-building methods are
    now explicit publication paths rather than hidden side effects.
- [x] Remove or rename `get_dG_sym_mut()`: it currently returns an owned clone,
  not mutable access.
- [x] Return borrowed views from `get_dG_sym()`/`get_dG()` instead of cloning
  complete nested maps on every read.
  - Key read paths now use borrowed nested-map views, and the old cloning-based
    accessors remain only as compatibility snapshots.
  - `PhaseThermoPropertyView` now also exposes `iter_ref()` plus explicit
    `phase_count` and `populated_phase_count`, so borrowed consumers do not
    need a boxed owned-key iterator merely to inspect cache state.
  - `NestedPhaseCacheView` now mirrors the same borrowed traversal contract
    through `NestedPhaseCacheIter`, including the single-phase `None` key.
    Both cache views now distinguish canonical phase count from populated
    property-cache count explicitly.
- [x] Invalidate layout, variables, property snapshots, and equations through
  one revision boundary whenever phases, components, lookup policy, or resolved
  data change.
  - `PhaseOrSolution` and `OnePhase` now carry `layout_revision` counters, and
    the main layout/property-building paths bump them on mutation while
    read-only Lagrange builders stay stable.
  - The revision boundary now also recreates cache bundles from canonical
    payload keys, so borrowed cache views cannot reveal values from the prior
    configuration merely because their request context was cleared.

### P1.4 Separate specification, resolution, and computation

 - [x] Replace `SubstanceSystemFactory::create_system(...many primitive
  arguments...) -> Result<_, String>` with a typed builder and typed error.
  - `SubstanceSystemSpec` and its fluent builder now carry the construction
    inputs, and `resolve()` performs the typed lookup step. The legacy wrapper
    remains only as a temporary compatibility layer.
 - [x] Make system construction validate only the specification; make database
  lookup/NIST access an explicit fallible `resolve()` step.
  - The new builder validates empty/degenerate containers before lookup, and
    resolution is now an explicit typed step rather than implicit inside the
    primitive argument list.
  - Lookup-policy application is centralized so single-phase and multi-phase
    resolution share the same configuration path.
  - [x] `ResolvedPhaseSystem` now atomically carries resolved `SubsData`, the
    corresponding ordered `PhaseSpec` values, and the derived `SystemLayout`
    before a single- or multi-phase facade is selected.
- [x] Reuse a shared `ThermoRepository`/lookup policy across phases instead of
  treating every phase as an independent database lifecycle.
  - Phase resolution can now receive one explicit `Arc<ThermoRepository>`;
    every resolved `SubsData` receives that same immutable catalog while
    keeping its own query state and calculator instances. The default path
    obtains the same shared catalog through `ThermoData::try_default_repository`.
- [ ] Store phase kind once on the phase, then derive the per-substance phase
  view needed by `SubsData`; do not duplicate the same value manually into
  every component as canonical state.
  - [x] `PhaseSpec` now derives the legacy `SubsData::map_of_phases` projection
    in one place. The raw per-substance map remains only because existing
    lower-level property calculations still consume that compatibility shape.
- [x] Keep explicit NIST fallback policy visible in the resolved-system report,
  including per-component provenance.
  - `ResolvedPhaseSystemReport` now records the NIST fallback policy and an
    ordered per-phase `SearchSummaryReport`; each row exposes the selected
    library, priority tier, state, and calculator family without raw cache
    access.

## Phase/Solution P2: API, performance, and module boundaries

- [ ] Replace closure arguments `Option<Vec<f64>>` and `Option<f64>` with
  borrowed typed composition/state views. Avoid cloning the full composition
  vector once per species evaluation.
  - Deferred until ChemEquilibrium accepts a fallible typed composition
    callback. The phase engine already exposes `PhaseComposition`; changing
    only this side would create a second adapter around the same legacy
    closure signature.
- [ ] Transpose and validate matrices once when constructing a numeric closure,
  not on every solver call.
  - Deferred until the equilibrium adapter owns the numeric closure boundary.
    Matrix orientation belongs to Lagrange assembly, not to phase lookup or
    property evaluation.
- [ ] Use dense ordered vectors for solver hot paths and maps only as lookup or
  presentation indices.
  - [x] `ThermoResultSnapshot` now projects numeric Gibbs and entropy caches
    into validated `SystemLayout`-ordered vectors. The remaining migration is
    to make equilibrium consumers accept these vectors directly instead of
    rebuilding map lookups inside their legacy closure path.
- [ ] Share immutable symbolic expressions and compiled functions where their
  ownership permits it.
- [ ] Normalize spelling and names (`calculate`, `mixture`, `coefficients`,
  `phase`, `Np`, and `dG`/`dS` domain notation) without erasing conventional
  scientific symbols.
- [x] Delete the large commented-out equilibrium-constant implementation.
  It was an uncompiled duplicate with no validated contract; any future
  equilibrium-constant behavior must enter through the dedicated equilibrium
  workstream with tests rather than being revived from a stale comment block.
- [x] Split the current files by responsibility: domain model/layout, builder
  and resolution, property evaluation, and equilibrium adapter. Keep public
  re-exports as the stable facade.
  - [x] Extract `phase_domain.rs`: typed phase state/model/specification and
    resolved-system payloads now have one focused home.
  - [x] Extract `phase_evaluation.rs`: numeric evaluation requests, condition
    validation, composition validation, and the pure `SubsData` bridge are no
    longer mixed with facade/cache code.
  - [x] Extract `phase_cache.rs`: symbolic layouts, per-phase cache bundles,
    borrowed cache views, and revision-aligned snapshots are separated from
    `PhaseSystem` orchestration.
  - [x] Extract `phase_factory.rs`: compatibility container adapters, typed
    specification/builder validation, lookup policy, repository resolution,
    and factory errors are separated from `PhaseSystem` orchestration.
  - [x] Extract `phase_facade.rs`: `CustomSubstance` is now a read-only
    compatibility facade over `OnePhase` and `PhaseOrSolution`, with no direct
    ownership of raw payloads or cache state.
  - [x] Extract `phase_interfaces.rs`: narrow consumer capabilities and their
    facade implementations are separated from the engine and from the broad
    legacy calculator trait.
  - [x] Extract `phase_system_facade.rs`: the public `PhaseOrSolution` API,
    pure evaluation entry points, read-only cache views, snapshots, and
    crate-private transition helpers no longer obscure `PhaseSystem` state
    orchestration.
  - [x] Extract `phase_legacy_api.rs`: the deprecated broad
    `ThermodynamicsCalculatorTrait` and its multi-phase compatibility
    implementation now live outside the engine; the disabled duplicate and
    unmaintained equilibrium stubs were removed from the facade module.
  - [x] Extract `phase_moles.rs`: canonical `n_i`/`Np` variable construction
    and sparse-to-ordered mole normalization now live outside lifecycle and
    cache orchestration, while `PhaseSystem` remains their single owner.
  - [x] Extract `phase_elements.rs`: pure component-by-element matrix and
    molar-mass assembly are now isolated from cache lifecycle and operate on
    cloned phase payloads aligned to `SystemLayout`.
  - [x] Extract `phase_operations.rs`: fallible phase-local preparation now
    stages cloned payloads transactionally outside `PhaseSystem`; the engine
    remains responsible only for committing a successful stage and invalidating
    derived state.
  - [x] Extract `phase_property_builders.rs`: symbolic Gibbs/entropy builders
    and compiled closure construction now return staged payloads and cache
    values without owning publication or revision policy.
  - [x] Document `phase_layout.rs` as the canonical solver-order contract,
    including the distinction between a substance and a phase-qualified
    component.
  - [x] Keep the remaining `PhaseSystem` orchestration together. It is the
    single owner of transactional state transitions and revision-aware cache
    publication; extracting it further would recreate the second orchestration
    facade this refactor removed.

## Phase/Solution P3: contract and story-test matrix

- [x] Add numeric/closure/symbolic equivalence tests at the same `T`, `P`, and
  composition for every supported `PhaseModel`.
  - [x] The `IdealGas` Gibbs path is now checked end-to-end against one resolved
    local NASA fixture: pure numeric evaluation, the cached closure, and the
    symbolic expression after `T`, `P`, and composition substitution agree.
  - [x] The `PureCondensed` Gibbs path is checked through a named liquid phase.
    It reuses a NASA source record while proving that numeric, closure, and
    symbolic paths all suppress the ideal-gas concentration correction.
  - [x] Entropy now has the same three-way equivalence coverage for `IdealGas`
    and `PureCondensed`; the latter proves the condensed path remains free of
    ideal-gas concentration terms.
  - [x] The standalone ChemEquilibrium log-moles formulation now has an
    offline fixture proving that the canonical numeric residual, its legacy
    closure adapter, and the symbolic residual generator evaluate identically
    for the same prepared problem and log-mole vector.
- [ ] Add a two-phase story with a shared substance across phases and verify
  element conservation, equation count, variable labels, and phase totals.
  - [x] The typed `spec -> resolve -> layout -> normalize` path now covers H2
    in gas and liquid phases. The story verifies distinct phase-qualified
    component labels, `IdealGas` versus `PureCondensed` models, independent
    mole variables/totals, and the expected cross-phase H2 total. Element and
    equilibrium-equation assertions remain part of the deferred equilibrium
    workstream.
- [ ] Add pure-condensed, ideal-gas, and explicitly unsupported solution-model
  fixtures.
- [x] Add state-transition tests for spec -> resolved -> layout -> evaluated ->
  invalidated/rebuilt.
  - [x] The one-phase lifecycle now has one story-test covering resolution,
    pure evaluation, explicit cache publication, payload mutation that clears
    layout/cache/report state, and clean reconstruction from the original spec.
  - [x] A matching multi-phase story now publishes Gibbs values for gas and
    condensed phases, mutates only the gas payload, and proves that all
    system-wide cache/layout/report state is invalidated before rebuilding.
- [x] Add compile-time/API tests proving callers cannot mutate phase/component
  collections or derived snapshots directly.
  - [x] The public `PhaseOrSolution` documentation now contains a
    `compile_fail` boundary test: an external caller cannot access the private
    `PhaseSystem` engine and therefore cannot bypass revision-aware transition
    methods or cache invalidation.
- [x] Add local-repository story tests for mixed library provenance without
  network access.
  - [x] A temporary two-library NASA fixture now resolves gas H2 from
    `NASA_gas` and liquid H2O from `NASA_cond` through one injected repository.
    The phase facade report proves both selected libraries while NIST fallback
    remains disabled.
- [ ] Add property tests for phase insertion order and composition-map insertion
  order invariance.
  - [x] Deterministic regression coverage now proves that permuting both raw
    phase records and `PhaseSpec` input leaves the resolved solver layout and
    phase order unchanged. Randomized/property coverage remains open.
  - [x] An exhaustive three-phase matrix now covers all six `PhaseOrSolution`
    insertion orders together with reverse component-map insertion, and proves
    stable component labels, symbolic mole variables, and normalized vectors.
- [ ] Replace length/contains-only assertions with exact component order,
  equation identity, numeric value, and typed-error assertions.

### Phase/Solution technical stage close-out

The independent engine work is complete for this stage: canonical layouts,
transactional payload/cache publication, pure numeric evaluation, provenance,
repository sharing, module boundaries, and deterministic lifecycle stories are
covered. Do not reopen this layer for cosmetic refactors alone.

### Deferred phase backlog

These are explicit future tasks, not unfinished work hidden inside the current
phase-engine implementation. Do not start them without entering the named
workstream and agreeing on its contract.

#### P1: physical-model decisions

- [ ] Define the first supported solution model before adding a `Solution`
  `PhaseModel` variant: ideal/non-ideal activity law, standard state,
  composition domain, and the meaning of pressure for that phase.
- [ ] Add fixtures for the chosen solution model and a typed rejection fixture
  for every still-unsupported model. The existing `IdealGas` and
  `PureCondensed` matrix is complete; this item is about new physics only.

#### P1: ChemEquilibrium migration

- [x] Give `ReactionExtentError` and `SolveError` stable `Display` and
  `std::error::Error` contracts. Nested `SubsDataError` and `SolveError`
  sources now remain discoverable through `source()` instead of forcing GUI or
  workflow code to parse `Debug` output.
- [ ] Remove the deprecated threshold-based `equilibrium_logmole_residual2` /
  `equilibrium_logmole_jacobian2` pair after any external experimental users
  migrate to the canonical validated log-moles formulation. The core solver
  and all new tests already avoid this alternate activity-mask model.
- [ ] Migrate every equilibrium consumer from `CustomSubstance` and
  `ThermodynamicsCalculatorTrait` to the narrow phase interfaces and the
  `phase_equilibrium_adapter`.
- [ ] Replace legacy `Fn(..., Option<Vec<f64>>, Option<f64>) -> f64` callbacks
  with a borrowed, fallible composition/state contract at the equilibrium
  solver boundary. Only then remove the legacy infallible closure adapters.
- [ ] Move one-time matrix orientation/validation to that new numeric closure
  boundary, then remove per-call matrix work from legacy Lagrange paths.
- [ ] Complete equilibrium stories: phase-qualified element conservation,
  equation count/identity, and two occurrences of the same substance in
  different phases.

#### P2: lower-level data and performance follow-up

- [ ] Remove the `SubsData::map_of_phases` compatibility projection after its
  property calculators accept phase-level state from `PhaseSpec` directly.
- [ ] Benchmark the phase/equilibrium hot path before sharing more `Expr`
  values or compiled functions. `PhaseFunction` is already shared through
  `Arc`; do not add ownership machinery without a measured bottleneck.
- [ ] Add randomized/property permutations beyond the existing exhaustive
  three-phase ordering matrix when a property-test dependency and stable
  generator policy are selected.

#### P3: repository-wide documentation debt

- [ ] Repair or explicitly mark as `text`/`ignore` the unrelated broken
  doctests in `User_substances`, `ChemEquilibrium`, `settings`, and
  `library_manager`. The phase-layout diagram is already `text`; this task
  must not be mixed with phase-engine behavior changes.
- [ ] Triage the remaining legacy length/contains-only assertions. New phase
  engine stories already use exact order, numeric values, and typed errors;
  broaden old assertions only together with the contract they are intended to
  protect, especially for equilibrium behavior.

## Phase/Solution Linear Execution Plan

1. **Pass 1: correctness scaffold**
   Add typed ids, `SystemLayout`, and failing regression tests for repeated
   species, ordering, variable collisions, and clone equivalence. Adapt the old
   structures internally without changing the public facade yet.
2. **Pass 2: composition and equation paths**
   Migrate `indexed_moles_variables()`, `create_full_map_of_mole_numbers()`, the
   element matrix, and Lagrange builders to the shared layout. Remove the P0
   panic paths and validate all closure inputs before publication.
3. **Pass 3: canonical phase engine**
   Introduce `PhaseSpec`/`PhaseModel`/`ResolvedPhaseSystem`, make construction
   and resolution separate typed steps, and turn `OnePhase` into a thin facade.
4. **Pass 4: state and API compression**
   Replace parallel public caches with typed snapshots, split the broad trait,
   privatize mutation, and migrate equilibrium consumers through the adapter.
5. **Pass 5: optimization and final matrix**
   Borrow hot-path inputs, share closures/expressions, remove duplicated/dead
   code, and complete the deterministic story/property test matrix.

Completion means the model can represent one substance in multiple phases
without identity loss, every matrix/vector/expression uses one deterministic
layout, cloning is truthful, invalid inputs are typed failures, and "solution"
has an explicit thermodynamic model rather than being only a name.