1use std::collections::BTreeSet;
34
35use serde::Serialize;
36
37pub const REDACTED: &str = "<redacted>";
40
41#[derive(Clone, Copy, Debug, Serialize, PartialEq, Eq, PartialOrd, Ord)]
49#[serde(rename_all = "snake_case")]
50pub enum DigestId {
51 AgentScore,
53 FieldContext,
55 RunProvenance,
57}
58
59#[derive(Clone, Copy, Debug, Serialize, PartialEq, Eq)]
61#[serde(tag = "coverage", rename_all = "snake_case")]
62pub enum Coverage {
63 Covered { digest: DigestId },
65 Redacted {
68 digest: DigestId,
69 reason: &'static str,
70 },
71 Excluded { reason: &'static str },
74}
75
76impl Coverage {
77 pub fn digest(&self) -> Option<DigestId> {
79 match self {
80 Coverage::Covered { digest } | Coverage::Redacted { digest, .. } => Some(*digest),
81 Coverage::Excluded { .. } => None,
82 }
83 }
84
85 pub fn binds_value(&self) -> bool {
87 matches!(self, Coverage::Covered { .. })
88 }
89}
90
91#[derive(Clone, Copy, Debug)]
93pub struct EvidenceInventory {
94 pub document: &'static str,
96 pub fields: &'static [(&'static str, Coverage)],
98}
99
100#[derive(Clone, Debug, Default, Serialize, PartialEq)]
102pub struct InventoryAudit {
103 pub undeclared: Vec<String>,
106 pub stale: Vec<String>,
108}
109
110impl InventoryAudit {
111 pub fn is_complete(&self) -> bool {
113 self.undeclared.is_empty() && self.stale.is_empty()
114 }
115}
116
117#[derive(Clone, Debug, Serialize, PartialEq, Eq)]
119#[serde(tag = "error", rename_all = "snake_case")]
120pub enum PreimageError {
121 UndeclaredField { field: String },
123 MissingField { field: String },
125 DuplicateField { field: String },
127}
128
129impl EvidenceInventory {
130 pub fn coverage(&self, field: &str) -> Option<Coverage> {
132 self.fields
133 .iter()
134 .find(|(name, _)| *name == field)
135 .map(|(_, c)| *c)
136 }
137
138 pub fn fields_for(&self, digest: DigestId) -> Vec<&'static str> {
141 self.fields
142 .iter()
143 .filter(|(_, c)| c.digest() == Some(digest))
144 .map(|(name, _)| *name)
145 .collect()
146 }
147
148 pub fn exclusions(&self) -> Vec<(&'static str, &'static str)> {
150 self.fields
151 .iter()
152 .filter_map(|(name, c)| match c {
153 Coverage::Excluded { reason } => Some((*name, *reason)),
154 Coverage::Covered { .. } | Coverage::Redacted { .. } => None,
155 })
156 .collect()
157 }
158
159 pub fn audit<S: AsRef<str>>(&self, observed: &[S]) -> InventoryAudit {
164 let declared: BTreeSet<&str> = self.fields.iter().map(|(n, _)| *n).collect();
165 let present: BTreeSet<&str> = observed.iter().map(|s| s.as_ref()).collect();
166 InventoryAudit {
167 undeclared: present
168 .difference(&declared)
169 .map(|s| (*s).to_string())
170 .collect(),
171 stale: declared
172 .difference(&present)
173 .map(|s| (*s).to_string())
174 .collect(),
175 }
176 }
177
178 pub fn preimage(
194 &self,
195 digest: DigestId,
196 values: &[(&str, &str)],
197 ) -> Result<Vec<u8>, PreimageError> {
198 let mut seen: BTreeSet<&str> = BTreeSet::new();
199 for (name, _) in values {
200 if self.coverage(name).is_none() {
201 return Err(PreimageError::UndeclaredField {
202 field: (*name).to_string(),
203 });
204 }
205 if !seen.insert(name) {
206 return Err(PreimageError::DuplicateField {
207 field: (*name).to_string(),
208 });
209 }
210 }
211
212 let mut out = Vec::new();
213 push_record(&mut out, "sharpebench.document", self.document);
214 push_record(&mut out, "sharpebench.digest", digest_name(digest));
215
216 for (name, coverage) in self.fields {
217 if coverage.digest() != Some(digest) {
218 continue;
219 }
220 let Some((_, value)) = values.iter().find(|(n, _)| n == name) else {
221 return Err(PreimageError::MissingField {
222 field: (*name).to_string(),
223 });
224 };
225 match coverage {
226 Coverage::Covered { .. } => push_record(&mut out, name, value),
227 Coverage::Redacted { .. } => push_record(&mut out, name, REDACTED),
228 Coverage::Excluded { .. } => unreachable!("filtered by digest match"),
229 }
230 }
231 Ok(out)
232 }
233}
234
235fn push_record(out: &mut Vec<u8>, name: &str, value: &str) {
236 out.extend_from_slice(name.as_bytes());
237 out.push(0x1f);
238 out.extend_from_slice(value.as_bytes());
239 out.push(0x1e);
240}
241
242fn digest_name(d: DigestId) -> &'static str {
243 match d {
244 DigestId::AgentScore => "agent_score",
245 DigestId::FieldContext => "field_context",
246 DigestId::RunProvenance => "run_provenance",
247 }
248}
249
250const COVERED_SCORE: Coverage = Coverage::Covered {
251 digest: DigestId::AgentScore,
252};
253const COVERED_FIELD: Coverage = Coverage::Covered {
254 digest: DigestId::FieldContext,
255};
256
257pub const COMPOSITE_SCORE_INVENTORY: EvidenceInventory = EvidenceInventory {
265 document: "sharpebench_core::composite::CompositeScore",
266 fields: &[
267 ("agent_id", COVERED_SCORE),
268 ("deflated_sharpe", COVERED_SCORE),
269 ("psr", COVERED_SCORE),
270 ("passed_k", COVERED_SCORE),
271 ("process_ok", COVERED_SCORE),
272 ("bootstrap_p", COVERED_SCORE),
273 ("bootstrap_error", COVERED_SCORE),
274 ("deflation_error", COVERED_SCORE),
275 ("selection_error", COVERED_SCORE),
276 ("raw_mean_return", COVERED_SCORE),
277 ("rank_eligible", COVERED_SCORE),
278 ("composite", COVERED_SCORE),
279 ("alpha", COVERED_FIELD),
280 ("beta", COVERED_FIELD),
281 ("calibration_brier", COVERED_SCORE),
282 ("calibration_observations", COVERED_SCORE),
283 ("edge_half_life", COVERED_SCORE),
284 ("field_reality_check_p", COVERED_FIELD),
285 ("max_drawdown", COVERED_SCORE),
286 ("mandate_ok", COVERED_SCORE),
287 ("worst_run_drawdown", COVERED_SCORE),
288 ("turnover", COVERED_SCORE),
289 ("pareto_optimal", COVERED_FIELD),
290 ("step_down_significant", COVERED_FIELD),
291 ("confidence_weighted_return", COVERED_SCORE),
292 ("cost", COVERED_SCORE),
293 ("return_per_cost", COVERED_SCORE),
294 ("field_spa_p", COVERED_FIELD),
295 ("field_spa_consistent_p", COVERED_FIELD),
296 ("field_significance_benchmark", COVERED_FIELD),
297 ("field_crowdedness", COVERED_FIELD),
298 ("field_crowdedness_peers", COVERED_FIELD),
299 ("in_sample_trials", COVERED_SCORE),
300 ("effective_n_trials", COVERED_SCORE),
301 ("dsr_percentile", COVERED_FIELD),
302 ("selection_median_dsr", COVERED_SCORE),
303 ("selection_gap", COVERED_SCORE),
304 ("rank_ordinal", COVERED_FIELD),
305 ("rolling_min_sharpe", COVERED_SCORE),
306 ("rolling_frac_positive", COVERED_SCORE),
307 ("rolling_windows", COVERED_SCORE),
308 ("sortino", COVERED_SCORE),
309 ("downside_deviation", COVERED_SCORE),
310 ("dsr_per_cost", COVERED_SCORE),
311 ("process_floored", COVERED_SCORE),
312 ("realized_floored_return", COVERED_SCORE),
313 ("dsr_ci_low", COVERED_SCORE),
314 ("dsr_ci_high", COVERED_SCORE),
315 ("dsr_se", COVERED_SCORE),
316 ("tie_group", COVERED_FIELD),
317 ("dsr_tied", COVERED_FIELD),
318 ("trials_sr_std", COVERED_SCORE),
319 ("trials_sr_std_annualized", COVERED_SCORE),
320 (
321 "trials_sr_std_annualized_equivalent",
322 Coverage::Excluded {
323 reason: "presentation unit of trials_sr_std, which is covered by agent_score; \
324 a fixed conversion carries no evidence the covered value does not, \
325 and binding it would let a units edit break a digest over unchanged \
326 evidence",
327 },
328 ),
329 ("deflation_bar_per_period", COVERED_SCORE),
330 (
331 "deflation_bar_annualized_equivalent",
332 Coverage::Excluded {
333 reason: "presentation unit of deflation_bar_per_period, which is covered by \
334 agent_score; excluded for the reason given on \
335 trials_sr_std_annualized_equivalent",
336 },
337 ),
338 ("deflation_null_mean_per_period", COVERED_SCORE),
339 ("pooled_observations", COVERED_SCORE),
340 ("trials_sr_std_source", COVERED_SCORE),
341 ("trials_sr_std_most_influential_vote", COVERED_FIELD),
344 ("runs_submitted", COVERED_SCORE),
345 ("runs_scored", COVERED_SCORE),
346 ("process_score", COVERED_SCORE),
347 ("process_warnings", COVERED_SCORE),
348 ("econ_rationality_score", COVERED_SCORE),
349 ("econ_dominance_violations", COVERED_SCORE),
350 (
351 "role_contributions",
352 Coverage::Excluded {
353 reason: "a variable-length nested structure whose element type carries its own \
354 fields; binding it through this flat inventory would go stale silently \
355 the moment RoleContribution gains a field, which is the exact failure \
356 this inventory exists to prevent. It needs its own inventory before it \
357 can be bound",
358 },
359 ),
360 ("declared_mandate", COVERED_SCORE),
361 ("verdict_applied", COVERED_SCORE),
362 ("declared_passed_k", COVERED_SCORE),
363 ("declared_mandate_eligible", COVERED_FIELD),
364 ("declared_mandate_ordinal", COVERED_FIELD),
365 (
366 "certification",
367 Coverage::Excluded {
368 reason: "a nested record whose withheld list is variable-length and whose \
369 elements carry their own fields; binding it through this flat \
370 inventory would go stale silently the moment Certification or \
371 CertificationGap gains a field, the failure given on \
372 role_contributions. It is a second, labeled verdict filled only under \
373 an opt-in rank mode, and needs its own inventory before it can be \
374 bound",
375 },
376 ),
377 ],
378};
379
380#[derive(Clone, Debug, Serialize, PartialEq, Eq)]
390pub struct RunProvenance {
391 pub agent_id: String,
392 pub dataset_id: String,
393 pub dataset_content_hash: String,
395 pub dataset_canary: String,
397 pub window_id: String,
398 pub seed: u64,
399 pub cost_profile: String,
400 pub harness_version: String,
401}
402
403pub const RUN_PROVENANCE_INVENTORY: EvidenceInventory = EvidenceInventory {
405 document: "sharpebench_core::evidence_coverage::RunProvenance",
406 fields: &[
407 (
408 "agent_id",
409 Coverage::Covered {
410 digest: DigestId::RunProvenance,
411 },
412 ),
413 (
414 "dataset_id",
415 Coverage::Covered {
416 digest: DigestId::RunProvenance,
417 },
418 ),
419 (
420 "dataset_content_hash",
421 Coverage::Covered {
422 digest: DigestId::RunProvenance,
423 },
424 ),
425 (
426 "dataset_canary",
427 Coverage::Redacted {
428 digest: DigestId::RunProvenance,
429 reason: "the canary is a leak tripwire: publishing a preimage that contains it \
430 hands an agent the token whose appearance in training data is the \
431 evidence of contamination. Binding its presence is enough, and \
432 verification must not require holding it",
433 },
434 ),
435 (
436 "window_id",
437 Coverage::Covered {
438 digest: DigestId::RunProvenance,
439 },
440 ),
441 (
442 "seed",
443 Coverage::Covered {
444 digest: DigestId::RunProvenance,
445 },
446 ),
447 (
448 "cost_profile",
449 Coverage::Covered {
450 digest: DigestId::RunProvenance,
451 },
452 ),
453 (
454 "harness_version",
455 Coverage::Covered {
456 digest: DigestId::RunProvenance,
457 },
458 ),
459 ],
460};
461
462#[cfg(test)]
463mod tests {
464 use super::*;
465 use crate::composite::{
466 rank_declared, AgentSubmission, CompositeScore, DeclaredMandate, MandateDeclarations, Run,
467 ScoreConfig,
468 };
469 use crate::entrant_visibility::declared_struct_fields;
470 use crate::process::Trace;
471
472 fn field_names<T: Serialize>(v: &T) -> Vec<String> {
474 let json = serde_json::to_value(v).expect("value serializes");
475 json.as_object()
476 .expect("evidence documents serialize as objects")
477 .keys()
478 .cloned()
479 .collect()
480 }
481
482 fn probe_submission(id: &str, mean: f64) -> AgentSubmission {
483 let runs: Vec<Run> = (0..5)
484 .map(|_| Run {
485 returns: (0..60)
486 .map(|i| mean + 0.0005 * (i as f64 * 0.7).sin())
487 .collect(),
488 trace: Trace::default(),
489 confidences: Vec::new(),
490 outcomes: Vec::new(),
491 cost: 1.0,
492 })
493 .collect();
494 AgentSubmission {
495 agent_id: id.to_string(),
496 runs,
497 in_sample_trials: 0,
498 candidates: Vec::new(),
499 }
500 }
501
502 fn observed_composite_score_fields() -> Vec<String> {
512 let subs = vec![
513 probe_submission("declared-probe", 0.002),
514 probe_submission("undeclared-probe", 0.0018),
515 ];
516 let mut declarations = MandateDeclarations::new();
517 declarations.insert(
518 "declared-probe".to_string(),
519 DeclaredMandate::AbsoluteReturn,
520 );
521 let mut scored = rank_declared(&subs, &declarations, &ScoreConfig::default());
522 scored.extend(rank_declared(
523 &subs,
524 &declarations,
525 &ScoreConfig {
526 n_boot: 0,
527 ..ScoreConfig::default()
528 },
529 ));
530
531 let mut names: BTreeSet<String> = declared_struct_fields::<CompositeScore>()
532 .iter()
533 .map(|name| (*name).to_string())
534 .collect();
535 for s in &scored {
536 names.extend(field_names(s));
537 }
538 names.into_iter().collect()
539 }
540
541 #[test]
546 fn every_composite_score_field_is_declared_covered_or_excluded() {
547 let names = observed_composite_score_fields();
548 let audit = COMPOSITE_SCORE_INVENTORY.audit(&names);
549 assert!(
550 audit.is_complete(),
551 "CompositeScore and its coverage inventory have diverged.\n\
552 Fields present but undeclared: {:?}\n\
553 Fields declared but absent: {:?}\n\
554 Add each new field to COMPOSITE_SCORE_INVENTORY as Covered {{ digest }} \
555 or Excluded {{ reason }}.",
556 audit.undeclared,
557 audit.stale
558 );
559 }
560
561 #[test]
562 fn every_run_provenance_field_is_declared_covered_or_excluded() {
563 let p = RunProvenance {
564 agent_id: "a".to_string(),
565 dataset_id: "us-indices-1d".to_string(),
566 dataset_content_hash: "abc".to_string(),
567 dataset_canary: "canary-token".to_string(),
568 window_id: "w3".to_string(),
569 seed: 7,
570 cost_profile: "retail".to_string(),
571 harness_version: "0.11.0".to_string(),
572 };
573 let audit = RUN_PROVENANCE_INVENTORY.audit(&field_names(&p));
574 assert!(audit.is_complete(), "{audit:?}");
575 }
576
577 #[test]
578 fn a_new_undeclared_field_fails_the_audit() {
579 let mut names = observed_composite_score_fields();
582 names.push("newly_added_metric".to_string());
583 let audit = COMPOSITE_SCORE_INVENTORY.audit(&names);
584 assert!(!audit.is_complete());
585 assert_eq!(audit.undeclared, vec!["newly_added_metric".to_string()]);
586 assert!(audit.stale.is_empty());
587 }
588
589 #[test]
592 fn fields_a_skipped_none_hides_are_still_classified() {
593 let subs = vec![probe_submission("probe", 0.002)];
594 let serialized: BTreeSet<String> =
595 rank_declared(&subs, &MandateDeclarations::new(), &ScoreConfig::default())
596 .iter()
597 .flat_map(field_names)
598 .collect();
599 for hidden in ["selection_error", "certification"] {
600 assert!(!serialized.contains(hidden), "{hidden} was serialized");
601 assert!(observed_composite_score_fields().contains(&hidden.to_string()));
602 }
603 assert_eq!(
604 COMPOSITE_SCORE_INVENTORY.coverage("selection_error"),
605 Some(COVERED_SCORE),
606 "a diagnostic of the agent's own candidates, like bootstrap_error and \
607 deflation_error"
608 );
609 assert!(matches!(
610 COMPOSITE_SCORE_INVENTORY.coverage("certification"),
611 Some(Coverage::Excluded { .. })
612 ));
613
614 let values: Vec<(&str, &str)> = COMPOSITE_SCORE_INVENTORY
616 .fields
617 .iter()
618 .map(|(n, _)| (*n, "0"))
619 .collect();
620 let bytes = COMPOSITE_SCORE_INVENTORY
621 .preimage(DigestId::AgentScore, &values)
622 .unwrap();
623 let record = b"selection_error\x1f0\x1e";
624 assert!(bytes.windows(record.len()).any(|w| w == record));
625 }
626
627 #[test]
628 fn a_removed_field_is_reported_as_stale() {
629 let names: Vec<String> = observed_composite_score_fields()
630 .into_iter()
631 .filter(|n| n != "psr")
632 .collect();
633 let audit = COMPOSITE_SCORE_INVENTORY.audit(&names);
634 assert_eq!(audit.stale, vec!["psr".to_string()]);
635 }
636
637 #[test]
642 fn every_exclusion_carries_a_reason() {
643 for inventory in [COMPOSITE_SCORE_INVENTORY, RUN_PROVENANCE_INVENTORY] {
644 for (name, reason) in inventory.exclusions() {
645 assert!(
646 reason.len() > 30,
647 "{}::{name} is excluded without a usable reason",
648 inventory.document
649 );
650 }
651 }
652 }
653
654 #[test]
655 fn every_redaction_carries_a_reason() {
656 for inventory in [COMPOSITE_SCORE_INVENTORY, RUN_PROVENANCE_INVENTORY] {
657 for (name, coverage) in inventory.fields {
658 if let Coverage::Redacted { reason, .. } = coverage {
659 assert!(
660 reason.len() > 30,
661 "{}::{name} is redacted without a usable reason",
662 inventory.document
663 );
664 }
665 }
666 }
667 }
668
669 #[test]
670 fn the_two_digests_partition_the_covered_fields() {
671 let score = COMPOSITE_SCORE_INVENTORY.fields_for(DigestId::AgentScore);
672 let context = COMPOSITE_SCORE_INVENTORY.fields_for(DigestId::FieldContext);
673 let excluded = COMPOSITE_SCORE_INVENTORY.exclusions();
674 assert_eq!(
675 score.len() + context.len() + excluded.len(),
676 COMPOSITE_SCORE_INVENTORY.fields.len(),
677 "every field lands in exactly one of the three buckets"
678 );
679 for f in &score {
680 assert!(!context.contains(f), "{f} is claimed by both digests");
681 }
682 assert_eq!(excluded.len(), 4, "exactly four deliberate exclusions");
683 }
684
685 #[test]
686 fn no_field_is_declared_twice() {
687 for inventory in [COMPOSITE_SCORE_INVENTORY, RUN_PROVENANCE_INVENTORY] {
688 let names: BTreeSet<&str> = inventory.fields.iter().map(|(n, _)| *n).collect();
689 assert_eq!(
690 names.len(),
691 inventory.fields.len(),
692 "{} declares a field twice",
693 inventory.document
694 );
695 }
696 }
697
698 #[test]
699 fn coverage_lookup_answers_the_consumer_question() {
700 assert_eq!(
701 COMPOSITE_SCORE_INVENTORY.coverage("deflated_sharpe"),
702 Some(COVERED_SCORE)
703 );
704 assert_eq!(
705 COMPOSITE_SCORE_INVENTORY
706 .coverage("alpha")
707 .unwrap()
708 .digest(),
709 Some(DigestId::FieldContext)
710 );
711 assert!(!COMPOSITE_SCORE_INVENTORY
712 .coverage("role_contributions")
713 .unwrap()
714 .binds_value());
715 assert_eq!(COMPOSITE_SCORE_INVENTORY.coverage("not_a_field"), None);
716 }
717
718 fn provenance_values() -> Vec<(&'static str, &'static str)> {
723 vec![
724 ("agent_id", "donchian-20-10"),
725 ("dataset_id", "us-indices-1d"),
726 ("dataset_content_hash", "9f86d081884c7d65"),
727 ("dataset_canary", "SUPER-SECRET-CANARY-42"),
728 ("window_id", "w3"),
729 ("seed", "7"),
730 ("cost_profile", "retail"),
731 ("harness_version", "0.11.0"),
732 ]
733 }
734
735 #[test]
736 fn a_secret_never_reaches_the_preimage() {
737 let bytes = RUN_PROVENANCE_INVENTORY
738 .preimage(DigestId::RunProvenance, &provenance_values())
739 .expect("complete value set");
740 let text = String::from_utf8(bytes).unwrap();
741 assert!(
742 !text.contains("SUPER-SECRET-CANARY-42"),
743 "the canary leaked into the bytes that get hashed"
744 );
745 assert!(
746 text.contains(REDACTED),
747 "the field's presence must still be bound"
748 );
749 assert!(text.contains("dataset_content_hash"));
750 }
751
752 #[test]
753 fn changing_a_secret_does_not_change_the_preimage() {
754 let base = RUN_PROVENANCE_INVENTORY
757 .preimage(DigestId::RunProvenance, &provenance_values())
758 .unwrap();
759 let mut rotated = provenance_values();
760 rotated[3] = ("dataset_canary", "A-DIFFERENT-CANARY");
761 let after = RUN_PROVENANCE_INVENTORY
762 .preimage(DigestId::RunProvenance, &rotated)
763 .unwrap();
764 assert_eq!(base, after);
765 }
766
767 #[test]
768 fn changing_a_covered_value_changes_the_preimage() {
769 let base = RUN_PROVENANCE_INVENTORY
770 .preimage(DigestId::RunProvenance, &provenance_values())
771 .unwrap();
772 let mut tampered = provenance_values();
773 tampered[2] = ("dataset_content_hash", "0000000000000000");
774 let after = RUN_PROVENANCE_INVENTORY
775 .preimage(DigestId::RunProvenance, &tampered)
776 .unwrap();
777 assert_ne!(base, after);
778 }
779
780 #[test]
781 fn the_preimage_is_deterministic_and_order_independent() {
782 let mut shuffled = provenance_values();
783 shuffled.reverse();
784 assert_eq!(
785 RUN_PROVENANCE_INVENTORY
786 .preimage(DigestId::RunProvenance, &provenance_values())
787 .unwrap(),
788 RUN_PROVENANCE_INVENTORY
789 .preimage(DigestId::RunProvenance, &shuffled)
790 .unwrap(),
791 "inventory order fixes the layout, not caller order"
792 );
793 }
794
795 #[test]
796 fn a_missing_bound_field_is_refused() {
797 let mut incomplete = provenance_values();
798 incomplete.retain(|(n, _)| *n != "seed");
799 assert_eq!(
800 RUN_PROVENANCE_INVENTORY.preimage(DigestId::RunProvenance, &incomplete),
801 Err(PreimageError::MissingField {
802 field: "seed".to_string()
803 })
804 );
805 }
806
807 #[test]
808 fn an_undeclared_or_duplicated_value_is_refused() {
809 let mut extra = provenance_values();
810 extra.push(("newly_added_metric", "1.0"));
811 assert_eq!(
812 RUN_PROVENANCE_INVENTORY.preimage(DigestId::RunProvenance, &extra),
813 Err(PreimageError::UndeclaredField {
814 field: "newly_added_metric".to_string()
815 })
816 );
817
818 let mut dup = provenance_values();
819 dup.push(("seed", "8"));
820 assert_eq!(
821 RUN_PROVENANCE_INVENTORY.preimage(DigestId::RunProvenance, &dup),
822 Err(PreimageError::DuplicateField {
823 field: "seed".to_string()
824 })
825 );
826 }
827
828 #[test]
829 fn an_excluded_field_never_appears_under_any_digest() {
830 let values: Vec<(&str, &str)> = COMPOSITE_SCORE_INVENTORY
831 .fields
832 .iter()
833 .map(|(n, _)| (*n, "0"))
834 .collect();
835 for digest in [DigestId::AgentScore, DigestId::FieldContext] {
836 let bytes = COMPOSITE_SCORE_INVENTORY.preimage(digest, &values).unwrap();
837 let text = String::from_utf8(bytes).unwrap();
838 for (name, _) in COMPOSITE_SCORE_INVENTORY.exclusions() {
839 assert!(
840 !text.contains(name),
841 "{name} is excluded but reached the {digest:?} preimage"
842 );
843 }
844 }
845 }
846
847 #[test]
848 fn the_two_digests_produce_different_preimages_over_the_same_values() {
849 let values: Vec<(&str, &str)> = COMPOSITE_SCORE_INVENTORY
850 .fields
851 .iter()
852 .map(|(n, _)| (*n, "0"))
853 .collect();
854 assert_ne!(
855 COMPOSITE_SCORE_INVENTORY
856 .preimage(DigestId::AgentScore, &values)
857 .unwrap(),
858 COMPOSITE_SCORE_INVENTORY
859 .preimage(DigestId::FieldContext, &values)
860 .unwrap(),
861 "the digest tag and the field set both differ"
862 );
863 }
864}