a3s_code_core/research/
mod.rs1mod citation;
9mod claim;
10mod error;
11mod event;
12mod evidence;
13mod evidence_graph;
14mod protocol;
15mod provenance;
16mod reproducibility;
17mod review;
18mod review_batch;
19mod run;
20mod workflow;
21
22pub use citation::{ResearchCitationV1, RESEARCH_CITATION_SCHEMA_V1};
23pub use claim::{ResearchClaimStatusV1, ResearchClaimV1, RESEARCH_CLAIM_SCHEMA_V1};
24pub use error::ResearchContractError;
25pub use event::{ResearchEventV1, RESEARCH_EVENT_SCHEMA_V1, RESEARCH_MAX_EVENT_TYPE_BYTES};
26pub use evidence::{
27 ResearchEvidenceFactKindV1, ResearchEvidenceFactV1, RESEARCH_EVIDENCE_FACT_SCHEMA_V1,
28 RESEARCH_MAX_FACT_METADATA, RESEARCH_MAX_METADATA_VALUE_BYTES,
29};
30pub use evidence_graph::{
31 ResearchEvidenceCompletenessV1, ResearchEvidenceGraphV1, RESEARCH_EVIDENCE_GRAPH_SCHEMA_V1,
32 RESEARCH_MAX_EVIDENCE_GRAPH_CITATIONS, RESEARCH_MAX_EVIDENCE_GRAPH_CLAIMS,
33};
34pub use protocol::{
35 ResearchProtocolError, ResearchWireEnvelopeV1, ResearchWireKindDescriptorV1,
36 ResearchWireKindV1, ResearchWireTypeV1, RESEARCH_PROTOCOL_SCHEMA_V1,
37 RESEARCH_PROTOCOL_VERSION_V1, RESEARCH_WIRE_KIND_DESCRIPTORS_V1,
38};
39pub use provenance::{
40 ResearchArtifactKindV1, ResearchProvenanceReceiptV1, RESEARCH_ARTIFACT_KINDS,
41 RESEARCH_PROVENANCE_RECEIPT_SCHEMA_V1,
42};
43pub use reproducibility::{
44 ResearchReproducibilityManifestV1, RESEARCH_REPRODUCIBILITY_MANIFEST_SCHEMA_V1,
45};
46pub use review::{
47 ResearchReviewCategoryV1, ResearchReviewFindingV1, ResearchReviewLocationV1,
48 ResearchReviewSeverityV1, ResearchReviewStatusV1, RESEARCH_REVIEW_FINDING_SCHEMA_V1,
49};
50pub use review_batch::{
51 ResearchReviewBatchV1, RESEARCH_MAX_REVIEW_FINDINGS, RESEARCH_REVIEW_BATCH_SCHEMA_V1,
52};
53pub use run::{
54 ResearchReproducibilityV1, ResearchRunStatusV1, ResearchRunV1, RESEARCH_RUN_SCHEMA_V1,
55};
56pub use workflow::{
57 ResearchRerunLineageV1, ResearchWorkflowPlanV1, ResearchWorkflowStepV1,
58 RESEARCH_MAX_WORKFLOW_STEPS, RESEARCH_RERUN_LINEAGE_SCHEMA_V1,
59 RESEARCH_WORKFLOW_PLAN_SCHEMA_V1, RESEARCH_WORKFLOW_STEP_SCHEMA_V1,
60};
61
62pub use protocol::RESEARCH_PROTOCOL_MAX_MESSAGE_BYTES;
64pub(crate) const RESEARCH_MAX_ID_BYTES: usize = 256;
65pub(crate) const RESEARCH_MAX_TEXT_BYTES: usize = 16 * 1024;
66pub(crate) const RESEARCH_MAX_DIGESTS: usize = 512;
67
68pub(crate) fn decode_json_slice<T>(bytes: &[u8]) -> Result<T, ResearchContractError>
69where
70 T: serde::de::DeserializeOwned,
71{
72 if bytes.len() > RESEARCH_PROTOCOL_MAX_MESSAGE_BYTES {
73 return Err(ResearchContractError::Encoding);
74 }
75 serde_json::from_slice(bytes)
76 .map_err(|error| ResearchContractError::Serialization(error.to_string()))
77}
78
79pub(crate) fn encode_json<T: serde::Serialize + ?Sized>(
80 value: &T,
81) -> Result<Vec<u8>, ResearchContractError> {
82 let bytes = serde_json::to_vec(value)
83 .map_err(|error| ResearchContractError::Serialization(error.to_string()))?;
84 if bytes.len() > RESEARCH_PROTOCOL_MAX_MESSAGE_BYTES {
85 return Err(ResearchContractError::Encoding);
86 }
87 Ok(bytes)
88}
89
90pub(crate) fn validate_id(field: &'static str, value: &str) -> Result<(), ResearchContractError> {
91 if value.is_empty()
92 || value.len() > RESEARCH_MAX_ID_BYTES
93 || value.contains('\0')
94 || value.contains(['\r', '\n'])
95 {
96 return Err(ResearchContractError::InvalidField(field));
97 }
98 Ok(())
99}
100
101pub(crate) fn validate_text(
102 field: &'static str,
103 value: &str,
104 max_bytes: usize,
105) -> Result<(), ResearchContractError> {
106 if value.is_empty()
107 || value.len() > max_bytes
108 || value.contains('\0')
109 || value.contains(['\r', '\n'])
110 {
111 return Err(ResearchContractError::InvalidField(field));
112 }
113 Ok(())
114}
115
116pub(crate) fn validate_digest_field(
117 field: &'static str,
118 value: &str,
119) -> Result<(), ResearchContractError> {
120 crate::evaluation::validate_digest(value)
121 .map_err(|_| ResearchContractError::InvalidDigest(field))
122}
123
124pub(crate) fn digest<T: serde::Serialize>(
125 domain: &'static str,
126 value: &T,
127) -> Result<String, ResearchContractError> {
128 crate::evaluation::digest_json(domain, value)
129 .map_err(|error| ResearchContractError::Serialization(error.to_string()))
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135
136 #[test]
137 fn bounded_identifiers_reject_all_ascii_line_endings() {
138 for value in ["reviewer\n", "reviewer\r", "reviewer\r\n", "reviewer\nnext"] {
139 assert_eq!(
140 validate_id("id", value),
141 Err(ResearchContractError::InvalidField("id"))
142 );
143 }
144 }
145
146 #[test]
147 fn bounded_text_rejects_all_ascii_line_endings() {
148 for value in ["finding\n", "finding\r", "finding\r\n", "finding\nnext"] {
149 assert_eq!(
150 validate_text("text", value, 128),
151 Err(ResearchContractError::InvalidField("text"))
152 );
153 }
154 }
155}