1use serde::{Deserialize, Serialize};
16
17pub mod v3;
20
21pub const MIN_RUNTIME_API_VERSION: u32 = 1;
23pub const RUNTIME_API_VERSION: u32 = 2;
25pub const MODEL_PACK_FORMAT_VERSION: u32 = 1;
27pub const HANDLER_PACKAGE_FORMAT_VERSION: u32 = 1;
29pub const HANDLER_API_VERSION: u32 = 1;
31pub const RUNTIME_STATE_FORMAT_VERSION: u32 = 1;
33pub const RELEASE_INDEX_SCHEMA_VERSION: u32 = 3;
43pub const MAX_MESSAGE_BYTES: usize = 1024 * 1024;
45
46pub const RUNTIME_ARTIFACT_ID: &str = "rill-runtime";
48pub const RUNTIME_ARTIFACT_ID_MUSL: &str = "rill-runtime-musl";
52pub const PM_ADAPTER_ARTIFACT_ID: &str = "rill-pm-adapter";
57pub const PM_ADAPTER_PROTOCOL_VERSION: u32 = 1;
60
61pub mod error_code {
77 pub const INVALID_JSON: &str = "invalidJson";
79 pub const INVALID_REQUEST_ID: &str = "invalidRequestId";
81 pub const INCOMPATIBLE_API_VERSION: &str = "incompatibleApiVersion";
83 pub const INVALID_CLIENT_IDENTITY: &str = "invalidClientIdentity";
85 pub const UNSUPPORTED_CAPABILITY: &str = "unsupportedCapability";
87 pub const NO_INVOKE_HANDLER: &str = "noInvokeHandler";
89 pub const HANDLER_TIMEOUT: &str = "handlerTimeout";
91 pub const HANDLER_TRAP: &str = "handlerTrap";
93 pub const HANDLER_OUTPUT_TOO_LARGE: &str = "handlerOutputTooLarge";
95 pub const HANDLER_INVALID_OUTPUT: &str = "handlerInvalidOutput";
97 pub const HANDLER_INTERNAL_ERROR: &str = "handlerInternalError";
100
101 pub const FROZEN_CODES: &[&str] = &[
107 HANDLER_INTERNAL_ERROR,
108 HANDLER_INVALID_OUTPUT,
109 HANDLER_OUTPUT_TOO_LARGE,
110 HANDLER_TIMEOUT,
111 HANDLER_TRAP,
112 INCOMPATIBLE_API_VERSION,
113 INVALID_CLIENT_IDENTITY,
114 INVALID_JSON,
115 INVALID_REQUEST_ID,
116 NO_INVOKE_HANDLER,
117 UNSUPPORTED_CAPABILITY,
118 ];
119
120 pub fn is_frozen(code: &str) -> bool {
122 FROZEN_CODES.contains(&code)
123 }
124}
125
126#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
131#[serde(rename_all = "camelCase", deny_unknown_fields)]
132pub struct ModelPackManifest {
133 pub format_version: u32,
134 pub id: String,
135 pub version: String,
136 pub runtime_api_version: u32,
137 pub min_runtime_version: String,
138 pub publisher_key_id: String,
139 pub capabilities: Vec<String>,
140}
141
142impl ModelPackManifest {
143 pub fn validate_shape(&self) -> Result<(), &'static str> {
144 if self.format_version != MODEL_PACK_FORMAT_VERSION {
145 return Err("unsupported model-pack format version");
146 }
147 if self.runtime_api_version != RUNTIME_API_VERSION {
148 return Err("unsupported runtime API version");
149 }
150 if self.id.is_empty() || self.id.len() > 96 {
151 return Err("invalid model-pack id");
152 }
153 if self.version.is_empty() || self.version.len() > 48 {
154 return Err("invalid model-pack version");
155 }
156 if self.publisher_key_id.is_empty() || self.publisher_key_id.len() > 96 {
157 return Err("invalid publisher key id");
158 }
159 Self::validate_capabilities(&self.capabilities)?;
160 Ok(())
161 }
162
163 pub fn validate_capabilities(capabilities: &[String]) -> Result<(), &'static str> {
164 if capabilities.is_empty() || capabilities.len() > 32 {
165 return Err("invalid capabilities list");
166 }
167 if capabilities
168 .iter()
169 .any(|capability| capability.is_empty() || capability.len() > 96)
170 {
171 return Err("invalid capability string");
172 }
173 let mut seen = std::collections::HashSet::new();
174 if !capabilities
175 .iter()
176 .all(|capability| seen.insert(capability.clone()))
177 {
178 return Err("duplicate capability");
179 }
180 Ok(())
181 }
182}
183
184#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
189#[serde(rename_all = "camelCase", deny_unknown_fields)]
190pub struct HandlerPackManifest {
191 pub format_version: u32,
192 pub id: String,
193 pub version: String,
194 pub handler_api_version: u32,
195 pub min_runtime_version: String,
196 pub publisher_key_id: String,
197 pub capabilities: Vec<String>,
198 pub module_sha256: String,
199 pub module_size: u64,
200}
201
202impl HandlerPackManifest {
203 pub fn validate_shape(&self) -> Result<(), &'static str> {
204 if self.format_version != HANDLER_PACKAGE_FORMAT_VERSION {
205 return Err("unsupported handler-pack format version");
206 }
207 if self.handler_api_version != HANDLER_API_VERSION {
208 return Err("unsupported handler API version");
209 }
210 if self.id.is_empty() || self.id.len() > 96 {
211 return Err("invalid handler id");
212 }
213 if self.version.is_empty() || self.version.len() > 48 {
214 return Err("invalid handler version");
215 }
216 if self.publisher_key_id.is_empty() || self.publisher_key_id.len() > 96 {
217 return Err("invalid handler publisher key id");
218 }
219 if self.min_runtime_version.is_empty() || self.min_runtime_version.len() > 48 {
220 return Err("invalid minimum runtime version");
221 }
222 ModelPackManifest::validate_capabilities(&self.capabilities)?;
223 if self.module_sha256.len() != 64
224 || !self
225 .module_sha256
226 .bytes()
227 .all(|byte| byte.is_ascii_hexdigit())
228 {
229 return Err("invalid module SHA-256");
230 }
231 if self.module_size == 0 || self.module_size > 4 * 1024 * 1024 {
232 return Err("invalid module size");
233 }
234 Ok(())
235 }
236}
237
238#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
243#[serde(rename_all = "camelCase")]
244pub enum ReleaseArtifactKind {
245 Runtime,
246 Model,
247 Handler,
248 #[serde(rename = "pm-adapter")]
249 PmAdapter,
250}
251
252#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
253#[serde(rename_all = "camelCase", deny_unknown_fields)]
254pub struct ReleaseArtifact {
255 pub kind: ReleaseArtifactKind,
256 pub id: String,
257 pub version: String,
258 #[serde(default)]
259 pub runtime_api_version: u32,
260 #[serde(default, skip_serializing_if = "Option::is_none")]
261 pub target_os: Option<String>,
262 #[serde(default, skip_serializing_if = "Option::is_none")]
263 pub target_arch: Option<String>,
264 #[serde(default, skip_serializing_if = "Option::is_none")]
269 pub target_libc: Option<String>,
270 #[serde(default, skip_serializing_if = "Option::is_none")]
271 pub handler_api_version: Option<u32>,
272 #[serde(default, skip_serializing_if = "Option::is_none")]
273 pub min_runtime_version: Option<String>,
274 #[serde(default, skip_serializing_if = "Option::is_none")]
275 pub pm_adapter_protocol_version: Option<u32>,
276 pub url: String,
277 pub sha256: String,
278 pub size: u64,
279}
280
281impl ReleaseArtifact {
282 pub fn validate_shape(&self) -> Result<(), &'static str> {
283 if self.id.is_empty() || self.id.len() > 96 {
284 return Err("invalid artifact id");
285 }
286 if self.version.is_empty() || self.version.len() > 48 {
287 return Err("invalid artifact version");
288 }
289 if self.url.is_empty() || self.url.len() > 2048 {
290 return Err("invalid artifact URL");
291 }
292 if self.sha256.len() != 64 || !self.sha256.bytes().all(|byte| byte.is_ascii_hexdigit()) {
293 return Err("invalid artifact SHA-256");
294 }
295 if self.size == 0 || self.size > 128 * 1024 * 1024 {
296 return Err("invalid artifact size");
297 }
298 match self.kind {
299 ReleaseArtifactKind::Runtime => {
300 if self.runtime_api_version != RUNTIME_API_VERSION {
301 return Err("unsupported artifact runtime API version");
302 }
303 if (self.id != RUNTIME_ARTIFACT_ID && self.id != RUNTIME_ARTIFACT_ID_MUSL)
309 || self.target_os.as_deref().is_none_or(str::is_empty)
310 || self.target_arch.as_deref().is_none_or(str::is_empty)
311 {
312 return Err("runtime artifact requires a target OS and architecture");
313 }
314 match self.target_os.as_deref() {
317 Some("linux") => {
318 let libc = self.target_libc.as_deref();
319 let expected = if self.id == RUNTIME_ARTIFACT_ID_MUSL {
320 Some("musl")
321 } else {
322 Some("gnu")
323 };
324 if libc != expected {
325 return Err("runtime artifact libc variant does not match its id");
326 }
327 }
328 _ => {
329 if self.target_libc.is_some() {
330 return Err("non-Linux runtime artifact must not carry a libc variant");
331 }
332 }
333 }
334 if self.handler_api_version.is_some() || self.min_runtime_version.is_some() {
335 return Err("runtime artifact must not carry handler fields");
336 }
337 }
338 ReleaseArtifactKind::Model => {
339 if self.runtime_api_version != RUNTIME_API_VERSION {
340 return Err("unsupported artifact runtime API version");
341 }
342 if self.target_os.is_some()
343 || self.target_arch.is_some()
344 || self.handler_api_version.is_some()
345 || self.min_runtime_version.is_some()
346 {
347 return Err("model artifact must be platform independent");
348 }
349 }
350 ReleaseArtifactKind::Handler => {
351 if self.runtime_api_version != RUNTIME_API_VERSION {
352 return Err("unsupported artifact runtime API version");
353 }
354 if self.target_os.is_some() || self.target_arch.is_some() {
355 return Err("handler artifact must be platform independent");
356 }
357 let handler_api = self
358 .handler_api_version
359 .ok_or("handler artifact requires handler API version")?;
360 if handler_api != HANDLER_API_VERSION {
361 return Err("unsupported handler API version");
362 }
363 let min_runtime = self
364 .min_runtime_version
365 .as_deref()
366 .ok_or("handler artifact requires minimum runtime version")?;
367 if min_runtime.is_empty() || min_runtime.len() > 48 {
368 return Err("invalid minimum runtime version");
369 }
370 }
371 ReleaseArtifactKind::PmAdapter => {
372 if self.runtime_api_version != 0 {
377 return Err("pm-adapter artifact must not set runtime API version");
378 }
379 if self.id != PM_ADAPTER_ARTIFACT_ID
380 || self.target_os.as_deref().is_none_or(str::is_empty)
381 || self.target_arch.as_deref().is_none_or(str::is_empty)
382 {
383 return Err("pm-adapter artifact requires a target OS and architecture");
384 }
385 if self.handler_api_version.is_some() || self.min_runtime_version.is_some() {
386 return Err("pm-adapter artifact must not carry handler fields");
387 }
388 if self.pm_adapter_protocol_version != Some(PM_ADAPTER_PROTOCOL_VERSION) {
389 return Err("unsupported pm-adapter protocol version");
390 }
391 }
392 }
393 Ok(())
394 }
395}
396
397#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
398#[serde(rename_all = "camelCase", deny_unknown_fields)]
399pub struct ReleaseIndexPayload {
400 pub schema_version: u32,
401 pub channel: String,
402 pub generated_at: String,
403 pub publisher_key_id: String,
404 pub artifacts: Vec<ReleaseArtifact>,
405}
406
407impl ReleaseIndexPayload {
408 pub fn validate_shape(&self) -> Result<(), &'static str> {
409 if self.schema_version != RELEASE_INDEX_SCHEMA_VERSION {
410 return Err("unsupported release-index schema");
411 }
412 if !matches!(self.channel.as_str(), "stable" | "candidate") {
413 return Err("unsupported release channel");
414 }
415 if self.generated_at.is_empty() || self.generated_at.len() > 64 {
416 return Err("invalid release-index timestamp");
417 }
418 if self.publisher_key_id.is_empty() || self.publisher_key_id.len() > 96 {
419 return Err("invalid release-index publisher");
420 }
421 if self.artifacts.is_empty() || self.artifacts.len() > 64 {
422 return Err("invalid release-index artifact count");
423 }
424 for artifact in &self.artifacts {
425 artifact.validate_shape()?;
426 }
427 Ok(())
428 }
429}
430
431#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
432#[serde(rename_all = "camelCase", deny_unknown_fields)]
433pub struct SignedReleaseIndex {
434 pub payload: ReleaseIndexPayload,
435 pub signature: String,
437}
438
439#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
444#[serde(
445 tag = "method",
446 rename_all = "camelCase",
447 rename_all_fields = "camelCase",
448 deny_unknown_fields
449)]
450pub enum RuntimeRequest {
451 Handshake {
452 request_id: String,
453 api_version: u32,
454 client_name: String,
455 client_version: String,
456 },
457 Health {
458 request_id: String,
459 api_version: u32,
460 },
461 Invoke {
462 request_id: String,
463 api_version: u32,
464 capability: String,
465 input: serde_json::Value,
466 },
467}
468
469impl RuntimeRequest {
470 pub fn request_id(&self) -> &str {
471 match self {
472 Self::Handshake { request_id, .. }
473 | Self::Health { request_id, .. }
474 | Self::Invoke { request_id, .. } => request_id,
475 }
476 }
477
478 pub fn api_version(&self) -> u32 {
479 match self {
480 Self::Handshake { api_version, .. }
481 | Self::Health { api_version, .. }
482 | Self::Invoke { api_version, .. } => *api_version,
483 }
484 }
485}
486
487#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
492#[serde(
493 tag = "kind",
494 rename_all = "camelCase",
495 rename_all_fields = "camelCase",
496 deny_unknown_fields
497)]
498pub enum RuntimeResponse {
499 Handshake {
500 request_id: String,
501 api_version: u32,
502 runtime_version: String,
503 model_pack_id: String,
504 model_pack_version: String,
505 capabilities: Vec<String>,
506 },
507 Health {
508 request_id: String,
509 api_version: u32,
510 healthy: bool,
511 model_pack_id: String,
512 model_pack_version: String,
513 },
514 Result {
515 request_id: String,
516 api_version: u32,
517 output: serde_json::Value,
518 },
519 Error {
520 request_id: String,
521 api_version: u32,
522 code: String,
523 message: String,
524 retryable: bool,
525 },
526}
527
528#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
533#[serde(
534 tag = "kind",
535 rename_all = "camelCase",
536 rename_all_fields = "camelCase",
537 deny_unknown_fields
538)]
539pub enum RuntimeResponseV2 {
540 Handshake {
541 request_id: String,
542 api_version: u32,
543 runtime_version: String,
544 model_pack_id: String,
545 model_pack_version: String,
546 capabilities: Vec<String>,
547 handler_id: String,
548 handler_version: String,
549 handler_api_version: u32,
550 effective_capabilities: Vec<String>,
551 },
552 Health {
553 request_id: String,
554 api_version: u32,
555 healthy: bool,
556 model_pack_id: String,
557 model_pack_version: String,
558 },
559 Result {
560 request_id: String,
561 api_version: u32,
562 output: serde_json::Value,
563 },
564 Error {
565 request_id: String,
566 api_version: u32,
567 code: String,
568 message: String,
569 retryable: bool,
570 },
571}
572
573#[cfg(test)]
574mod tests {
575 use super::*;
576
577 #[test]
578 fn protocol_v1_roundtrip_is_tagged_and_strict() {
579 let request = RuntimeRequest::Health {
580 request_id: "health-1".into(),
581 api_version: 1,
582 };
583 let json = serde_json::to_string(&request).unwrap();
584 assert!(json.contains("\"method\":\"health\""));
585 let restored: RuntimeRequest = serde_json::from_str(&json).unwrap();
586 assert_eq!(restored, request);
587 assert!(
588 serde_json::from_str::<RuntimeRequest>(
589 r#"{"method":"health","requestId":"x","apiVersion":1,"extra":true}"#
590 )
591 .is_err()
592 );
593 }
594
595 #[test]
596 fn v1_handshake_fixture_is_stable() {
597 let request = RuntimeRequest::Handshake {
598 request_id: "fixture".into(),
599 api_version: 1,
600 client_name: "example-host".into(),
601 client_version: "0.6.10".into(),
602 };
603 assert_eq!(
604 serde_json::to_string(&request).unwrap(),
605 r#"{"method":"handshake","requestId":"fixture","apiVersion":1,"clientName":"example-host","clientVersion":"0.6.10"}"#
606 );
607 }
608
609 #[test]
610 fn v1_handshake_response_fixture_is_stable() {
611 let response = RuntimeResponse::Handshake {
612 request_id: "fixture".into(),
613 api_version: 1,
614 runtime_version: "0.6.0".into(),
615 model_pack_id: "rillml.example.default".into(),
616 model_pack_version: "0.6.0".into(),
617 capabilities: vec!["rillml.example".into()],
618 };
619 assert_eq!(
620 serde_json::to_string(&response).unwrap(),
621 r#"{"kind":"handshake","requestId":"fixture","apiVersion":1,"runtimeVersion":"0.6.0","modelPackId":"rillml.example.default","modelPackVersion":"0.6.0","capabilities":["rillml.example"]}"#
622 );
623 }
624
625 #[test]
626 fn v2_handshake_response_fixture_is_stable() {
627 let response = RuntimeResponseV2::Handshake {
628 request_id: "v2-fixture".into(),
629 api_version: 2,
630 runtime_version: "0.7.0".into(),
631 model_pack_id: "rillml.example.default".into(),
632 model_pack_version: "0.7.0".into(),
633 capabilities: vec!["rillml.example".into()],
634 handler_id: "org.example.handler".into(),
635 handler_version: "1.0.0".into(),
636 handler_api_version: 1,
637 effective_capabilities: vec!["rillml.example".into()],
638 };
639 let json = serde_json::to_string(&response).unwrap();
640 assert!(json.contains("\"handlerId\":\"org.example.handler\""));
641 assert!(json.contains("\"handlerApiVersion\":1"));
642 assert!(json.contains("\"effectiveCapabilities\":[\"rillml.example\"]"));
643 let mut bad = serde_json::from_str::<RuntimeResponseV2>(&json).unwrap();
646 if let RuntimeResponseV2::Handshake { handler_id, .. } = &mut bad {
647 handler_id.push('x');
648 }
649 let bad_json = serde_json::to_string(&bad).unwrap();
650 assert_ne!(bad_json, json);
651 }
652
653 #[test]
654 fn v1_response_rejects_handler_fields() {
655 let json = r#"{"kind":"handshake","requestId":"x","apiVersion":1,"runtimeVersion":"0.7.0","modelPackId":"m","modelPackVersion":"1","capabilities":["c"],"handlerId":"h"}"#;
656 assert!(serde_json::from_str::<RuntimeResponse>(json).is_err());
657 }
658
659 #[test]
660 fn invoke_roundtrip_preserves_capability_and_input() {
661 let request = RuntimeRequest::Invoke {
662 request_id: "invoke-1".into(),
663 api_version: 2,
664 capability: "rillml.example".into(),
665 input: serde_json::json!({"samples": []}),
666 };
667 let json = serde_json::to_string(&request).unwrap();
668 assert!(json.contains("\"method\":\"invoke\""));
669 assert!(json.contains("\"capability\":\"rillml.example\""));
670 let restored: RuntimeRequest = serde_json::from_str(&json).unwrap();
671 assert_eq!(restored, request);
672 }
673
674 #[test]
675 fn release_artifacts_enforce_platform_boundaries() {
676 let runtime = ReleaseArtifact {
677 kind: ReleaseArtifactKind::Runtime,
678 id: RUNTIME_ARTIFACT_ID.into(),
679 version: "0.7.0".into(),
680 runtime_api_version: RUNTIME_API_VERSION,
681 target_os: Some("macos".into()),
682 target_arch: Some("aarch64".into()),
683 target_libc: None,
684 handler_api_version: None,
685 min_runtime_version: None,
686 pm_adapter_protocol_version: None,
687 url: "https://example.invalid/rill-runtime".into(),
688 sha256: "ab".repeat(32),
689 size: 1024,
690 };
691 assert!(runtime.validate_shape().is_ok());
692
693 let mut model = runtime.clone();
694 model.kind = ReleaseArtifactKind::Model;
695 model.id = "rillml.example.default".into();
696 model.target_os = None;
697 model.target_arch = None;
698 assert!(model.validate_shape().is_ok());
699
700 let mut handler = runtime.clone();
701 handler.kind = ReleaseArtifactKind::Handler;
702 handler.id = "org.example.handler".into();
703 handler.target_os = None;
704 handler.target_arch = None;
705 handler.handler_api_version = Some(HANDLER_API_VERSION);
706 handler.min_runtime_version = Some("0.7.0".into());
707 assert!(handler.validate_shape().is_ok());
708
709 handler.target_os = Some("linux".into());
711 assert!(handler.validate_shape().is_err());
712 handler.target_os = None;
713
714 handler.handler_api_version = None;
716 assert!(handler.validate_shape().is_err());
717 handler.handler_api_version = Some(HANDLER_API_VERSION);
718
719 handler.min_runtime_version = None;
721 assert!(handler.validate_shape().is_err());
722 }
723
724 #[test]
725 fn pm_adapter_artifact_roundtrip_and_shape_validation() {
726 let json = r#"{"kind":"pm-adapter","id":"rill-pm-adapter","version":"1.2.0-rc.1","pmAdapterProtocolVersion":1,"targetOs":"linux","targetArch":"x86_64","url":"https://example.invalid/adapter","sha256":"3333333333333333333333333333333333333333333333333333333333333333","size":4096}"#;
729 let artifact: ReleaseArtifact = serde_json::from_str(json).unwrap();
730 assert_eq!(artifact.kind, ReleaseArtifactKind::PmAdapter);
731 assert_eq!(
732 artifact.pm_adapter_protocol_version,
733 Some(PM_ADAPTER_PROTOCOL_VERSION)
734 );
735 assert!(artifact.validate_shape().is_ok());
736
737 assert!(
739 serde_json::from_str::<ReleaseArtifact>(&json.replace("pm-adapter", "pmAdapter"))
740 .is_err()
741 );
742
743 let mut bad = artifact.clone();
745 bad.pm_adapter_protocol_version = Some(99);
746 assert!(bad.validate_shape().is_err());
747
748 let mut bad = artifact.clone();
750 bad.handler_api_version = Some(HANDLER_API_VERSION);
751 assert!(bad.validate_shape().is_err());
752
753 let mut bad = artifact.clone();
755 bad.runtime_api_version = RUNTIME_API_VERSION;
756 assert!(bad.validate_shape().is_err());
757
758 let mut bad = artifact.clone();
760 bad.target_os = None;
761 assert!(bad.validate_shape().is_err());
762
763 let mut bad = artifact.clone();
765 bad.id = "org.example.other".into();
766 assert!(bad.validate_shape().is_err());
767 }
768
769 #[test]
770 fn handler_manifest_validates_shape() {
771 let manifest = HandlerPackManifest {
772 format_version: HANDLER_PACKAGE_FORMAT_VERSION,
773 id: "org.example.handler".into(),
774 version: "1.0.0".into(),
775 handler_api_version: HANDLER_API_VERSION,
776 min_runtime_version: "0.7.0".into(),
777 publisher_key_id: "test-key".into(),
778 capabilities: vec!["org.example.predict".into()],
779 module_sha256: "ab".repeat(32),
780 module_size: 1024,
781 };
782 assert!(manifest.validate_shape().is_ok());
783
784 let mut bad = manifest.clone();
785 bad.format_version = 99;
786 assert!(bad.validate_shape().is_err());
787
788 let mut bad = manifest.clone();
789 bad.handler_api_version = 99;
790 assert!(bad.validate_shape().is_err());
791
792 let mut bad = manifest.clone();
793 bad.capabilities = vec![];
794 assert!(bad.validate_shape().is_err());
795
796 let mut bad = manifest.clone();
797 bad.module_sha256 = "short".into();
798 assert!(bad.validate_shape().is_err());
799
800 let mut bad = manifest.clone();
801 bad.module_size = 0;
802 assert!(bad.validate_shape().is_err());
803 }
804}