-
Notifications
You must be signed in to change notification settings - Fork 1
fix(collector): accept closed same-hour reference fragments #175
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -649,8 +649,9 @@ pub(crate) fn with_validated_research_segments<T>( | |
| .context("reference archive byte total overflow")?; | ||
| if let Some(previous) = references.last() { | ||
| let previous = segment_hour(&previous.identity)?; | ||
| if segment_hour(&reference.identity)? != previous + TimeDelta::hours(1) { | ||
| bail!("reference segments must be consecutive UTC hours"); | ||
| let current = segment_hour(&reference.identity)?; | ||
| if current < previous || current > previous + TimeDelta::hours(1) { | ||
| bail!("reference segments must be same or consecutive UTC hours"); | ||
|
Comment on lines
+653
to
+654
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When an identical same-hour triplet is supplied more than once and that fragment contains no trade rows (for example, a metadata/settlement-only fragment alongside a separate trade fragment), this relaxed check accepts it and the later duplicate guard cannot detect it because it only tracks Useful? React with 👍 / 👎. |
||
| } | ||
| } | ||
| references.push(reference); | ||
|
|
@@ -970,6 +971,62 @@ mod tests { | |
| rejects(&config, "consecutive UTC hours"); | ||
| } | ||
|
|
||
| #[test] | ||
| fn validates_closed_reference_fragments_from_the_same_hour() { | ||
| let temp = tempfile::tempdir().unwrap(); | ||
| let root = fs::canonicalize(temp.path()).unwrap(); | ||
| let market = triplet(&root, "crypto_expiry", &market_rows()); | ||
| let first = triplet( | ||
| &root, | ||
| "crypto_expiry_reference", | ||
| &[row(0, metadata("market_metadata")), row(1, trade())], | ||
| ); | ||
| let second = triplet( | ||
| &root, | ||
| "crypto_expiry_reference", | ||
| &[ | ||
| row(0, metadata("market_metadata")), | ||
| row(1, metadata("market_settlement")), | ||
| ], | ||
| ); | ||
| let config = ResearchSegmentValidationConfig { | ||
| market, | ||
| references: vec![first, second], | ||
| }; | ||
|
|
||
| let report = validate_research_segments(&config).unwrap(); | ||
|
|
||
| assert_eq!(report.references.len(), 2); | ||
| assert_eq!(report.references[0].hour, "05"); | ||
| assert_eq!(report.references[1].hour, "05"); | ||
| } | ||
|
|
||
| #[test] | ||
| fn rejects_skipped_reference_hours() { | ||
| let temp = tempfile::tempdir().unwrap(); | ||
| let root = fs::canonicalize(temp.path()).unwrap(); | ||
| let market = triplet(&root, "crypto_expiry", &market_rows()); | ||
| let first = triplet( | ||
| &root, | ||
| "crypto_expiry_reference", | ||
| &[row(0, metadata("market_metadata")), row(1, trade())], | ||
| ); | ||
| let mut second = vec![ | ||
| row(0, metadata("market_metadata")), | ||
| row(1, metadata("market_settlement")), | ||
| ]; | ||
| for record in &mut second { | ||
| record["recorded_at"] = json!("2026-07-17T07:01:00Z"); | ||
| } | ||
| let second = triplet(&root, "crypto_expiry_reference", &second); | ||
| let config = ResearchSegmentValidationConfig { | ||
| market, | ||
| references: vec![first, second], | ||
| }; | ||
|
|
||
| rejects(&config, "same or consecutive UTC hours"); | ||
| } | ||
|
|
||
| #[test] | ||
| fn accepts_reference_segments_with_event_local_trade_completion_proof() { | ||
| let mut reference = reference_rows(true); | ||
|
|
@@ -1029,7 +1086,7 @@ mod tests { | |
| row(2, metadata("market_settlement")), | ||
| ]; | ||
| for record in &mut second { | ||
| record["recorded_at"] = json!("2026-07-17T06:01:00Z"); | ||
| record["recorded_at"] = json!("2026-07-17T05:02:00Z"); | ||
| } | ||
| let second = triplet(&root, "crypto_expiry_reference", &second); | ||
| let config = ResearchSegmentValidationConfig { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When two fragments are in the same UTC hour, this condition accepts either order because
current == previousregardless of theirstart_recorded_at/end_recorded_atvalues. A fragment recorded at 05:30 followed by one recorded at 05:10 therefore passes validation, after whichcombine_referencesconcatenates them in that caller-supplied order and downstream selection retains the first metadata occurrence, making evidence provenance and output depend on an invalid ordering. Compare the authenticated timestamps for equal-hour fragments so backward inputs remain fail-closed.Useful? React with 👍 / 👎.