1use std::collections::BTreeMap;
17use std::fmt;
18use std::io::{self, Read, Write};
19use std::time::Duration;
20
21use serde::{Deserialize, Serialize};
22use serde_json::Value;
23use serde_json::value::RawValue;
24
25use crate::types::{
26 LeanWorkerCapabilityMetadata, LeanWorkerDeclarationFilter, LeanWorkerDeclarationInspectionRequest,
27 LeanWorkerDeclarationInspectionResult, LeanWorkerDeclarationRow, LeanWorkerDeclarationSearch,
28 LeanWorkerDeclarationSearchResult, LeanWorkerDeclarationType, LeanWorkerDeclarationVerificationRequest,
29 LeanWorkerDeclarationVerificationResult, LeanWorkerDoctorReport, LeanWorkerElabOptions, LeanWorkerElabResult,
30 LeanWorkerImportStats, LeanWorkerKernelResult, LeanWorkerMetaResult, LeanWorkerMetaTransparency,
31 LeanWorkerModuleQuery, LeanWorkerModuleQueryBatchOutcome, LeanWorkerModuleQueryOutcome,
32 LeanWorkerModuleQuerySelector, LeanWorkerOutputBudgets, LeanWorkerProofAttemptRequest,
33 LeanWorkerProofAttemptResult, LeanWorkerRendered, LeanWorkerSessionImportProfile,
34};
35
36pub const PROTOCOL_VERSION: u16 = 10;
39
40pub const MAX_FRAME_BYTES: u32 = 1024 * 1024;
50
51pub const MIN_FRAME_BYTES: u32 = 64 * 1024;
54
55pub const MAX_FRAME_BYTES_HARD_CAP: u32 = 256 * 1024 * 1024;
58
59#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
61#[non_exhaustive]
62pub struct Frame {
63 pub version: u16,
65 pub message: Message,
67}
68
69impl Frame {
70 #[must_use]
72 pub fn new(message: Message) -> Self {
73 Self {
74 version: PROTOCOL_VERSION,
75 message,
76 }
77 }
78}
79
80#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
82#[serde(tag = "type", content = "body", rename_all = "snake_case")]
83#[non_exhaustive]
84pub enum Message {
85 Handshake {
88 worker_version: String,
90 protocol_version: u16,
92 },
93 ConfigureFrameLimit {
100 max_frame_bytes: u32,
102 },
103 Request(Request),
105 Response(Response),
107 Diagnostic(Diagnostic),
109 ProgressTick(ProgressTick),
111 DataRow(DataRow),
113 FatalExit(FatalExit),
116}
117
118#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
120#[serde(tag = "op", rename_all = "snake_case")]
121#[non_exhaustive]
122pub enum Request {
123 Health,
124 LoadFixtureCapability {
125 manifest_path: String,
126 },
127 CallFixtureMul {
128 manifest_path: String,
129 lhs: u64,
130 rhs: u64,
131 },
132 TriggerLeanPanic {
133 manifest_path: String,
134 },
135 OpenHostSession {
136 project_root: String,
137 mode: HostSessionMode,
138 imports: Vec<String>,
139 import_profile: LeanWorkerSessionImportProfile,
140 },
141 Elaborate {
142 source: String,
143 options: LeanWorkerElabOptions,
144 },
145 KernelCheck {
146 source: String,
147 options: LeanWorkerElabOptions,
148 progress: bool,
149 },
150 DeclarationKinds {
151 names: Vec<String>,
152 progress: bool,
153 },
154 DeclarationNames {
155 names: Vec<String>,
156 progress: bool,
157 },
158 RunDataStream {
159 export: String,
160 request_json: String,
161 progress: bool,
162 },
163 CapabilityMetadata {
164 export: String,
165 request_json: String,
166 },
167 CapabilityDoctor {
168 export: String,
169 request_json: String,
170 },
171 JsonCommand {
172 export: String,
173 request_json: String,
174 },
175 InferType {
176 source: String,
177 options: LeanWorkerElabOptions,
178 },
179 Whnf {
180 source: String,
181 options: LeanWorkerElabOptions,
182 },
183 IsDefEq {
184 lhs: String,
185 rhs: String,
186 transparency: LeanWorkerMetaTransparency,
187 options: LeanWorkerElabOptions,
188 },
189 Describe {
190 name: String,
191 },
192 SearchDeclarations {
193 search: LeanWorkerDeclarationSearch,
194 },
195 DeclarationType {
196 name: String,
197 max_bytes: usize,
198 },
199 InspectDeclaration {
200 request: LeanWorkerDeclarationInspectionRequest,
201 },
202 AttemptProof {
203 request: LeanWorkerProofAttemptRequest,
204 options: LeanWorkerElabOptions,
205 progress: bool,
206 },
207 VerifyDeclaration {
208 request: LeanWorkerDeclarationVerificationRequest,
209 options: LeanWorkerElabOptions,
210 progress: bool,
211 },
212 ListDeclarationsStrings {
213 filter: LeanWorkerDeclarationFilter,
214 progress: bool,
215 },
216 DescribeBulk {
217 names: Vec<String>,
218 progress: bool,
219 },
220 ProcessModuleQuery {
221 source: String,
222 query: LeanWorkerModuleQuery,
223 options: LeanWorkerElabOptions,
224 },
225 ProcessModuleQueryBatch {
226 source: String,
227 selectors: Vec<LeanWorkerModuleQuerySelector>,
228 budgets: LeanWorkerOutputBudgets,
229 options: LeanWorkerElabOptions,
230 },
231 ClearModuleSnapshotCache,
232 EmitTestRows {
235 streams: Vec<String>,
236 },
237 EmitTestRowsThenExit,
238 EmitTestRowsThenPanic,
239 Terminate,
240}
241
242#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
244#[serde(tag = "kind", rename_all = "snake_case")]
245#[non_exhaustive]
246pub enum HostSessionMode {
247 Capability {
249 package: String,
250 lib_name: String,
251 manifest_path: Option<String>,
252 },
253 ShimsOnly,
255}
256
257#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
259#[serde(tag = "status", rename_all = "snake_case")]
260#[non_exhaustive]
261pub enum Response {
262 HealthOk,
263 CapabilityLoaded,
264 U64 {
265 value: u64,
266 },
267 HostSessionOpened {
268 import_stats: LeanWorkerImportStats,
269 },
270 Elaboration {
271 outcome: LeanWorkerElabResult,
272 },
273 KernelCheck {
274 outcome: LeanWorkerKernelResult,
275 },
276 Strings {
277 values: Vec<String>,
278 },
279 StreamComplete {
280 summary: StreamSummary,
281 },
282 StreamExportFailed {
283 status_byte: u8,
284 },
285 StreamCallbackFailed {
286 status_byte: u8,
287 description: String,
288 },
289 StreamRowMalformed {
290 message: String,
291 },
292 CapabilityMetadata {
293 metadata: LeanWorkerCapabilityMetadata,
294 },
295 CapabilityDoctor {
296 report: LeanWorkerDoctorReport,
297 },
298 CapabilityMetadataMalformed {
299 message: String,
300 },
301 CapabilityDoctorMalformed {
302 message: String,
303 },
304 JsonCommand {
305 response_json: String,
306 },
307 MetaExpr {
308 result: LeanWorkerMetaResult<LeanWorkerRendered>,
309 },
310 MetaBool {
311 result: LeanWorkerMetaResult<bool>,
312 },
313 Declaration {
314 row: Option<LeanWorkerDeclarationRow>,
315 },
316 DeclarationSearch {
317 result: LeanWorkerDeclarationSearchResult,
318 },
319 DeclarationType {
320 row: Option<LeanWorkerDeclarationType>,
321 },
322 DeclarationInspection {
323 result: LeanWorkerDeclarationInspectionResult,
324 },
325 ProofAttempt {
326 result: LeanWorkerProofAttemptResult,
327 },
328 DeclarationVerification {
329 result: LeanWorkerDeclarationVerificationResult,
330 },
331 DeclarationBulk {
332 rows: Vec<LeanWorkerDeclarationRow>,
333 },
334 ProcessModuleQuery {
335 outcome: LeanWorkerModuleQueryOutcome,
336 },
337 ProcessModuleQueryBatch {
338 outcome: LeanWorkerModuleQueryBatchOutcome,
339 },
340 ModuleSnapshotCacheCleared {
341 result: crate::types::LeanWorkerModuleSnapshotCacheClearResult,
342 },
343 RowsComplete {
344 count: u64,
345 },
346 Terminating,
347 Error {
348 code: String,
349 message: String,
350 },
351}
352
353#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
355#[non_exhaustive]
356pub struct Diagnostic {
357 pub code: String,
359 pub message: String,
361}
362
363impl Diagnostic {
364 #[must_use]
366 pub fn new(code: impl Into<String>, message: impl Into<String>) -> Self {
367 Self {
368 code: code.into(),
369 message: message.into(),
370 }
371 }
372}
373
374#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
377#[non_exhaustive]
378pub struct ProgressTick {
379 pub phase: String,
381 pub current: u64,
383 pub total: Option<u64>,
385}
386
387impl ProgressTick {
388 #[must_use]
390 pub fn new(phase: impl Into<String>, current: u64, total: Option<u64>) -> Self {
391 Self {
392 phase: phase.into(),
393 current,
394 total,
395 }
396 }
397}
398
399#[derive(Clone, Debug, Deserialize, Serialize)]
406pub struct DataRow {
407 pub stream: String,
409 pub sequence: u64,
411 pub payload: Box<RawValue>,
413}
414
415impl PartialEq for DataRow {
416 fn eq(&self, other: &Self) -> bool {
417 self.stream == other.stream && self.sequence == other.sequence && self.payload.get() == other.payload.get()
418 }
419}
420
421impl Eq for DataRow {}
422
423#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
425#[non_exhaustive]
426pub struct StreamSummary {
427 pub total_rows: u64,
429 pub per_stream_counts: BTreeMap<String, u64>,
431 pub elapsed_micros: u64,
433 pub metadata: Option<Value>,
435}
436
437impl StreamSummary {
438 #[must_use]
441 pub fn new(
442 total_rows: u64,
443 per_stream_counts: BTreeMap<String, u64>,
444 elapsed: Duration,
445 metadata: Option<Value>,
446 ) -> Self {
447 Self {
448 total_rows,
449 per_stream_counts,
450 elapsed_micros: elapsed.as_micros().try_into().unwrap_or(u64::MAX),
451 metadata,
452 }
453 }
454}
455
456#[derive(Debug, Default)]
459#[non_exhaustive]
460pub struct DataRowEmitter {
461 sequences: BTreeMap<String, u64>,
462 count: u64,
463}
464
465impl DataRowEmitter {
466 pub fn next(&mut self, stream: impl Into<String>, payload: Box<RawValue>) -> DataRow {
469 let stream = stream.into();
470 let sequence = self.sequences.entry(stream.clone()).or_insert(0);
471 let row = DataRow {
472 stream,
473 sequence: *sequence,
474 payload,
475 };
476 *sequence = sequence.saturating_add(1);
477 self.count = self.count.saturating_add(1);
478 row
479 }
480
481 #[cfg(test)]
482 fn emit(
483 &mut self,
484 writer: &mut impl Write,
485 stream: impl Into<String>,
486 payload: &Value,
487 ) -> Result<(), ProtocolError> {
488 let row = self.next(stream, serde_json::value::to_raw_value(payload)?);
489 write_frame(writer, Message::DataRow(row), MAX_FRAME_BYTES)
490 }
491
492 #[must_use]
494 pub fn count(&self) -> u64 {
495 self.count
496 }
497
498 #[must_use]
500 pub fn per_stream_counts(&self) -> BTreeMap<String, u64> {
501 self.sequences.clone()
502 }
503}
504
505#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
508#[non_exhaustive]
509pub struct FatalExit {
510 pub status: String,
512 pub stderr: String,
514}
515
516impl FatalExit {
517 #[must_use]
519 pub fn new(status: impl Into<String>, stderr: impl Into<String>) -> Self {
520 Self {
521 status: status.into(),
522 stderr: stderr.into(),
523 }
524 }
525}
526
527#[derive(Debug)]
529#[non_exhaustive]
530pub enum ProtocolError {
531 Io(io::Error),
533 Json(serde_json::Error),
535 FrameTooLarge {
537 len: u32,
539 max: u32,
541 },
542 VersionMismatch {
544 expected: u16,
546 actual: u16,
548 },
549}
550
551impl ProtocolError {
552 #[must_use]
556 pub fn is_eof(&self) -> bool {
557 matches!(self, Self::Io(err) if err.kind() == io::ErrorKind::UnexpectedEof)
558 }
559}
560
561impl fmt::Display for ProtocolError {
562 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
563 match self {
564 Self::Io(err) => write!(f, "worker protocol I/O failed: {err}"),
565 Self::Json(err) => write!(f, "worker protocol JSON decode failed: {err}"),
566 Self::FrameTooLarge { len, max } => {
567 write!(f, "worker protocol frame too large: {len} bytes exceeds {max}")
568 }
569 Self::VersionMismatch { expected, actual } => {
570 write!(
571 f,
572 "worker protocol version mismatch: expected {expected}, received {actual}"
573 )
574 }
575 }
576 }
577}
578
579impl std::error::Error for ProtocolError {}
580
581impl From<io::Error> for ProtocolError {
582 fn from(value: io::Error) -> Self {
583 Self::Io(value)
584 }
585}
586
587impl From<serde_json::Error> for ProtocolError {
588 fn from(value: serde_json::Error) -> Self {
589 Self::Json(value)
590 }
591}
592
593pub fn write_frame(writer: &mut impl Write, message: Message, max_frame_bytes: u32) -> Result<(), ProtocolError> {
606 let bytes = serde_json::to_vec(&Frame::new(message))?;
607 let len = u32::try_from(bytes.len()).map_err(|_| ProtocolError::FrameTooLarge {
608 len: u32::MAX,
609 max: max_frame_bytes,
610 })?;
611 if len > max_frame_bytes {
612 return Err(ProtocolError::FrameTooLarge {
613 len,
614 max: max_frame_bytes,
615 });
616 }
617 writer.write_all(&len.to_be_bytes())?;
618 writer.write_all(&bytes)?;
619 writer.flush()?;
620 Ok(())
621}
622
623pub fn read_frame(reader: &mut impl Read, max_frame_bytes: u32) -> Result<Frame, ProtocolError> {
636 let mut len_bytes = [0_u8; 4];
637 reader.read_exact(&mut len_bytes)?;
638 let len = u32::from_be_bytes(len_bytes);
639 if len > max_frame_bytes {
640 return Err(ProtocolError::FrameTooLarge {
641 len,
642 max: max_frame_bytes,
643 });
644 }
645 let mut bytes = vec![0_u8; len as usize];
646 reader.read_exact(&mut bytes)?;
647 let frame: Frame = serde_json::from_slice(&bytes)?;
648 if frame.version != PROTOCOL_VERSION {
649 return Err(ProtocolError::VersionMismatch {
650 expected: PROTOCOL_VERSION,
651 actual: frame.version,
652 });
653 }
654 Ok(frame)
655}
656
657#[cfg(test)]
658mod tests {
659 #![allow(clippy::expect_used, clippy::panic)]
660
661 use std::io::Cursor;
662
663 use serde_json::json;
664 use serde_json::value::RawValue;
665
666 use super::{
667 DataRow, DataRowEmitter, MAX_FRAME_BYTES, MAX_FRAME_BYTES_HARD_CAP, MIN_FRAME_BYTES, Message, ProtocolError,
668 Request, Response, read_frame, write_frame,
669 };
670 use crate::types::{
671 LeanWorkerDeclarationFilter, LeanWorkerDeclarationFlags, LeanWorkerDeclarationInspection,
672 LeanWorkerDeclarationInspectionFields, LeanWorkerDeclarationInspectionRequest,
673 LeanWorkerDeclarationInspectionResult, LeanWorkerDeclarationNameMatch, LeanWorkerDeclarationProofSearchFacts,
674 LeanWorkerDeclarationSearch, LeanWorkerDeclarationSearchBias, LeanWorkerDeclarationSearchFacts,
675 LeanWorkerDeclarationSearchPruning, LeanWorkerDeclarationSearchResult, LeanWorkerDeclarationSearchRow,
676 LeanWorkerDeclarationSearchScope, LeanWorkerDeclarationSearchTimings, LeanWorkerDeclarationTargetInfo,
677 LeanWorkerDeclarationVerificationFacts, LeanWorkerDeclarationVerificationRequest,
678 LeanWorkerDeclarationVerificationResult, LeanWorkerDeclarationVerificationStatus,
679 LeanWorkerDeclarationVerificationTarget, LeanWorkerDerivedWorkFacts, LeanWorkerElabFailure,
680 LeanWorkerElabOptions, LeanWorkerModuleCacheStatus, LeanWorkerModuleQuery, LeanWorkerModuleQueryBatchEnvelope,
681 LeanWorkerModuleQueryBatchItem, LeanWorkerModuleQueryBatchOutcome, LeanWorkerModuleQueryBatchResult,
682 LeanWorkerModuleQueryCacheFacts, LeanWorkerModuleQueryOutcome, LeanWorkerModuleQueryResult,
683 LeanWorkerModuleQuerySelector, LeanWorkerModuleQueryTimings, LeanWorkerModuleSourceSpan,
684 LeanWorkerOutputBudgets, LeanWorkerProofAttemptEnvelope, LeanWorkerProofAttemptRequest,
685 LeanWorkerProofAttemptResult, LeanWorkerProofAttemptRow, LeanWorkerProofAttemptStatus,
686 LeanWorkerProofCandidate, LeanWorkerProofEditTarget, LeanWorkerProofPositionSelector,
687 LeanWorkerProofPositionSummary, LeanWorkerProofStateResult, LeanWorkerRenderedInfo, LeanWorkerRendering,
688 LeanWorkerResourceExhaustedFacts, LeanWorkerSorryPolicy, LeanWorkerSourceRange, LeanWorkerTypeAtResult,
689 };
690
691 fn raw_json(value: &serde_json::Value) -> Box<RawValue> {
692 serde_json::value::to_raw_value(value).expect("test JSON converts to raw value")
693 }
694
695 fn assert_frame_round_trips(message: &Message) {
696 let mut bytes = Vec::new();
697 write_frame(&mut bytes, message.clone(), MAX_FRAME_BYTES).expect("frame writes");
698 let frame = read_frame(&mut Cursor::new(bytes), MAX_FRAME_BYTES).expect("frame reads");
699 assert_eq!(&frame.message, message);
700 }
701
702 fn declaration_target_info_fixture(declaration_name: &str) -> LeanWorkerDeclarationTargetInfo {
703 let span = LeanWorkerModuleSourceSpan {
704 start_line: 1,
705 start_column: 1,
706 end_line: 1,
707 end_column: 10,
708 };
709 let short_name = declaration_name.rsplit('.').next().unwrap_or(declaration_name);
710 LeanWorkerDeclarationTargetInfo {
711 short_name: short_name.to_owned(),
712 declaration_name: declaration_name.to_owned(),
713 namespace_name: declaration_name
714 .strip_suffix(&format!(".{short_name}"))
715 .unwrap_or("")
716 .to_owned(),
717 declaration_kind: "theorem".to_owned(),
718 declaration_span: span.clone(),
719 name_span: span.clone(),
720 body_span: span,
721 }
722 }
723
724 fn verification_facts_fixture(
725 candidates: Vec<LeanWorkerDeclarationTargetInfo>,
726 axioms_available: bool,
727 ) -> LeanWorkerDeclarationVerificationFacts {
728 LeanWorkerDeclarationVerificationFacts {
729 target: None,
730 diagnostics: LeanWorkerElabFailure {
731 diagnostics: Vec::new(),
732 truncated: false,
733 },
734 unresolved_goals: Vec::new(),
735 contains_sorry: false,
736 contains_admit: false,
737 contains_sorry_ax: false,
738 axioms: Vec::new(),
739 axioms_truncated: false,
740 output_truncated: false,
741 candidates,
742 axioms_available,
743 }
744 }
745
746 #[test]
747 fn data_row_round_trips_through_length_delimited_frame() {
748 let row = DataRow {
749 stream: "rows".to_owned(),
750 sequence: 7,
751 payload: raw_json(&json!({ "name": "Nat.add", "score": 3 })),
752 };
753 let mut bytes = Vec::new();
754 write_frame(&mut bytes, Message::DataRow(row.clone()), MAX_FRAME_BYTES).expect("data row writes");
755 let frame = read_frame(&mut Cursor::new(bytes), MAX_FRAME_BYTES).expect("data row reads");
756 assert_eq!(frame.message, Message::DataRow(row));
757 }
758
759 #[test]
760 fn data_row_emitter_assigns_per_stream_sequences() {
761 let mut emitter = DataRowEmitter::default();
762 let mut bytes = Vec::new();
763 emitter
764 .emit(&mut bytes, "rows", &json!({ "i": 0 }))
765 .expect("first row writes");
766 emitter
767 .emit(&mut bytes, "warnings", &json!({ "i": 1 }))
768 .expect("second row writes");
769 emitter
770 .emit(&mut bytes, "rows", &json!({ "i": 2 }))
771 .expect("third row writes");
772 assert_eq!(emitter.count(), 3);
773
774 let mut cursor = Cursor::new(bytes);
775 let rows = [
776 read_frame(&mut cursor, MAX_FRAME_BYTES).expect("first row reads"),
777 read_frame(&mut cursor, MAX_FRAME_BYTES).expect("second row reads"),
778 read_frame(&mut cursor, MAX_FRAME_BYTES).expect("third row reads"),
779 ];
780 assert_eq!(
781 rows.map(|frame| frame.message),
782 [
783 Message::DataRow(DataRow {
784 stream: "rows".to_owned(),
785 sequence: 0,
786 payload: raw_json(&json!({ "i": 0 })),
787 }),
788 Message::DataRow(DataRow {
789 stream: "warnings".to_owned(),
790 sequence: 0,
791 payload: raw_json(&json!({ "i": 1 })),
792 }),
793 Message::DataRow(DataRow {
794 stream: "rows".to_owned(),
795 sequence: 1,
796 payload: raw_json(&json!({ "i": 2 })),
797 }),
798 ],
799 );
800 }
801
802 #[test]
803 fn oversized_data_row_is_rejected_before_write() {
804 let row = DataRow {
805 stream: "rows".to_owned(),
806 sequence: 0,
807 payload: raw_json(&json!({ "blob": "x".repeat(MAX_FRAME_BYTES as usize) })),
808 };
809 let mut bytes = Vec::new();
810 let err =
811 write_frame(&mut bytes, Message::DataRow(row), MAX_FRAME_BYTES).expect_err("oversized frame is rejected");
812 match err {
813 ProtocolError::FrameTooLarge { len, max } => {
814 assert!(len > max);
815 assert_eq!(max, MAX_FRAME_BYTES);
816 }
817 other @ (ProtocolError::Io(_) | ProtocolError::Json(_) | ProtocolError::VersionMismatch { .. }) => {
818 panic!("expected FrameTooLarge, got {other:?}");
819 }
820 }
821 }
822
823 #[test]
824 fn oversized_data_row_is_rejected_before_read_allocation() {
825 let mut bytes = Vec::new();
826 bytes.extend_from_slice(&(MAX_FRAME_BYTES.saturating_add(1)).to_be_bytes());
827 let err = read_frame(&mut Cursor::new(bytes), MAX_FRAME_BYTES).expect_err("oversized frame is rejected");
828 match err {
829 ProtocolError::FrameTooLarge { len, max } => {
830 assert_eq!(len, MAX_FRAME_BYTES + 1);
831 assert_eq!(max, MAX_FRAME_BYTES);
832 }
833 other @ (ProtocolError::Io(_) | ProtocolError::Json(_) | ProtocolError::VersionMismatch { .. }) => {
834 panic!("expected FrameTooLarge, got {other:?}");
835 }
836 }
837 }
838
839 #[test]
840 fn larger_cap_accepts_frame_rejected_under_default() {
841 let raised = MAX_FRAME_BYTES.saturating_mul(8);
845 let row = DataRow {
846 stream: "rows".to_owned(),
847 sequence: 0,
848 payload: raw_json(&json!({ "blob": "x".repeat(2 * MAX_FRAME_BYTES as usize) })),
849 };
850 let mut buf = Vec::new();
851 write_frame(&mut buf, Message::DataRow(row.clone()), raised).expect("oversize-under-default frame writes");
852 let frame = read_frame(&mut Cursor::new(buf), raised).expect("oversize-under-default frame reads");
853 assert_eq!(frame.message, Message::DataRow(row));
854 }
855
856 #[test]
857 fn frame_cap_bounds_constants_are_consistent() {
858 const { assert!(MIN_FRAME_BYTES <= MAX_FRAME_BYTES) };
859 const { assert!(MAX_FRAME_BYTES <= MAX_FRAME_BYTES_HARD_CAP) };
860 }
861
862 #[test]
863 fn malformed_frame_payload_is_protocol_error() {
864 let mut bytes = Vec::new();
865 bytes.extend_from_slice(&1_u32.to_be_bytes());
866 bytes.push(b'{');
867 let err = read_frame(&mut Cursor::new(bytes), MAX_FRAME_BYTES).expect_err("malformed JSON is rejected");
868 match err {
869 ProtocolError::Json(_) => {}
870 other @ (ProtocolError::Io(_)
871 | ProtocolError::FrameTooLarge { .. }
872 | ProtocolError::VersionMismatch { .. }) => {
873 panic!("expected Json error, got {other:?}");
874 }
875 }
876 }
877
878 #[test]
879 fn rows_complete_response_round_trips() {
880 let mut bytes = Vec::new();
881 write_frame(
882 &mut bytes,
883 Message::Response(Response::RowsComplete { count: 2 }),
884 MAX_FRAME_BYTES,
885 )
886 .expect("rows complete writes");
887 let frame = read_frame(&mut Cursor::new(bytes), MAX_FRAME_BYTES).expect("rows complete reads");
888 assert_eq!(frame.message, Message::Response(Response::RowsComplete { count: 2 }));
889 }
890
891 #[test]
892 fn declaration_search_request_and_response_round_trip() {
893 let request = Message::Request(Request::SearchDeclarations {
894 search: LeanWorkerDeclarationSearch {
895 name_fragment: Some("map".to_owned()),
896 name_match: LeanWorkerDeclarationNameMatch::Suffix,
897 kind: Some("theorem".to_owned()),
898 required_constants: vec!["List.map".to_owned()],
899 conclusion_head: Some("Eq".to_owned()),
900 scope_biases: vec![LeanWorkerDeclarationSearchBias {
901 scope: LeanWorkerDeclarationSearchScope::Namespace,
902 prefix: "List".to_owned(),
903 strict: false,
904 weight: 7,
905 }],
906 limit: 3,
907 filter: LeanWorkerDeclarationFilter {
908 include_private: false,
909 include_generated: false,
910 include_internal: false,
911 },
912 include_source: false,
913 },
914 });
915 let mut bytes = Vec::new();
916 write_frame(&mut bytes, request.clone(), MAX_FRAME_BYTES).expect("declaration search request writes");
917 let frame = read_frame(&mut Cursor::new(bytes), MAX_FRAME_BYTES).expect("declaration search request reads");
918 assert_eq!(frame.message, request);
919
920 let response = Message::Response(Response::DeclarationSearch {
921 result: LeanWorkerDeclarationSearchResult {
922 declarations: vec![LeanWorkerDeclarationSearchRow {
923 name: "List.map_map".to_owned(),
924 kind: "theorem".to_owned(),
925 module: Some("Init.Data.List.Lemmas".to_owned()),
926 source: None,
927 match_reason: "name,kind,required_constants,conclusion_head".to_owned(),
928 score: 127,
929 rank: 1,
930 flags: LeanWorkerDeclarationFlags::default(),
931 }],
932 truncated: true,
933 facts: LeanWorkerDeclarationSearchFacts {
934 declarations_scanned: 100,
935 after_name_filter: 10,
936 after_kind_filter: 8,
937 after_required_constants_filter: 4,
938 after_conclusion_filter: 2,
939 after_scope_filter: 2,
940 source_lookups: 0,
941 broad_pruning: vec![LeanWorkerDeclarationSearchPruning {
942 stage: "limit".to_owned(),
943 reason: "broad_search_limit".to_owned(),
944 count: 1,
945 }],
946 truncated: true,
947 timings: LeanWorkerDeclarationSearchTimings {
948 scan_micros: 1000,
949 rank_micros: 50,
950 source_micros: 0,
951 },
952 derived_work: LeanWorkerDerivedWorkFacts::default(),
953 },
954 },
955 });
956 let mut bytes = Vec::new();
957 write_frame(&mut bytes, response.clone(), MAX_FRAME_BYTES).expect("declaration search response writes");
958 let frame = read_frame(&mut Cursor::new(bytes), MAX_FRAME_BYTES).expect("declaration search response reads");
959 assert_eq!(frame.message, response);
960 }
961
962 #[test]
963 fn declaration_inspection_request_and_response_round_trip() {
964 let request = Message::Request(Request::InspectDeclaration {
965 request: LeanWorkerDeclarationInspectionRequest {
966 name: "List.map_map".to_owned(),
967 fields: LeanWorkerDeclarationInspectionFields {
968 source: true,
969 statement: true,
970 docstring: true,
971 attributes: true,
972 flags: true,
973 rendering: LeanWorkerRendering::Pretty,
974 proof_search: true,
975 },
976 budgets: LeanWorkerOutputBudgets {
977 per_field_bytes: 128,
978 total_bytes: 512,
979 },
980 },
981 });
982 let mut bytes = Vec::new();
983 write_frame(&mut bytes, request.clone(), MAX_FRAME_BYTES).expect("declaration inspection request writes");
984 let frame = read_frame(&mut Cursor::new(bytes), MAX_FRAME_BYTES).expect("declaration inspection request reads");
985 assert_eq!(frame.message, request);
986
987 let response = Message::Response(Response::DeclarationInspection {
988 result: LeanWorkerDeclarationInspectionResult::Found {
989 declaration: Box::new(LeanWorkerDeclarationInspection {
990 name: "List.map_map".to_owned(),
991 kind: "theorem".to_owned(),
992 module: Some("Init.Data.List.Lemmas".to_owned()),
993 source: Some(LeanWorkerSourceRange {
994 file: "Init/Data/List/Lemmas.lean".to_owned(),
995 start_line: 1,
996 start_column: 1,
997 end_line: 1,
998 end_column: 10,
999 }),
1000 statement: Some(LeanWorkerRenderedInfo {
1001 value: "forall ...".to_owned(),
1002 truncated: true,
1003 }),
1004 docstring: Some(LeanWorkerRenderedInfo {
1005 value: "doc".to_owned(),
1006 truncated: false,
1007 }),
1008 attributes: vec!["simp".to_owned(), "rw".to_owned()],
1009 proof_search: LeanWorkerDeclarationProofSearchFacts {
1010 computed: true,
1011 unavailable_reason: None,
1012 is_simp: true,
1013 is_rw_candidate: true,
1014 is_instance: false,
1015 is_class: false,
1016 class_name: None,
1017 },
1018 flags: LeanWorkerDeclarationFlags::default(),
1019 derived_work: LeanWorkerDerivedWorkFacts::default(),
1020 statement_rendering: Some(LeanWorkerRendering::Pretty),
1021 }),
1022 },
1023 });
1024 let mut bytes = Vec::new();
1025 write_frame(&mut bytes, response.clone(), MAX_FRAME_BYTES).expect("declaration inspection response writes");
1026 let frame =
1027 read_frame(&mut Cursor::new(bytes), MAX_FRAME_BYTES).expect("declaration inspection response reads");
1028 assert_eq!(frame.message, response);
1029
1030 let not_found = Message::Response(Response::DeclarationInspection {
1031 result: LeanWorkerDeclarationInspectionResult::NotFound {
1032 name: "Missing.name".to_owned(),
1033 },
1034 });
1035 let mut bytes = Vec::new();
1036 write_frame(&mut bytes, not_found.clone(), MAX_FRAME_BYTES)
1037 .expect("declaration inspection not-found response writes");
1038 let frame = read_frame(&mut Cursor::new(bytes), MAX_FRAME_BYTES)
1039 .expect("declaration inspection not-found response reads");
1040 assert_eq!(frame.message, not_found);
1041
1042 let unsupported = Message::Response(Response::DeclarationInspection {
1043 result: LeanWorkerDeclarationInspectionResult::Unsupported,
1044 });
1045 let mut bytes = Vec::new();
1046 write_frame(&mut bytes, unsupported.clone(), MAX_FRAME_BYTES)
1047 .expect("declaration inspection unsupported response writes");
1048 let frame = read_frame(&mut Cursor::new(bytes), MAX_FRAME_BYTES)
1049 .expect("declaration inspection unsupported response reads");
1050 assert_eq!(frame.message, unsupported);
1051 }
1052
1053 #[test]
1054 fn module_query_request_and_response_round_trip() {
1055 let request = Message::Request(Request::ProcessModuleQuery {
1056 source: "def x := 1\n#check x\n".to_owned(),
1057 query: LeanWorkerModuleQuery::TypeAt { line: 2, column: 8 },
1058 options: LeanWorkerElabOptions::default(),
1059 });
1060 let mut bytes = Vec::new();
1061 write_frame(&mut bytes, request.clone(), MAX_FRAME_BYTES).expect("module query request writes");
1062 let frame = read_frame(&mut Cursor::new(bytes), MAX_FRAME_BYTES).expect("module query request reads");
1063 assert_eq!(frame.message, request);
1064
1065 let response = Message::Response(Response::ProcessModuleQuery {
1066 outcome: LeanWorkerModuleQueryOutcome::Ok {
1067 imports: Vec::new(),
1068 result: LeanWorkerModuleQueryResult::TypeAt(LeanWorkerTypeAtResult::Term {
1069 span: LeanWorkerModuleSourceSpan {
1070 start_line: 2,
1071 start_column: 8,
1072 end_line: 2,
1073 end_column: 9,
1074 },
1075 expr: LeanWorkerRenderedInfo {
1076 value: "x".to_owned(),
1077 truncated: false,
1078 },
1079 type_str: LeanWorkerRenderedInfo {
1080 value: "Nat".to_owned(),
1081 truncated: false,
1082 },
1083 expected_type: None,
1084 }),
1085 },
1086 });
1087 let mut bytes = Vec::new();
1088 write_frame(&mut bytes, response.clone(), MAX_FRAME_BYTES).expect("module query response writes");
1089 let frame = read_frame(&mut Cursor::new(bytes), MAX_FRAME_BYTES).expect("module query response reads");
1090 assert_eq!(frame.message, response);
1091
1092 let unsupported = LeanWorkerModuleQueryOutcome::Unsupported;
1093 let json = serde_json::to_value(&unsupported).expect("unsupported serializes");
1094 assert_eq!(json, json!({ "status": "unsupported" }));
1095
1096 let diagnostics = LeanWorkerModuleQueryResult::Diagnostics(LeanWorkerElabFailure {
1097 diagnostics: Vec::new(),
1098 truncated: false,
1099 });
1100 let json = serde_json::to_value(&diagnostics).expect("diagnostics serializes");
1101 assert_eq!(
1102 json,
1103 json!({
1104 "result": "diagnostics",
1105 "body": {
1106 "diagnostics": [],
1107 "truncated": false
1108 }
1109 })
1110 );
1111 }
1112
1113 #[test]
1114 fn module_query_batch_request_and_response_round_trip() {
1115 let request = Message::Request(Request::ProcessModuleQueryBatch {
1116 source: "theorem t : True := by\n trivial\n".to_owned(),
1117 selectors: vec![
1118 LeanWorkerModuleQuerySelector::Diagnostics {
1119 id: "diagnostics".to_owned(),
1120 },
1121 LeanWorkerModuleQuerySelector::ProofState {
1122 id: "state".to_owned(),
1123 line: 2,
1124 column: 4,
1125 },
1126 ],
1127 budgets: LeanWorkerOutputBudgets::default(),
1128 options: LeanWorkerElabOptions::default(),
1129 });
1130 let mut bytes = Vec::new();
1131 write_frame(&mut bytes, request.clone(), MAX_FRAME_BYTES).expect("module query batch request writes");
1132 let frame = read_frame(&mut Cursor::new(bytes), MAX_FRAME_BYTES).expect("module query batch request reads");
1133 assert_eq!(frame.message, request);
1134
1135 let response = Message::Response(Response::ProcessModuleQueryBatch {
1136 outcome: LeanWorkerModuleQueryBatchOutcome::Ok {
1137 imports: Vec::new(),
1138 result: LeanWorkerModuleQueryBatchEnvelope {
1139 items: vec![LeanWorkerModuleQueryBatchItem::Ok {
1140 id: "diagnostics".to_owned(),
1141 result: Box::new(LeanWorkerModuleQueryBatchResult::Diagnostics(LeanWorkerElabFailure {
1142 diagnostics: Vec::new(),
1143 truncated: false,
1144 })),
1145 }],
1146 total_truncated: false,
1147 },
1148 facts: LeanWorkerModuleQueryCacheFacts {
1149 cache_status: LeanWorkerModuleCacheStatus::Miss,
1150 timings: LeanWorkerModuleQueryTimings::zero(),
1151 output_bytes: 0,
1152 cache_entry_count: Some(1),
1153 cache_approx_bytes: Some(1024),
1154 resource: None,
1155 },
1156 },
1157 });
1158 let mut bytes = Vec::new();
1159 write_frame(&mut bytes, response.clone(), MAX_FRAME_BYTES).expect("module query batch response writes");
1160 let frame = read_frame(&mut Cursor::new(bytes), MAX_FRAME_BYTES).expect("module query batch response reads");
1161 assert_eq!(frame.message, response);
1162 }
1163
1164 #[test]
1165 fn module_query_cache_facts_resource_is_additive_wire_field() {
1166 let old_json = serde_json::json!({
1167 "cache_status": "miss",
1168 "timings": {
1169 "header_import_micros": 0,
1170 "elaboration_micros": 0,
1171 "projection_micros": 0,
1172 "rendering_micros": 0
1173 },
1174 "output_bytes": 0,
1175 "cache_entry_count": null,
1176 "cache_approx_bytes": null
1177 });
1178 let old_facts: LeanWorkerModuleQueryCacheFacts =
1179 serde_json::from_value(old_json).expect("old cache facts deserialize without resource");
1180 assert!(old_facts.resource.is_none());
1181
1182 let resource = LeanWorkerResourceExhaustedFacts {
1183 cause: "worker_rss_hard_limit".to_owned(),
1184 work_entered_child: true,
1185 operation: Some("worker_process_module_query_batch".to_owned()),
1186 current_rss_kib: Some(2048),
1187 limit_kib: Some(1024),
1188 import_count: Some(1),
1189 worker_generation: Some(2),
1190 restart_reason: Some("rss_hard_limit".to_owned()),
1191 queue_wait_ms: None,
1192 duration_ms: None,
1193 cold_open_attempts: None,
1194 cold_open_admitted: None,
1195 cold_open_refusals: None,
1196 import_like_requests: Some(1),
1197 import_like_admitted: Some(1),
1198 last_import_stats: None,
1199 };
1200 let facts = LeanWorkerModuleQueryCacheFacts {
1201 resource: Some(Box::new(resource.clone())),
1202 ..LeanWorkerModuleQueryCacheFacts::uncached(0)
1203 };
1204 let round_trip: LeanWorkerModuleQueryCacheFacts =
1205 serde_json::from_value(serde_json::to_value(&facts).expect("facts serialize")).expect("facts deserialize");
1206 assert_eq!(round_trip.resource.as_deref(), Some(&resource));
1207 }
1208
1209 #[test]
1210 fn proof_attempt_request_and_response_round_trip() {
1211 let span = LeanWorkerModuleSourceSpan {
1212 start_line: 1,
1213 start_column: 22,
1214 end_line: 2,
1215 end_column: 7,
1216 };
1217 let request = Message::Request(Request::AttemptProof {
1218 request: LeanWorkerProofAttemptRequest {
1219 source: "theorem t : True := by\n trivial\n".to_owned(),
1220 edit: LeanWorkerProofEditTarget::Declaration {
1221 name: "t".to_owned(),
1222 position: LeanWorkerProofPositionSelector::Default,
1223 },
1224 candidates: vec![LeanWorkerProofCandidate {
1225 id: "rfl".to_owned(),
1226 text: "by trivial".to_owned(),
1227 }],
1228 budgets: LeanWorkerOutputBudgets::default(),
1229 },
1230 options: LeanWorkerElabOptions::default(),
1231 progress: true,
1232 });
1233 let mut bytes = Vec::new();
1234 write_frame(&mut bytes, request.clone(), MAX_FRAME_BYTES).expect("proof attempt request writes");
1235 let frame = read_frame(&mut Cursor::new(bytes), MAX_FRAME_BYTES).expect("proof attempt request reads");
1236 assert_eq!(frame.message, request);
1237
1238 let response = Message::Response(Response::ProofAttempt {
1239 result: LeanWorkerProofAttemptResult::Ok {
1240 imports: Vec::new(),
1241 result: LeanWorkerProofAttemptEnvelope {
1242 candidates: vec![LeanWorkerProofAttemptRow {
1243 id: "rfl".to_owned(),
1244 status: LeanWorkerProofAttemptStatus::Closed,
1245 candidate_text: LeanWorkerRenderedInfo {
1246 value: "rfl".to_owned(),
1247 truncated: false,
1248 },
1249 diagnostics: LeanWorkerElabFailure {
1250 diagnostics: Vec::new(),
1251 truncated: false,
1252 },
1253 downstream_diagnostics: LeanWorkerElabFailure {
1254 diagnostics: Vec::new(),
1255 truncated: false,
1256 },
1257 goals: Vec::new(),
1258 declaration: Some(LeanWorkerDeclarationTargetInfo {
1259 short_name: "t".to_owned(),
1260 declaration_name: "t".to_owned(),
1261 namespace_name: String::new(),
1262 declaration_kind: "theorem".to_owned(),
1263 declaration_span: span.clone(),
1264 name_span: span.clone(),
1265 body_span: span,
1266 }),
1267 proof_position: Some(LeanWorkerProofPositionSummary {
1268 index: 0,
1269 tactic: LeanWorkerRenderedInfo {
1270 value: "trivial".to_owned(),
1271 truncated: false,
1272 },
1273 }),
1274 output_truncated: false,
1275 }],
1276 candidate_limit: 8,
1277 candidates_truncated: false,
1278 },
1279 },
1280 });
1281 let mut bytes = Vec::new();
1282 write_frame(&mut bytes, response.clone(), MAX_FRAME_BYTES).expect("proof attempt response writes");
1283 let frame = read_frame(&mut Cursor::new(bytes), MAX_FRAME_BYTES).expect("proof attempt response reads");
1284 assert_eq!(frame.message, response);
1285 }
1286
1287 #[test]
1288 fn proof_position_selector_tags_are_stable_and_round_trip() {
1289 let cases = [
1290 (
1291 LeanWorkerProofPositionSelector::Default,
1292 serde_json::json!({"kind": "default"}),
1293 ),
1294 (
1295 LeanWorkerProofPositionSelector::Index { index: 3 },
1296 serde_json::json!({"kind": "index", "index": 3}),
1297 ),
1298 (
1299 LeanWorkerProofPositionSelector::AfterText {
1300 text: "intro x".to_owned(),
1301 occurrence: Some(1),
1302 },
1303 serde_json::json!({"kind": "after_text", "text": "intro x", "occurrence": 1}),
1304 ),
1305 (
1306 LeanWorkerProofPositionSelector::Entry,
1307 serde_json::json!({"kind": "entry"}),
1308 ),
1309 ];
1310 for (selector, expected) in cases {
1311 let value = serde_json::to_value(&selector).expect("selector serializes");
1312 assert_eq!(value, expected, "selector tag must be stable: {selector:?}");
1313 let parsed: LeanWorkerProofPositionSelector =
1314 serde_json::from_value(expected).expect("selector deserializes");
1315 assert_eq!(parsed, selector, "selector must round-trip through JSON");
1316 }
1317 }
1318
1319 #[test]
1320 fn declaration_verification_request_and_response_round_trip() {
1321 let request = Message::Request(Request::VerifyDeclaration {
1322 request: LeanWorkerDeclarationVerificationRequest {
1323 source: "theorem t : True := by\n trivial\n".to_owned(),
1324 target: LeanWorkerDeclarationVerificationTarget::Name { name: "t".to_owned() },
1325 sorry_policy: LeanWorkerSorryPolicy::Deny,
1326 report_axioms: true,
1327 budgets: LeanWorkerOutputBudgets::default(),
1328 },
1329 options: LeanWorkerElabOptions::default(),
1330 progress: false,
1331 });
1332 let mut bytes = Vec::new();
1333 write_frame(&mut bytes, request.clone(), MAX_FRAME_BYTES).expect("verification request writes");
1334 let frame = read_frame(&mut Cursor::new(bytes), MAX_FRAME_BYTES).expect("verification request reads");
1335 assert_eq!(frame.message, request);
1336
1337 let response = Message::Response(Response::DeclarationVerification {
1338 result: LeanWorkerDeclarationVerificationResult::Ok {
1339 verification_status: LeanWorkerDeclarationVerificationStatus::Accepted,
1340 facts: Box::new(LeanWorkerDeclarationVerificationFacts {
1341 target: None,
1342 diagnostics: LeanWorkerElabFailure {
1343 diagnostics: Vec::new(),
1344 truncated: false,
1345 },
1346 unresolved_goals: Vec::new(),
1347 contains_sorry: false,
1348 contains_admit: false,
1349 contains_sorry_ax: false,
1350 axioms: vec!["propext".to_owned(), "Classical.choice".to_owned()],
1351 axioms_truncated: false,
1352 output_truncated: false,
1353 candidates: Vec::new(),
1354 axioms_available: true,
1355 }),
1356 imports: Vec::new(),
1357 },
1358 });
1359 let mut bytes = Vec::new();
1360 write_frame(&mut bytes, response.clone(), MAX_FRAME_BYTES).expect("verification response writes");
1361 let frame = read_frame(&mut Cursor::new(bytes), MAX_FRAME_BYTES).expect("verification response reads");
1362 assert_eq!(frame.message, response);
1363 }
1364
1365 #[test]
1366 fn verification_needs_build_and_ambiguous_round_trip() {
1367 let needs_build = Message::Response(Response::DeclarationVerification {
1370 result: LeanWorkerDeclarationVerificationResult::MissingImports {
1371 verification_status: LeanWorkerDeclarationVerificationStatus::NeedsBuild,
1372 facts: Box::new(verification_facts_fixture(Vec::new(), false)),
1373 imports: vec!["Mathlib.Tactic".to_owned()],
1374 missing: vec!["Mathlib.Unbuilt.Dep".to_owned()],
1375 },
1376 });
1377 assert_frame_round_trips(&needs_build);
1378
1379 let ambiguous = Message::Response(Response::DeclarationVerification {
1381 result: LeanWorkerDeclarationVerificationResult::Ok {
1382 verification_status: LeanWorkerDeclarationVerificationStatus::Ambiguous,
1383 facts: Box::new(verification_facts_fixture(
1384 vec![
1385 declaration_target_info_fixture("A.foo"),
1386 declaration_target_info_fixture("B.foo"),
1387 ],
1388 false,
1389 )),
1390 imports: Vec::new(),
1391 },
1392 });
1393 assert_frame_round_trips(&ambiguous);
1394 }
1395
1396 #[test]
1397 fn proof_state_ambiguous_and_needs_build_round_trip() {
1398 let ambiguous = Message::Response(Response::ProcessModuleQueryBatch {
1399 outcome: LeanWorkerModuleQueryBatchOutcome::Ok {
1400 result: LeanWorkerModuleQueryBatchEnvelope {
1401 items: vec![LeanWorkerModuleQueryBatchItem::Ok {
1402 id: "state".to_owned(),
1403 result: Box::new(LeanWorkerModuleQueryBatchResult::ProofState(
1404 LeanWorkerProofStateResult::Ambiguous {
1405 candidates: vec![
1406 declaration_target_info_fixture("A.foo"),
1407 declaration_target_info_fixture("B.foo"),
1408 ],
1409 },
1410 )),
1411 }],
1412 total_truncated: false,
1413 },
1414 imports: Vec::new(),
1415 facts: LeanWorkerModuleQueryCacheFacts::uncached(0),
1416 },
1417 });
1418 assert_frame_round_trips(&ambiguous);
1419
1420 let needs_build = Message::Response(Response::ProcessModuleQueryBatch {
1421 outcome: LeanWorkerModuleQueryBatchOutcome::Ok {
1422 result: LeanWorkerModuleQueryBatchEnvelope {
1423 items: vec![LeanWorkerModuleQueryBatchItem::Ok {
1424 id: "state".to_owned(),
1425 result: Box::new(LeanWorkerModuleQueryBatchResult::ProofState(
1426 LeanWorkerProofStateResult::NeedsBuild {
1427 missing: vec!["Mathlib.Unbuilt.Dep".to_owned()],
1428 },
1429 )),
1430 }],
1431 total_truncated: false,
1432 },
1433 imports: Vec::new(),
1434 facts: LeanWorkerModuleQueryCacheFacts::uncached(0),
1435 },
1436 });
1437 assert_frame_round_trips(&needs_build);
1438 }
1439
1440 #[test]
1441 fn inspection_fields_default_rendering_is_pretty() {
1442 let json = serde_json::json!({
1446 "source": true,
1447 "statement": true,
1448 "docstring": false,
1449 "attributes": false,
1450 "flags": false,
1451 });
1452 let fields: LeanWorkerDeclarationInspectionFields =
1453 serde_json::from_value(json).expect("fields without rendering deserialize");
1454 assert_eq!(fields.rendering, LeanWorkerRendering::Pretty);
1455 assert!(!fields.proof_search);
1456 }
1457}