1use crate::error::{
8 require_id, require_non_empty, require_non_empty_slice, require_schema_version,
9 require_valid_timestamp, EffectRuntimeValidationError, EffectValidationResult,
10};
11use crate::v25::{V25ConstitutionCitation, V25ObligationRefs};
12use crate::vocab::{
13 BlastRadiusCeilingV1, BudgetSufficiencyResultV1, CheckResultV1, CloseMidflightBehaviorV1,
14 CommitAtomicityV1, EffectClassV1, PublicationStatusV1, RetryOwnerV1, ReversibilityClassV1,
15 RunModeV1,
16};
17use schemars::JsonSchema;
18use serde::{Deserialize, Serialize};
19use stack_ids::{
20 ApprovalGrantId, EffectCommitDecisionId, EffectIntentId, EffectPreflightReportId,
21 EffectWindowId, ExecutionPermitId,
22};
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
26#[serde(rename_all = "snake_case")]
27pub enum EffectPreflightDispositionV1 {
28 Blocked,
29 AdvisoryOnly,
30 CommitEligible,
31}
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
35#[serde(rename_all = "snake_case")]
36pub enum EffectCommitDispositionV1 {
37 Denied,
38 AdvisoryOnly,
39 Authorized,
40}
41
42#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
44pub struct EffectWindowV1 {
45 pub schema_version: String,
46 pub effect_window_id: EffectWindowId,
47 pub earliest_start: String,
48 pub latest_admissible_start: String,
49 pub timeout_at: String,
50 pub commit_atomicity: CommitAtomicityV1,
51 pub retry_owner: RetryOwnerV1,
52 pub close_midflight_behavior: CloseMidflightBehaviorV1,
53}
54
55#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
60pub struct EffectIntentV1 {
61 pub schema_version: String,
62 pub effect_intent_id: EffectIntentId,
63 pub effect_class: EffectClassV1,
64 pub target_surface: String,
65 pub intended_outcome: String,
66 pub initiating_artifact_refs: Vec<String>,
67 pub required_capability_class: String,
68 pub allowed_scope: String,
69 pub blast_radius_ceiling: BlastRadiusCeilingV1,
70 pub reversibility_class: ReversibilityClassV1,
71 pub idempotency_key: String,
72 pub execution_window_id: EffectWindowId,
73 pub observability_obligation_refs: Vec<String>,
74 pub approval_refs: Vec<String>,
75 pub run_mode: RunModeV1,
76 pub publication_status: PublicationStatusV1,
77}
78
79#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
84pub struct EffectPreflightReportV1 {
85 pub schema_version: String,
86 pub effect_preflight_report_id: EffectPreflightReportId,
87 pub effect_intent_id: EffectIntentId,
88 #[serde(flatten)]
91 pub citation: V25ConstitutionCitation,
92 #[serde(flatten)]
93 pub obligation_refs: V25ObligationRefs,
94 pub input_integrity_result: CheckResultV1,
95 pub admission_check_result: CheckResultV1,
96 pub dependency_reachability_result: CheckResultV1,
97 pub budget_sufficiency_result: BudgetSufficiencyResultV1,
98 pub disposition: EffectPreflightDispositionV1,
99 pub generated_at: String,
100 pub notes: Vec<String>,
101}
102
103#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
108pub struct EffectCommitDecisionV1 {
109 pub schema_version: String,
110 pub effect_commit_decision_id: EffectCommitDecisionId,
111 pub effect_intent_id: EffectIntentId,
112 pub effect_preflight_report_id: EffectPreflightReportId,
113 #[serde(flatten)]
116 pub citation: V25ConstitutionCitation,
117 #[serde(flatten)]
118 pub obligation_refs: V25ObligationRefs,
119 #[serde(default)]
120 pub approval_obligation_refs: Vec<String>,
121 pub decision_state: EffectCommitDispositionV1,
122 pub approver_refs: Vec<String>,
123 #[serde(default, skip_serializing_if = "Option::is_none")]
124 pub execution_permit_id: Option<ExecutionPermitId>,
125 #[serde(default, skip_serializing_if = "Option::is_none")]
126 pub approval_grant_id: Option<ApprovalGrantId>,
127 pub refusal_reason: String,
128 pub generated_at: String,
129}
130
131impl EffectWindowV1 {
132 pub const SCHEMA_VERSION: &'static str = "EffectWindowV1";
133
134 pub fn builder(
136 effect_window_id: EffectWindowId,
137 earliest_start: impl Into<String>,
138 latest_admissible_start: impl Into<String>,
139 timeout_at: impl Into<String>,
140 commit_atomicity: CommitAtomicityV1,
141 retry_owner: RetryOwnerV1,
142 close_midflight_behavior: CloseMidflightBehaviorV1,
143 ) -> EffectWindowV1Builder {
144 EffectWindowV1Builder::new(
145 effect_window_id,
146 earliest_start,
147 latest_admissible_start,
148 timeout_at,
149 commit_atomicity,
150 retry_owner,
151 close_midflight_behavior,
152 )
153 }
154
155 pub fn validate(&self) -> EffectValidationResult {
157 require_schema_version("EffectWindowV1", Self::SCHEMA_VERSION, &self.schema_version)?;
158 require_id(&self.effect_window_id, "effect_window_id")?;
159 require_valid_timestamp(&self.earliest_start, "earliest_start")?;
160 require_valid_timestamp(&self.latest_admissible_start, "latest_admissible_start")?;
161 require_valid_timestamp(&self.timeout_at, "timeout_at")?;
162 Ok(())
163 }
164}
165
166#[derive(Debug, Clone)]
168pub struct EffectWindowV1Builder {
169 effect_window_id: EffectWindowId,
170 earliest_start: String,
171 latest_admissible_start: String,
172 timeout_at: String,
173 commit_atomicity: CommitAtomicityV1,
174 retry_owner: RetryOwnerV1,
175 close_midflight_behavior: CloseMidflightBehaviorV1,
176}
177
178impl EffectWindowV1Builder {
179 pub fn new(
181 effect_window_id: EffectWindowId,
182 earliest_start: impl Into<String>,
183 latest_admissible_start: impl Into<String>,
184 timeout_at: impl Into<String>,
185 commit_atomicity: CommitAtomicityV1,
186 retry_owner: RetryOwnerV1,
187 close_midflight_behavior: CloseMidflightBehaviorV1,
188 ) -> Self {
189 Self {
190 effect_window_id,
191 earliest_start: earliest_start.into(),
192 latest_admissible_start: latest_admissible_start.into(),
193 timeout_at: timeout_at.into(),
194 commit_atomicity,
195 retry_owner,
196 close_midflight_behavior,
197 }
198 }
199
200 pub fn build(self) -> Result<EffectWindowV1, EffectRuntimeValidationError> {
202 let value = EffectWindowV1 {
203 schema_version: EffectWindowV1::SCHEMA_VERSION.to_string(),
204 effect_window_id: self.effect_window_id,
205 earliest_start: self.earliest_start,
206 latest_admissible_start: self.latest_admissible_start,
207 timeout_at: self.timeout_at,
208 commit_atomicity: self.commit_atomicity,
209 retry_owner: self.retry_owner,
210 close_midflight_behavior: self.close_midflight_behavior,
211 };
212 value.validate()?;
213 Ok(value)
214 }
215}
216
217impl EffectIntentV1 {
218 pub const SCHEMA_VERSION: &'static str = "EffectIntentV1";
219
220 #[allow(clippy::too_many_arguments)]
222 pub fn builder(
223 effect_intent_id: EffectIntentId,
224 effect_class: EffectClassV1,
225 target_surface: impl Into<String>,
226 intended_outcome: impl Into<String>,
227 initiating_artifact_refs: Vec<String>,
228 required_capability_class: impl Into<String>,
229 allowed_scope: impl Into<String>,
230 blast_radius_ceiling: BlastRadiusCeilingV1,
231 reversibility_class: ReversibilityClassV1,
232 idempotency_key: impl Into<String>,
233 execution_window_id: EffectWindowId,
234 observability_obligation_refs: Vec<String>,
235 approval_refs: Vec<String>,
236 run_mode: RunModeV1,
237 publication_status: PublicationStatusV1,
238 ) -> EffectIntentV1Builder {
239 EffectIntentV1Builder::new(
240 effect_intent_id,
241 effect_class,
242 target_surface,
243 intended_outcome,
244 initiating_artifact_refs,
245 required_capability_class,
246 allowed_scope,
247 blast_radius_ceiling,
248 reversibility_class,
249 idempotency_key,
250 execution_window_id,
251 observability_obligation_refs,
252 approval_refs,
253 run_mode,
254 publication_status,
255 )
256 }
257
258 pub fn validate(&self) -> EffectValidationResult {
260 require_schema_version("EffectIntentV1", Self::SCHEMA_VERSION, &self.schema_version)?;
261 require_id(&self.effect_intent_id, "effect_intent_id")?;
262 require_non_empty(&self.target_surface, "target_surface")?;
263 require_non_empty(&self.intended_outcome, "intended_outcome")?;
264 require_non_empty_slice(&self.initiating_artifact_refs, "initiating_artifact_refs")?;
265 require_non_empty(&self.required_capability_class, "required_capability_class")?;
266 require_non_empty(&self.allowed_scope, "allowed_scope")?;
267 require_non_empty(&self.idempotency_key, "idempotency_key")?;
268 require_id(&self.execution_window_id, "execution_window_id")?;
269 require_non_empty_slice(
270 &self.observability_obligation_refs,
271 "observability_obligation_refs",
272 )?;
273 if matches!(self.run_mode, RunModeV1::Live) {
274 require_non_empty_slice(&self.approval_refs, "approval_refs")?;
275 }
276 Ok(())
277 }
278}
279
280#[derive(Debug, Clone)]
282pub struct EffectIntentV1Builder {
283 effect_intent_id: EffectIntentId,
284 effect_class: EffectClassV1,
285 target_surface: String,
286 intended_outcome: String,
287 initiating_artifact_refs: Vec<String>,
288 required_capability_class: String,
289 allowed_scope: String,
290 blast_radius_ceiling: BlastRadiusCeilingV1,
291 reversibility_class: ReversibilityClassV1,
292 idempotency_key: String,
293 execution_window_id: EffectWindowId,
294 observability_obligation_refs: Vec<String>,
295 approval_refs: Vec<String>,
296 run_mode: RunModeV1,
297 publication_status: PublicationStatusV1,
298}
299
300impl EffectIntentV1Builder {
301 #[allow(clippy::too_many_arguments)]
303 pub fn new(
304 effect_intent_id: EffectIntentId,
305 effect_class: EffectClassV1,
306 target_surface: impl Into<String>,
307 intended_outcome: impl Into<String>,
308 initiating_artifact_refs: Vec<String>,
309 required_capability_class: impl Into<String>,
310 allowed_scope: impl Into<String>,
311 blast_radius_ceiling: BlastRadiusCeilingV1,
312 reversibility_class: ReversibilityClassV1,
313 idempotency_key: impl Into<String>,
314 execution_window_id: EffectWindowId,
315 observability_obligation_refs: Vec<String>,
316 approval_refs: Vec<String>,
317 run_mode: RunModeV1,
318 publication_status: PublicationStatusV1,
319 ) -> Self {
320 Self {
321 effect_intent_id,
322 effect_class,
323 target_surface: target_surface.into(),
324 intended_outcome: intended_outcome.into(),
325 initiating_artifact_refs,
326 required_capability_class: required_capability_class.into(),
327 allowed_scope: allowed_scope.into(),
328 blast_radius_ceiling,
329 reversibility_class,
330 idempotency_key: idempotency_key.into(),
331 execution_window_id,
332 observability_obligation_refs,
333 approval_refs,
334 run_mode,
335 publication_status,
336 }
337 }
338
339 pub fn with_effect_class(mut self, effect_class: EffectClassV1) -> Self {
341 self.effect_class = effect_class;
342 self
343 }
344
345 pub fn with_blast_radius_ceiling(mut self, ceiling: BlastRadiusCeilingV1) -> Self {
347 self.blast_radius_ceiling = ceiling;
348 self
349 }
350
351 pub fn with_reversibility_class(mut self, class: ReversibilityClassV1) -> Self {
353 self.reversibility_class = class;
354 self
355 }
356
357 pub fn with_run_mode(mut self, mode: RunModeV1) -> Self {
359 self.run_mode = mode;
360 self
361 }
362
363 pub fn with_publication_status(mut self, status: PublicationStatusV1) -> Self {
365 self.publication_status = status;
366 self
367 }
368
369 pub fn with_approval_refs(mut self, refs: Vec<String>) -> Self {
371 self.approval_refs = refs;
372 self
373 }
374
375 pub fn build(self) -> Result<EffectIntentV1, EffectRuntimeValidationError> {
377 let value = EffectIntentV1 {
378 schema_version: EffectIntentV1::SCHEMA_VERSION.to_string(),
379 effect_intent_id: self.effect_intent_id,
380 effect_class: self.effect_class,
381 target_surface: self.target_surface,
382 intended_outcome: self.intended_outcome,
383 initiating_artifact_refs: self.initiating_artifact_refs,
384 required_capability_class: self.required_capability_class,
385 allowed_scope: self.allowed_scope,
386 blast_radius_ceiling: self.blast_radius_ceiling,
387 reversibility_class: self.reversibility_class,
388 idempotency_key: self.idempotency_key,
389 execution_window_id: self.execution_window_id,
390 observability_obligation_refs: self.observability_obligation_refs,
391 approval_refs: self.approval_refs,
392 run_mode: self.run_mode,
393 publication_status: self.publication_status,
394 };
395 value.validate()?;
396 Ok(value)
397 }
398}
399
400impl EffectPreflightReportV1 {
401 pub const SCHEMA_VERSION: &'static str = "EffectPreflightReportV1";
402
403 #[allow(clippy::too_many_arguments)]
405 pub fn builder(
406 effect_preflight_report_id: EffectPreflightReportId,
407 effect_intent_id: EffectIntentId,
408 citation: V25ConstitutionCitation,
409 obligation_refs: V25ObligationRefs,
410 input_integrity_result: CheckResultV1,
411 admission_check_result: CheckResultV1,
412 dependency_reachability_result: CheckResultV1,
413 budget_sufficiency_result: BudgetSufficiencyResultV1,
414 disposition: EffectPreflightDispositionV1,
415 generated_at: impl Into<String>,
416 notes: Vec<String>,
417 ) -> EffectPreflightReportV1Builder {
418 EffectPreflightReportV1Builder::new(
419 effect_preflight_report_id,
420 effect_intent_id,
421 citation,
422 obligation_refs,
423 input_integrity_result,
424 admission_check_result,
425 dependency_reachability_result,
426 budget_sufficiency_result,
427 disposition,
428 generated_at,
429 notes,
430 )
431 }
432
433 pub fn validate(&self) -> EffectValidationResult {
435 require_schema_version(
436 "EffectPreflightReportV1",
437 Self::SCHEMA_VERSION,
438 &self.schema_version,
439 )?;
440 require_id(
441 &self.effect_preflight_report_id,
442 "effect_preflight_report_id",
443 )?;
444 require_id(&self.effect_intent_id, "effect_intent_id")?;
445 require_non_empty(&self.generated_at, "generated_at")?;
446 require_non_empty_slice(
447 &self.obligation_refs.required_obligation_refs,
448 "required_obligation_refs",
449 )?;
450 if matches!(
451 self.disposition,
452 EffectPreflightDispositionV1::CommitEligible
453 ) && !matches!(
454 (
455 self.input_integrity_result,
456 self.admission_check_result,
457 self.dependency_reachability_result,
458 self.budget_sufficiency_result,
459 ),
460 (
461 CheckResultV1::Pass,
462 CheckResultV1::Pass,
463 CheckResultV1::Pass,
464 BudgetSufficiencyResultV1::Sufficient,
465 )
466 ) {
467 return Err(EffectRuntimeValidationError::InvalidState(
468 "commit-eligible preflight requires pass/pass/pass/sufficient checks",
469 ));
470 }
471 Ok(())
472 }
473}
474
475#[derive(Debug, Clone)]
477pub struct EffectPreflightReportV1Builder {
478 effect_preflight_report_id: EffectPreflightReportId,
479 effect_intent_id: EffectIntentId,
480 citation: V25ConstitutionCitation,
481 obligation_refs: V25ObligationRefs,
482 input_integrity_result: CheckResultV1,
483 admission_check_result: CheckResultV1,
484 dependency_reachability_result: CheckResultV1,
485 budget_sufficiency_result: BudgetSufficiencyResultV1,
486 disposition: EffectPreflightDispositionV1,
487 generated_at: String,
488 notes: Vec<String>,
489}
490
491impl EffectPreflightReportV1Builder {
492 #[allow(clippy::too_many_arguments)]
494 pub fn new(
495 effect_preflight_report_id: EffectPreflightReportId,
496 effect_intent_id: EffectIntentId,
497 citation: V25ConstitutionCitation,
498 obligation_refs: V25ObligationRefs,
499 input_integrity_result: CheckResultV1,
500 admission_check_result: CheckResultV1,
501 dependency_reachability_result: CheckResultV1,
502 budget_sufficiency_result: BudgetSufficiencyResultV1,
503 disposition: EffectPreflightDispositionV1,
504 generated_at: impl Into<String>,
505 notes: Vec<String>,
506 ) -> Self {
507 Self {
508 effect_preflight_report_id,
509 effect_intent_id,
510 citation,
511 obligation_refs,
512 input_integrity_result,
513 admission_check_result,
514 dependency_reachability_result,
515 budget_sufficiency_result,
516 disposition,
517 generated_at: generated_at.into(),
518 notes,
519 }
520 }
521
522 pub fn build(self) -> Result<EffectPreflightReportV1, EffectRuntimeValidationError> {
524 let value = EffectPreflightReportV1 {
525 schema_version: EffectPreflightReportV1::SCHEMA_VERSION.to_string(),
526 effect_preflight_report_id: self.effect_preflight_report_id,
527 effect_intent_id: self.effect_intent_id,
528 citation: self.citation,
529 obligation_refs: self.obligation_refs,
530 input_integrity_result: self.input_integrity_result,
531 admission_check_result: self.admission_check_result,
532 dependency_reachability_result: self.dependency_reachability_result,
533 budget_sufficiency_result: self.budget_sufficiency_result,
534 disposition: self.disposition,
535 generated_at: self.generated_at,
536 notes: self.notes,
537 };
538 value.validate()?;
539 Ok(value)
540 }
541}
542
543impl EffectCommitDecisionV1 {
544 pub const SCHEMA_VERSION: &'static str = "EffectCommitDecisionV1";
545
546 #[allow(clippy::too_many_arguments)]
548 pub fn builder(
549 effect_commit_decision_id: EffectCommitDecisionId,
550 effect_intent_id: EffectIntentId,
551 effect_preflight_report_id: EffectPreflightReportId,
552 citation: V25ConstitutionCitation,
553 obligation_refs: V25ObligationRefs,
554 approval_obligation_refs: Vec<String>,
555 decision_state: EffectCommitDispositionV1,
556 approver_refs: Vec<String>,
557 execution_permit_id: Option<ExecutionPermitId>,
558 approval_grant_id: Option<ApprovalGrantId>,
559 refusal_reason: impl Into<String>,
560 generated_at: impl Into<String>,
561 ) -> EffectCommitDecisionV1Builder {
562 EffectCommitDecisionV1Builder::new(
563 effect_commit_decision_id,
564 effect_intent_id,
565 effect_preflight_report_id,
566 citation,
567 obligation_refs,
568 approval_obligation_refs,
569 decision_state,
570 approver_refs,
571 execution_permit_id,
572 approval_grant_id,
573 refusal_reason,
574 generated_at,
575 )
576 }
577
578 pub fn validate(&self) -> EffectValidationResult {
580 require_schema_version(
581 "EffectCommitDecisionV1",
582 Self::SCHEMA_VERSION,
583 &self.schema_version,
584 )?;
585 require_id(&self.effect_commit_decision_id, "effect_commit_decision_id")?;
586 require_id(&self.effect_intent_id, "effect_intent_id")?;
587 require_id(
588 &self.effect_preflight_report_id,
589 "effect_preflight_report_id",
590 )?;
591 require_non_empty_slice(
592 &self.obligation_refs.required_obligation_refs,
593 "required_obligation_refs",
594 )?;
595 require_non_empty(&self.generated_at, "generated_at")?;
596 match self.decision_state {
597 EffectCommitDispositionV1::Authorized => {
598 if self.execution_permit_id.is_none() {
599 return Err(EffectRuntimeValidationError::InvalidState(
600 "authorized commit decisions require execution_permit_id",
601 ));
602 }
603 require_non_empty_slice(&self.approver_refs, "approver_refs")?;
604 require_non_empty_slice(
605 &self.approval_obligation_refs,
606 "approval_obligation_refs",
607 )?;
608 if !self.refusal_reason.trim().is_empty() {
609 return Err(EffectRuntimeValidationError::InvalidState(
610 "authorized commit decisions must not carry refusal_reason",
611 ));
612 }
613 }
614 EffectCommitDispositionV1::AdvisoryOnly => {
615 if self.execution_permit_id.is_some() || self.approval_grant_id.is_some() {
616 return Err(EffectRuntimeValidationError::InvalidState(
617 "advisory-only commit decisions must not mint execution permits or approval grants",
618 ));
619 }
620 }
621 EffectCommitDispositionV1::Denied => {
622 if self.execution_permit_id.is_some() || self.approval_grant_id.is_some() {
623 return Err(EffectRuntimeValidationError::InvalidState(
624 "denied commit decisions must not mint execution permits or approval grants",
625 ));
626 }
627 require_non_empty(&self.refusal_reason, "refusal_reason")?;
628 }
629 }
630 Ok(())
631 }
632}
633
634#[derive(Debug, Clone)]
636pub struct EffectCommitDecisionV1Builder {
637 effect_commit_decision_id: EffectCommitDecisionId,
638 effect_intent_id: EffectIntentId,
639 effect_preflight_report_id: EffectPreflightReportId,
640 citation: V25ConstitutionCitation,
641 obligation_refs: V25ObligationRefs,
642 approval_obligation_refs: Vec<String>,
643 decision_state: EffectCommitDispositionV1,
644 approver_refs: Vec<String>,
645 execution_permit_id: Option<ExecutionPermitId>,
646 approval_grant_id: Option<ApprovalGrantId>,
647 refusal_reason: String,
648 generated_at: String,
649}
650
651impl EffectCommitDecisionV1Builder {
652 #[allow(clippy::too_many_arguments)]
654 pub fn new(
655 effect_commit_decision_id: EffectCommitDecisionId,
656 effect_intent_id: EffectIntentId,
657 effect_preflight_report_id: EffectPreflightReportId,
658 citation: V25ConstitutionCitation,
659 obligation_refs: V25ObligationRefs,
660 approval_obligation_refs: Vec<String>,
661 decision_state: EffectCommitDispositionV1,
662 approver_refs: Vec<String>,
663 execution_permit_id: Option<ExecutionPermitId>,
664 approval_grant_id: Option<ApprovalGrantId>,
665 refusal_reason: impl Into<String>,
666 generated_at: impl Into<String>,
667 ) -> Self {
668 Self {
669 effect_commit_decision_id,
670 effect_intent_id,
671 effect_preflight_report_id,
672 citation,
673 obligation_refs,
674 approval_obligation_refs,
675 decision_state,
676 approver_refs,
677 execution_permit_id,
678 approval_grant_id,
679 refusal_reason: refusal_reason.into(),
680 generated_at: generated_at.into(),
681 }
682 }
683
684 pub fn build(self) -> Result<EffectCommitDecisionV1, EffectRuntimeValidationError> {
686 let value = EffectCommitDecisionV1 {
687 schema_version: EffectCommitDecisionV1::SCHEMA_VERSION.to_string(),
688 effect_commit_decision_id: self.effect_commit_decision_id,
689 effect_intent_id: self.effect_intent_id,
690 effect_preflight_report_id: self.effect_preflight_report_id,
691 citation: self.citation,
692 obligation_refs: self.obligation_refs,
693 approval_obligation_refs: self.approval_obligation_refs,
694 decision_state: self.decision_state,
695 approver_refs: self.approver_refs,
696 execution_permit_id: self.execution_permit_id,
697 approval_grant_id: self.approval_grant_id,
698 refusal_reason: self.refusal_reason,
699 generated_at: self.generated_at,
700 };
701 value.validate()?;
702 Ok(value)
703 }
704}