1use crate::canonical::{is_lower_hex, parse_canonical_line};
4use crate::{FailureCode, PromotionError, PromotionResult};
5use serde::{Deserialize, Serialize};
6use std::collections::BTreeSet;
7
8const LEGACY_REQUEST_SCHEMA: u64 = 1;
9const LEGACY_REQUEST_KIND: &str = "hashsigs-u280-hardware-promotion-request-v1";
10const REQUEST_MAXIMUM_BYTES: usize = 1024 * 1024;
11
12pub const PERFORMANCE_PROFILE: &str = "HASHSIGS_SHA256_GENERIC_V1";
14pub const PERFORMANCE_BATCH: u32 = 16_384;
16pub const MINIMUM_MEASURED_SAMPLES: u32 = 30;
18pub const BASELINE_CLOCK_HZ: u64 = 250_000_000;
20pub const MINIMUM_SIGNATURES_PER_SECOND: u64 = 500_000;
22pub const MAXIMUM_CAPTURE_CYCLES_PER_RESULT: u64 = 500;
24pub const MAXIMUM_LUTS: u64 = 200_000;
26pub const MAXIMUM_REGISTERS: u64 = 300_000;
28pub const MAXIMUM_DSPS: u64 = 4_000;
30
31#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
32#[serde(deny_unknown_fields)]
33struct FileBinding {
34 absolute_path: String,
35 bytes: u64,
36 sha256: String,
37}
38
39impl FileBinding {
40 fn absolute_path(&self) -> &str {
41 &self.absolute_path
42 }
43
44 fn validate(&self, label: &str) -> PromotionResult<()> {
45 validate_absolute_path(&self.absolute_path, label)?;
46 if self.bytes == 0 || !is_lower_hex(&self.sha256, 64, false) {
47 return Err(PromotionError::new(
48 FailureCode::Noncanonical,
49 format!("{label} has a zero length or non-canonical SHA-256"),
50 ));
51 }
52 Ok(())
53 }
54
55 fn locator(&self) -> RequestFileLocator<'_> {
56 RequestFileLocator {
57 absolute_path: &self.absolute_path,
58 bytes: self.bytes,
59 sha256: &self.sha256,
60 }
61 }
62}
63
64pub(crate) struct RequestFileLocator<'a> {
71 absolute_path: &'a str,
72 bytes: u64,
73 sha256: &'a str,
74}
75
76impl<'a> RequestFileLocator<'a> {
77 pub(crate) const fn absolute_path(&self) -> &'a str {
79 self.absolute_path
80 }
81
82 pub(crate) const fn bytes(&self) -> u64 {
84 self.bytes
85 }
86
87 pub(crate) const fn sha256(&self) -> &'a str {
89 self.sha256
90 }
91}
92
93#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
94#[serde(deny_unknown_fields)]
95struct DirectoryBinding {
96 absolute_path: String,
97}
98
99impl DirectoryBinding {
100 fn absolute_path(&self) -> &str {
101 &self.absolute_path
102 }
103
104 fn validate(&self, label: &str) -> PromotionResult<()> {
105 validate_absolute_path(&self.absolute_path, label)
106 }
107
108 fn locator(&self) -> RequestDirectoryLocator<'_> {
109 RequestDirectoryLocator {
110 absolute_path: &self.absolute_path,
111 }
112 }
113}
114
115pub(crate) struct RequestDirectoryLocator<'a> {
121 absolute_path: &'a str,
122}
123
124impl<'a> RequestDirectoryLocator<'a> {
125 pub(crate) const fn absolute_path(&self) -> &'a str {
127 self.absolute_path
128 }
129}
130
131#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
132#[serde(deny_unknown_fields)]
133struct RouteInputs {
134 staged_run_root: DirectoryBinding,
135 staged_run_manifest: FileBinding,
136 acceptance_run_root: DirectoryBinding,
137 complete_run_inventory: FileBinding,
138 accepted_250mhz_route_seal: FileBinding,
139}
140
141impl RouteInputs {
142 fn validate(&self) -> PromotionResult<()> {
143 self.staged_run_root.validate("route staged-run root")?;
144 self.staged_run_manifest
145 .validate("route staged-run manifest")?;
146 self.acceptance_run_root
147 .validate("route acceptance-run root")?;
148 self.complete_run_inventory
149 .validate("route complete-run inventory")?;
150 self.accepted_250mhz_route_seal
151 .validate("accepted 250 MHz route seal")?;
152 require_child(
153 &self.staged_run_root,
154 &self.staged_run_manifest,
155 "route staged-run manifest",
156 )?;
157 require_child(
158 &self.acceptance_run_root,
159 &self.complete_run_inventory,
160 "route complete-run inventory",
161 )?;
162 require_child(
163 &self.acceptance_run_root,
164 &self.accepted_250mhz_route_seal,
165 "accepted 250 MHz route seal",
166 )
167 }
168}
169
170#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
171#[serde(deny_unknown_fields)]
172struct TransportInputs {
173 root: DirectoryBinding,
174 top_manifest: FileBinding,
175}
176
177impl TransportInputs {
178 fn validate(&self) -> PromotionResult<()> {
179 self.root.validate("transport root")?;
180 self.top_manifest.validate("transport top manifest")?;
181 require_child(&self.root, &self.top_manifest, "transport top manifest")
182 }
183}
184
185#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
186#[serde(deny_unknown_fields)]
187struct OracleInputs {
188 result_root: DirectoryBinding,
189 result_manifest: FileBinding,
190 cli_receipt_capture: FileBinding,
191}
192
193impl OracleInputs {
194 fn validate(&self) -> PromotionResult<()> {
195 self.result_root.validate("oracle result root")?;
196 self.result_manifest.validate("oracle result manifest")?;
197 self.cli_receipt_capture
198 .validate("oracle CLI receipt capture")?;
199 require_child(
200 &self.result_root,
201 &self.result_manifest,
202 "oracle result manifest",
203 )
204 }
205}
206
207#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
208#[serde(deny_unknown_fields)]
209struct CoordinationInputs {
210 complete_burn_history_snapshot: FileBinding,
211 history_snapshot_manifest: FileBinding,
212 selected_burn_receipt: FileBinding,
213}
214
215impl CoordinationInputs {
216 fn validate(&self) -> PromotionResult<()> {
217 self.complete_burn_history_snapshot
218 .validate("complete coordination burn history")?;
219 self.history_snapshot_manifest
220 .validate("coordination history manifest")?;
221 self.selected_burn_receipt
222 .validate("selected coordination burn receipt")
223 }
224}
225
226#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
227#[serde(deny_unknown_fields)]
228struct PromotionPolicy {
229 profile: String,
230 batch: u32,
231 minimum_measured_samples: u32,
232 baseline_clock_hz: u64,
233 minimum_signatures_per_second: u64,
234 maximum_capture_cycles_per_result: u64,
235 maximum_luts: u64,
236 maximum_registers: u64,
237 maximum_dsps: u64,
238}
239
240impl PromotionPolicy {
241 fn validate_fixed(&self) -> PromotionResult<()> {
242 let fixed = self.profile == PERFORMANCE_PROFILE
243 && self.batch == PERFORMANCE_BATCH
244 && self.minimum_measured_samples == MINIMUM_MEASURED_SAMPLES
245 && self.baseline_clock_hz == BASELINE_CLOCK_HZ
246 && self.minimum_signatures_per_second == MINIMUM_SIGNATURES_PER_SECOND
247 && self.maximum_capture_cycles_per_result == MAXIMUM_CAPTURE_CYCLES_PER_RESULT
248 && self.maximum_luts == MAXIMUM_LUTS
249 && self.maximum_registers == MAXIMUM_REGISTERS
250 && self.maximum_dsps == MAXIMUM_DSPS;
251 if !fixed {
252 return Err(PromotionError::new(
253 FailureCode::Schema,
254 "promotion request does not contain the fixed schema-1 policy",
255 ));
256 }
257 Ok(())
258 }
259}
260
261#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
262#[serde(deny_unknown_fields)]
263struct LegacyPromotionRequestWire {
264 schema: u64,
265 kind: String,
266 run_id: String,
267 route: RouteInputs,
268 transport: TransportInputs,
269 oracle: OracleInputs,
270 coordination: CoordinationInputs,
271 policy: PromotionPolicy,
272}
273
274impl LegacyPromotionRequestWire {
275 fn validate(&self) -> PromotionResult<()> {
276 if self.schema != LEGACY_REQUEST_SCHEMA || self.kind != LEGACY_REQUEST_KIND {
277 return Err(PromotionError::new(
278 FailureCode::Schema,
279 "promotion request schema or kind is unsupported",
280 ));
281 }
282 if !is_lower_hex(&self.run_id, 32, true) {
283 return Err(PromotionError::new(
284 FailureCode::Noncanonical,
285 "promotion run_id must be a nonzero 128-bit lowercase hex value",
286 ));
287 }
288 self.route.validate()?;
289 self.transport.validate()?;
290 self.oracle.validate()?;
291 self.coordination.validate()?;
292 self.policy.validate_fixed()?;
293
294 let paths = [
295 self.route.staged_run_root.absolute_path(),
296 self.route.staged_run_manifest.absolute_path(),
297 self.route.acceptance_run_root.absolute_path(),
298 self.route.complete_run_inventory.absolute_path(),
299 self.route.accepted_250mhz_route_seal.absolute_path(),
300 self.transport.root.absolute_path(),
301 self.transport.top_manifest.absolute_path(),
302 self.oracle.result_root.absolute_path(),
303 self.oracle.result_manifest.absolute_path(),
304 self.oracle.cli_receipt_capture.absolute_path(),
305 self.coordination
306 .complete_burn_history_snapshot
307 .absolute_path(),
308 self.coordination.history_snapshot_manifest.absolute_path(),
309 self.coordination.selected_burn_receipt.absolute_path(),
310 ];
311 let unique = paths.into_iter().collect::<BTreeSet<_>>();
312 if unique.len() != paths.len() {
313 return Err(PromotionError::new(
314 FailureCode::Schema,
315 "promotion request aliases two independently bound input paths",
316 ));
317 }
318 Ok(())
319 }
320}
321
322#[derive(Clone, Debug, Eq, PartialEq)]
329pub struct PromotionRequest {
330 wire: LegacyPromotionRequestWire,
331}
332
333impl PromotionRequest {
334 pub fn parse_canonical(bytes: &[u8]) -> PromotionResult<Self> {
341 if caller_supplied_terminal_claim(bytes) {
342 return Err(PromotionError::new(
343 FailureCode::PrematureClaim,
344 "promotion requests cannot contain terminal success fields",
345 ));
346 }
347 let request: LegacyPromotionRequestWire =
348 parse_canonical_line(bytes, REQUEST_MAXIMUM_BYTES, "promotion request")?;
349 request.validate()?;
350 Ok(Self { wire: request })
351 }
352
353 #[must_use]
355 pub fn run_id(&self) -> &str {
356 &self.wire.run_id
357 }
358
359 pub(crate) fn coordination_history_binding(&self) -> (u64, &str) {
360 let locators = self.coordination_locators();
361 let history = locators.complete_burn_history_snapshot();
362 (history.bytes(), history.sha256())
363 }
364
365 pub(crate) fn selected_burn_binding(&self) -> (u64, &str) {
366 let locators = self.coordination_locators();
367 let selected = locators.selected_burn_receipt();
368 (selected.bytes(), selected.sha256())
369 }
370
371 pub(crate) fn route_locators(&self) -> RouteRequestLocators<'_> {
375 RouteRequestLocators {
376 staged_run_root: self.wire.route.staged_run_root.locator(),
377 staged_run_manifest: self.wire.route.staged_run_manifest.locator(),
378 acceptance_run_root: self.wire.route.acceptance_run_root.locator(),
379 complete_run_inventory: self.wire.route.complete_run_inventory.locator(),
380 accepted_250mhz_route_seal: self.wire.route.accepted_250mhz_route_seal.locator(),
381 }
382 }
383
384 pub(crate) fn coordination_locators(&self) -> CoordinationRequestLocators<'_> {
388 CoordinationRequestLocators {
389 complete_burn_history_snapshot: self
390 .wire
391 .coordination
392 .complete_burn_history_snapshot
393 .locator(),
394 history_snapshot_manifest: self.wire.coordination.history_snapshot_manifest.locator(),
395 selected_burn_receipt: self.wire.coordination.selected_burn_receipt.locator(),
396 }
397 }
398
399 #[allow(dead_code)] pub(crate) fn transport_locators(&self) -> TransportRequestLocators<'_> {
405 TransportRequestLocators {
406 root: self.wire.transport.root.locator(),
407 top_manifest: self.wire.transport.top_manifest.locator(),
408 }
409 }
410
411 #[allow(dead_code)] pub(crate) fn oracle_locators(&self) -> OracleRequestLocators<'_> {
417 OracleRequestLocators {
418 result_root: self.wire.oracle.result_root.locator(),
419 result_manifest: self.wire.oracle.result_manifest.locator(),
420 cli_receipt_capture: self.wire.oracle.cli_receipt_capture.locator(),
421 }
422 }
423}
424
425pub(crate) struct RouteRequestLocators<'a> {
432 staged_run_root: RequestDirectoryLocator<'a>,
433 staged_run_manifest: RequestFileLocator<'a>,
434 acceptance_run_root: RequestDirectoryLocator<'a>,
435 complete_run_inventory: RequestFileLocator<'a>,
436 accepted_250mhz_route_seal: RequestFileLocator<'a>,
437}
438
439impl<'a> RouteRequestLocators<'a> {
440 pub(crate) const fn staged_run_root(&self) -> &RequestDirectoryLocator<'a> {
442 &self.staged_run_root
443 }
444
445 pub(crate) const fn staged_run_manifest(&self) -> &RequestFileLocator<'a> {
447 &self.staged_run_manifest
448 }
449
450 pub(crate) const fn acceptance_run_root(&self) -> &RequestDirectoryLocator<'a> {
452 &self.acceptance_run_root
453 }
454
455 pub(crate) const fn complete_run_inventory(&self) -> &RequestFileLocator<'a> {
457 &self.complete_run_inventory
458 }
459
460 pub(crate) const fn accepted_250mhz_route_seal(&self) -> &RequestFileLocator<'a> {
462 &self.accepted_250mhz_route_seal
463 }
464}
465
466#[allow(dead_code)] pub(crate) struct TransportRequestLocators<'a> {
474 root: RequestDirectoryLocator<'a>,
475 top_manifest: RequestFileLocator<'a>,
476}
477
478#[allow(dead_code)] impl<'a> TransportRequestLocators<'a> {
480 pub(crate) const fn root(&self) -> &RequestDirectoryLocator<'a> {
482 &self.root
483 }
484
485 pub(crate) const fn top_manifest(&self) -> &RequestFileLocator<'a> {
487 &self.top_manifest
488 }
489}
490
491#[allow(dead_code)] pub(crate) struct OracleRequestLocators<'a> {
499 result_root: RequestDirectoryLocator<'a>,
500 result_manifest: RequestFileLocator<'a>,
501 cli_receipt_capture: RequestFileLocator<'a>,
502}
503
504#[allow(dead_code)] impl<'a> OracleRequestLocators<'a> {
506 pub(crate) const fn result_root(&self) -> &RequestDirectoryLocator<'a> {
508 &self.result_root
509 }
510
511 pub(crate) const fn result_manifest(&self) -> &RequestFileLocator<'a> {
513 &self.result_manifest
514 }
515
516 pub(crate) const fn cli_receipt_capture(&self) -> &RequestFileLocator<'a> {
518 &self.cli_receipt_capture
519 }
520}
521
522pub(crate) struct CoordinationRequestLocators<'a> {
528 complete_burn_history_snapshot: RequestFileLocator<'a>,
529 history_snapshot_manifest: RequestFileLocator<'a>,
530 selected_burn_receipt: RequestFileLocator<'a>,
531}
532
533impl<'a> CoordinationRequestLocators<'a> {
534 pub(crate) const fn complete_burn_history_snapshot(&self) -> &RequestFileLocator<'a> {
536 &self.complete_burn_history_snapshot
537 }
538
539 pub(crate) const fn history_snapshot_manifest(&self) -> &RequestFileLocator<'a> {
541 &self.history_snapshot_manifest
542 }
543
544 pub(crate) const fn selected_burn_receipt(&self) -> &RequestFileLocator<'a> {
546 &self.selected_burn_receipt
547 }
548}
549
550fn caller_supplied_terminal_claim(bytes: &[u8]) -> bool {
551 let Ok(serde_json::Value::Object(object)) = serde_json::from_slice(bytes) else {
552 return false;
553 };
554 [
555 "hardware_execution",
556 "card_validated",
557 "cryptographic_outputs_valid",
558 "performance_validated",
559 "hardware_completion_promotable",
560 ]
561 .into_iter()
562 .any(|field| object.contains_key(field))
563}
564
565fn require_child(root: &DirectoryBinding, file: &FileBinding, label: &str) -> PromotionResult<()> {
566 let prefix = format!("{}/", root.absolute_path());
567 if !file.absolute_path().starts_with(&prefix) {
568 return Err(PromotionError::new(
569 FailureCode::Schema,
570 format!("{label} is not descriptor-rooted beneath its declared directory"),
571 ));
572 }
573 Ok(())
574}
575
576pub(crate) fn validate_absolute_path(value: &str, label: &str) -> PromotionResult<()> {
577 let canonical = value.starts_with('/')
578 && value.len() > 1
579 && !value.ends_with('/')
580 && !value.contains("//")
581 && !value.contains('\0')
582 && value
583 .split('/')
584 .skip(1)
585 .all(|component| !component.is_empty() && component != "." && component != "..");
586 if !canonical {
587 return Err(PromotionError::new(
588 FailureCode::Noncanonical,
589 format!("{label} is not a traversal-free canonical absolute path"),
590 ));
591 }
592 Ok(())
593}
594
595#[cfg(test)]
596pub(crate) fn synthetic_request(run_id: &str) -> PromotionRequest {
597 let file = |path: &str, salt: char| FileBinding {
598 absolute_path: path.to_owned(),
599 bytes: 10,
600 sha256: salt.to_string().repeat(64),
601 };
602 PromotionRequest {
603 wire: LegacyPromotionRequestWire {
604 schema: LEGACY_REQUEST_SCHEMA,
605 kind: LEGACY_REQUEST_KIND.to_owned(),
606 run_id: run_id.to_owned(),
607 route: RouteInputs {
608 staged_run_root: DirectoryBinding {
609 absolute_path: "/evidence/route-stage".to_owned(),
610 },
611 staged_run_manifest: file("/evidence/route-stage/SHA256SUMS", '1'),
612 acceptance_run_root: DirectoryBinding {
613 absolute_path: "/evidence/route-acceptance".to_owned(),
614 },
615 complete_run_inventory: file("/evidence/route-acceptance/inventory.json", '2'),
616 accepted_250mhz_route_seal: file("/evidence/route-acceptance/route-seal.json", '3'),
617 },
618 transport: TransportInputs {
619 root: DirectoryBinding {
620 absolute_path: "/evidence/transport".to_owned(),
621 },
622 top_manifest: file("/evidence/transport/SHA256SUMS", '4'),
623 },
624 oracle: OracleInputs {
625 result_root: DirectoryBinding {
626 absolute_path: "/evidence/oracle".to_owned(),
627 },
628 result_manifest: file("/evidence/oracle/SHA256SUMS", '5'),
629 cli_receipt_capture: file("/evidence/oracle-receipt.json", '6'),
630 },
631 coordination: CoordinationInputs {
632 complete_burn_history_snapshot: file("/evidence/coordination-history.jsonl", '7'),
633 history_snapshot_manifest: file("/evidence/coordination-history.SHA256SUMS", '8'),
634 selected_burn_receipt: file("/evidence/coordination-burn.json", '9'),
635 },
636 policy: PromotionPolicy {
637 profile: PERFORMANCE_PROFILE.to_owned(),
638 batch: PERFORMANCE_BATCH,
639 minimum_measured_samples: MINIMUM_MEASURED_SAMPLES,
640 baseline_clock_hz: BASELINE_CLOCK_HZ,
641 minimum_signatures_per_second: MINIMUM_SIGNATURES_PER_SECOND,
642 maximum_capture_cycles_per_result: MAXIMUM_CAPTURE_CYCLES_PER_RESULT,
643 maximum_luts: MAXIMUM_LUTS,
644 maximum_registers: MAXIMUM_REGISTERS,
645 maximum_dsps: MAXIMUM_DSPS,
646 },
647 },
648 }
649}
650
651#[cfg(test)]
652pub(crate) fn synthetic_request_with_coordination(
653 run_id: &str,
654 history: &[u8],
655 selected_receipt: &[u8],
656) -> PromotionRequest {
657 let mut request = synthetic_request(run_id);
658 request
659 .wire
660 .coordination
661 .complete_burn_history_snapshot
662 .bytes = u64::try_from(history.len()).unwrap();
663 request
664 .wire
665 .coordination
666 .complete_burn_history_snapshot
667 .sha256 = crate::canonical::sha256_hex(history);
668 request.wire.coordination.selected_burn_receipt.bytes =
669 u64::try_from(selected_receipt.len()).unwrap();
670 request.wire.coordination.selected_burn_receipt.sha256 =
671 crate::canonical::sha256_hex(selected_receipt);
672 request
673}
674
675#[cfg(test)]
676pub(crate) fn synthetic_request_with_coordination_snapshot(
677 run_id: &str,
678 files: [(&str, &[u8]); 3],
679) -> PromotionRequest {
680 let mut request = synthetic_request(run_id);
681 request
682 .wire
683 .coordination
684 .complete_burn_history_snapshot
685 .absolute_path = files[0].0.to_owned();
686 request
687 .wire
688 .coordination
689 .complete_burn_history_snapshot
690 .bytes = u64::try_from(files[0].1.len()).unwrap();
691 request
692 .wire
693 .coordination
694 .complete_burn_history_snapshot
695 .sha256 = crate::canonical::sha256_hex(files[0].1);
696 request
697 .wire
698 .coordination
699 .history_snapshot_manifest
700 .absolute_path = files[1].0.to_owned();
701 request.wire.coordination.history_snapshot_manifest.bytes =
702 u64::try_from(files[1].1.len()).unwrap();
703 request.wire.coordination.history_snapshot_manifest.sha256 =
704 crate::canonical::sha256_hex(files[1].1);
705 request
706 .wire
707 .coordination
708 .selected_burn_receipt
709 .absolute_path = files[2].0.to_owned();
710 request.wire.coordination.selected_burn_receipt.bytes =
711 u64::try_from(files[2].1.len()).unwrap();
712 request.wire.coordination.selected_burn_receipt.sha256 =
713 crate::canonical::sha256_hex(files[2].1);
714 request
715}
716
717#[cfg(test)]
718mod tests {
719 use super::{LegacyPromotionRequestWire, PromotionRequest, synthetic_request};
720 use crate::FailureCode;
721 use crate::canonical::canonical_line;
722
723 fn fixture() -> LegacyPromotionRequestWire {
724 synthetic_request(&"1".repeat(32)).wire
725 }
726
727 #[test]
728 fn exact_request_passes_and_success_fields_are_premature_claims() {
729 let canonical = canonical_line(&fixture()).unwrap();
730 assert_eq!(
731 PromotionRequest::parse_canonical(&canonical).unwrap().wire,
732 fixture()
733 );
734
735 let mut value: serde_json::Value = serde_json::from_slice(&canonical).unwrap();
736 value["hardware_execution"] = serde_json::Value::Bool(false);
737 let attempted = serde_json::to_vec(&value).unwrap();
738 assert_eq!(
739 PromotionRequest::parse_canonical(&attempted)
740 .unwrap_err()
741 .code(),
742 FailureCode::PrematureClaim
743 );
744 }
745
746 #[test]
747 fn duplicates_reordering_and_relaxed_policy_fail_closed() {
748 let canonical = String::from_utf8(canonical_line(&fixture()).unwrap()).unwrap();
749 let duplicate = canonical.replacen("{\"schema\":1", "{\"schema\":1,\"schema\":1", 1);
750 assert!(PromotionRequest::parse_canonical(duplicate.as_bytes()).is_err());
751
752 let reordered = canonical.replacen(
753 "{\"schema\":1,\"kind\":\"hashsigs-u280-hardware-promotion-request-v1\"",
754 "{\"kind\":\"hashsigs-u280-hardware-promotion-request-v1\",\"schema\":1",
755 1,
756 );
757 assert_eq!(
758 PromotionRequest::parse_canonical(reordered.as_bytes())
759 .unwrap_err()
760 .code(),
761 FailureCode::Noncanonical
762 );
763
764 let relaxed = canonical.replacen(
765 "\"minimum_signatures_per_second\":500000",
766 "\"minimum_signatures_per_second\":499999",
767 1,
768 );
769 assert_eq!(
770 PromotionRequest::parse_canonical(relaxed.as_bytes())
771 .unwrap_err()
772 .code(),
773 FailureCode::Schema
774 );
775
776 let unsupported_schema = canonical.replacen("\"schema\":1", "\"schema\":2", 1);
777 assert_eq!(
778 PromotionRequest::parse_canonical(unsupported_schema.as_bytes())
779 .unwrap_err()
780 .code(),
781 FailureCode::Schema
782 );
783 }
784
785 #[test]
786 fn coordination_locators_are_exact_request_inputs_not_proofs() {
787 let request = synthetic_request(&"1".repeat(32));
788 let locators = request.coordination_locators();
789 let history = locators.complete_burn_history_snapshot();
790 assert_eq!(
791 history.absolute_path(),
792 "/evidence/coordination-history.jsonl"
793 );
794 assert_eq!(history.bytes(), 10);
795 assert_eq!(history.sha256(), "7".repeat(64));
796 assert_eq!(
797 locators.history_snapshot_manifest().absolute_path(),
798 "/evidence/coordination-history.SHA256SUMS"
799 );
800 assert_eq!(
801 locators.selected_burn_receipt().absolute_path(),
802 "/evidence/coordination-burn.json"
803 );
804 }
805
806 #[test]
807 fn route_locators_are_exact_request_inputs_not_proofs() {
808 let request = synthetic_request(&"1".repeat(32));
809 let locators = request.route_locators();
810 assert_eq!(
811 locators.staged_run_root().absolute_path(),
812 "/evidence/route-stage"
813 );
814 assert_eq!(
815 locators.staged_run_manifest().absolute_path(),
816 "/evidence/route-stage/SHA256SUMS"
817 );
818 assert_eq!(
819 locators.acceptance_run_root().absolute_path(),
820 "/evidence/route-acceptance"
821 );
822 assert_eq!(locators.complete_run_inventory().bytes(), 10);
823 assert_eq!(locators.complete_run_inventory().sha256(), "2".repeat(64));
824 assert_eq!(
825 locators.accepted_250mhz_route_seal().absolute_path(),
826 "/evidence/route-acceptance/route-seal.json"
827 );
828 }
829
830 #[test]
831 fn transport_locators_are_exact_request_inputs_not_proofs() {
832 let request = synthetic_request(&"1".repeat(32));
833 let locators = request.transport_locators();
834 assert_eq!(locators.root().absolute_path(), "/evidence/transport");
835 assert_eq!(
836 locators.top_manifest().absolute_path(),
837 "/evidence/transport/SHA256SUMS"
838 );
839 assert_eq!(locators.top_manifest().bytes(), 10);
840 assert_eq!(locators.top_manifest().sha256(), "4".repeat(64));
841 }
842
843 #[test]
844 fn oracle_locators_are_exact_request_inputs_not_proofs() {
845 let request = synthetic_request(&"1".repeat(32));
846 let locators = request.oracle_locators();
847 assert_eq!(locators.result_root().absolute_path(), "/evidence/oracle");
848 assert_eq!(
849 locators.result_manifest().absolute_path(),
850 "/evidence/oracle/SHA256SUMS"
851 );
852 assert_eq!(locators.result_manifest().bytes(), 10);
853 assert_eq!(locators.result_manifest().sha256(), "5".repeat(64));
854 assert_eq!(
855 locators.cli_receipt_capture().absolute_path(),
856 "/evidence/oracle-receipt.json"
857 );
858 assert_eq!(locators.cli_receipt_capture().bytes(), 10);
859 assert_eq!(locators.cli_receipt_capture().sha256(), "6".repeat(64));
860 }
861}