Skip to main content

aidens_contracts/
reserved_v11.rs

1//! Reserved v11B/v11C-adjacent display artifacts.
2//!
3//! These families remain draft/advisory unless activated by explicit future evidence and owner-crate admission.
4
5use super::*;
6
7#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
8#[serde(rename_all = "kebab-case")]
9pub enum V11ActivationLevelV1 {
10    #[default]
11    ReservedDraft,
12    AdvisoryOnly,
13    Quarantined,
14    ActiveByFutureOwner,
15}
16
17impl V11ActivationLevelV1 {
18    pub fn is_active(self) -> bool {
19        matches!(self, Self::ActiveByFutureOwner)
20    }
21
22    pub fn allows_truth_promotion(self) -> bool {
23        self.is_active()
24    }
25}
26
27fn v11_activation_reserved_draft() -> V11ActivationLevelV1 {
28    V11ActivationLevelV1::ReservedDraft
29}
30
31fn bool_true() -> bool {
32    true
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
36#[serde(rename_all = "kebab-case")]
37pub enum ExternalAdmissionDispositionV1 {
38    Quarantined,
39    Rejected,
40    AdvisoryOnly,
41    AdmittedByCanonicalOwner,
42}
43
44#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
45pub struct ExternalArtifactAdmissionDecisionV1 {
46    pub decision_id: ArtifactId,
47    pub kind: ArtifactKindV1,
48    pub source_artifact_id: ArtifactId,
49    pub disposition: ExternalAdmissionDispositionV1,
50    pub activation_level: V11ActivationLevelV1,
51    pub truth_promotion_allowed: bool,
52    pub proof_waiver_allowed: bool,
53    #[serde(default, skip_serializing_if = "Vec::is_empty")]
54    pub reason_codes: Vec<String>,
55    #[serde(default, skip_serializing_if = "Vec::is_empty")]
56    pub canonical_backpointers: Vec<CanonicalBackpointerV1>,
57    pub decided_at: DateTime<Utc>,
58}
59
60impl ExternalArtifactAdmissionDecisionV1 {
61    pub fn default_quarantine(source_artifact_id: ArtifactId) -> Self {
62        Self {
63            decision_id: generated_artifact_id_from_material(
64                "external-admission-decision",
65                source_artifact_id.as_str(),
66            ),
67            kind: ArtifactKindV1::AdmissionDecision,
68            source_artifact_id,
69            disposition: ExternalAdmissionDispositionV1::Quarantined,
70            activation_level: V11ActivationLevelV1::Quarantined,
71            truth_promotion_allowed: false,
72            proof_waiver_allowed: false,
73            reason_codes: vec![
74                "external-admission-defaults-to-quarantine".into(),
75                "canonical-owner-admission-required".into(),
76            ],
77            canonical_backpointers: canonical_owner_backpointer(
78                "remote-oracle-admission",
79                "LocalAdmissionRecommendationV1",
80                "canonical-external-admission-owner",
81            ),
82            decided_at: Utc::now(),
83        }
84    }
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
88#[serde(rename_all = "kebab-case")]
89pub enum AdvisorySystemKindV1 {
90    LearnedRanker,
91    HeuristicAdvisor,
92    FixtureBackedEvaluator,
93    HumanDisplay,
94}
95
96#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
97pub struct AdvisorySystemPromotionGuardV1 {
98    pub guard_id: ArtifactId,
99    pub system_kind: AdvisorySystemKindV1,
100    pub activation_level: V11ActivationLevelV1,
101    pub truth_promotion_allowed: bool,
102    pub proof_waiver_allowed: bool,
103    #[serde(default, skip_serializing_if = "Vec::is_empty")]
104    pub reason_codes: Vec<String>,
105    pub evaluated_at: DateTime<Utc>,
106}
107
108impl AdvisorySystemPromotionGuardV1 {
109    pub fn advisory(system_kind: AdvisorySystemKindV1) -> Self {
110        let material = format!("{system_kind:?}:advisory");
111        Self {
112            guard_id: generated_artifact_id_from_material("advisory-promotion-guard", &material),
113            system_kind,
114            activation_level: V11ActivationLevelV1::AdvisoryOnly,
115            truth_promotion_allowed: false,
116            proof_waiver_allowed: false,
117            reason_codes: vec![
118                "advisory-system-cannot-promote-truth".into(),
119                "advisory-system-cannot-waive-proof".into(),
120            ],
121            evaluated_at: Utc::now(),
122        }
123    }
124}
125
126#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
127#[serde(rename_all = "kebab-case")]
128pub enum RegionGraphKindV1 {
129    Storage,
130    Retrieval,
131    Inference,
132    Repair,
133    Control,
134}
135
136impl RegionGraphKindV1 {
137    pub fn can_execute_kernel(self) -> bool {
138        matches!(self, Self::Inference | Self::Repair)
139    }
140}
141
142impl fmt::Display for RegionGraphKindV1 {
143    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144        f.write_str(match self {
145            Self::Storage => "storage",
146            Self::Retrieval => "retrieval",
147            Self::Inference => "inference",
148            Self::Repair => "repair",
149            Self::Control => "control",
150        })
151    }
152}
153
154#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
155#[serde(rename_all = "kebab-case")]
156pub enum RegionNodeKindV1 {
157    Claim,
158    Evidence,
159    Factor,
160    Boundary,
161    Nuisance,
162}
163
164#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
165pub struct RegionNodeV1 {
166    pub node_id: ArtifactId,
167    pub node_kind: RegionNodeKindV1,
168    pub label: String,
169    #[serde(default, skip_serializing_if = "Option::is_none")]
170    pub source_artifact_id: Option<ArtifactId>,
171    #[serde(default, skip_serializing_if = "Vec::is_empty")]
172    pub reason_codes: Vec<String>,
173}
174
175impl RegionNodeV1 {
176    pub fn new(
177        node_kind: RegionNodeKindV1,
178        label: impl Into<String>,
179        source_artifact_id: Option<ArtifactId>,
180    ) -> Self {
181        Self {
182            node_id: display_only_unstable_id("region-node"),
183            node_kind,
184            label: label.into(),
185            source_artifact_id,
186            reason_codes: vec!["region-node-defined".into()],
187        }
188    }
189}
190
191#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
192pub struct RegionEdgeV1 {
193    pub edge_id: ArtifactId,
194    pub from_node_id: ArtifactId,
195    pub to_node_id: ArtifactId,
196    pub relation: String,
197}
198
199impl RegionEdgeV1 {
200    pub fn new(
201        from_node_id: ArtifactId,
202        to_node_id: ArtifactId,
203        relation: impl Into<String>,
204    ) -> Self {
205        Self {
206            edge_id: display_only_unstable_id("region-edge"),
207            from_node_id,
208            to_node_id,
209            relation: relation.into(),
210        }
211    }
212}
213
214#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
215pub struct RegionHyperedgeV1 {
216    pub hyperedge_id: ArtifactId,
217    pub node_ids: Vec<ArtifactId>,
218    pub relation: String,
219}
220
221impl RegionHyperedgeV1 {
222    pub fn new(mut node_ids: Vec<ArtifactId>, relation: impl Into<String>) -> Self {
223        node_ids.sort();
224        node_ids.dedup();
225        Self {
226            hyperedge_id: display_only_unstable_id("region-hyperedge"),
227            node_ids,
228            relation: relation.into(),
229        }
230    }
231}
232
233#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
234pub struct RegionFactorV1 {
235    pub factor_id: ArtifactId,
236    pub scope_node_ids: Vec<ArtifactId>,
237    pub factor_kind: String,
238    pub weight: f64,
239    #[serde(default, skip_serializing_if = "Vec::is_empty")]
240    pub reason_codes: Vec<String>,
241}
242
243impl RegionFactorV1 {
244    pub fn new(
245        mut scope_node_ids: Vec<ArtifactId>,
246        factor_kind: impl Into<String>,
247        weight: f64,
248    ) -> Self {
249        scope_node_ids.sort();
250        scope_node_ids.dedup();
251        Self {
252            factor_id: display_only_unstable_id("region-factor"),
253            scope_node_ids,
254            factor_kind: factor_kind.into(),
255            weight,
256            reason_codes: vec!["bounded-region-factor".into()],
257        }
258    }
259}
260
261#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
262pub struct RegionContractV1 {
263    pub contract_id: ArtifactId,
264    pub kind: ArtifactKindV1,
265    pub graph_id: ArtifactId,
266    pub graph_kind: RegionGraphKindV1,
267    pub region_id: ArtifactId,
268    #[serde(default, skip_serializing_if = "Option::is_none")]
269    pub canonical_region_id: Option<StackRegionId>,
270    pub node_ids: Vec<ArtifactId>,
271    #[serde(default, skip_serializing_if = "Vec::is_empty")]
272    pub boundary_node_ids: Vec<ArtifactId>,
273    #[serde(default, skip_serializing_if = "Vec::is_empty")]
274    pub factor_ids: Vec<ArtifactId>,
275    pub max_nodes: u32,
276    pub local_repair_allowed: bool,
277    #[serde(default = "v11_activation_reserved_draft")]
278    pub activation_level: V11ActivationLevelV1,
279    #[serde(default = "bool_true")]
280    pub advisory_only: bool,
281    #[serde(default, skip_serializing_if = "Vec::is_empty")]
282    pub reason_codes: Vec<String>,
283    #[serde(default, skip_serializing_if = "Vec::is_empty")]
284    pub canonical_backpointers: Vec<CanonicalBackpointerV1>,
285    pub created_at: DateTime<Utc>,
286}
287
288impl RegionContractV1 {
289    pub fn new(
290        graph_id: ArtifactId,
291        graph_kind: RegionGraphKindV1,
292        region_id: ArtifactId,
293        mut node_ids: Vec<ArtifactId>,
294        mut boundary_node_ids: Vec<ArtifactId>,
295        mut factor_ids: Vec<ArtifactId>,
296        max_nodes: u32,
297    ) -> Self {
298        node_ids.sort();
299        node_ids.dedup();
300        boundary_node_ids.sort();
301        boundary_node_ids.dedup();
302        factor_ids.sort();
303        factor_ids.dedup();
304        let local_repair_allowed =
305            graph_kind == RegionGraphKindV1::Repair || graph_kind == RegionGraphKindV1::Inference;
306        Self {
307            contract_id: display_only_unstable_id("region-contract"),
308            kind: ArtifactKindV1::RegionContract,
309            graph_id,
310            graph_kind,
311            region_id,
312            canonical_region_id: None,
313            node_ids,
314            boundary_node_ids,
315            factor_ids,
316            max_nodes,
317            local_repair_allowed,
318            activation_level: V11ActivationLevelV1::ReservedDraft,
319            advisory_only: true,
320            reason_codes: vec![
321                "region-boundary-contract".into(),
322                "v11b-region-contract-reserved-draft".into(),
323            ],
324            canonical_backpointers: canonical_owner_backpointer(
325                "constraint-compiler",
326                "CompiledRegion",
327                "canonical-region-contract-owner",
328            ),
329            created_at: Utc::now(),
330        }
331    }
332
333    pub fn is_bounded(&self) -> bool {
334        self.node_ids.len() <= self.max_nodes as usize
335    }
336}
337
338#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
339pub struct RegionBoundaryMessageV1 {
340    pub message_id: ArtifactId,
341    pub kind: ArtifactKindV1,
342    pub source_region_id: ArtifactId,
343    pub destination_region_id: ArtifactId,
344    pub artifact_family: String,
345    pub payload_ref: ArtifactId,
346    pub payload_digest: DisplayDigestV1,
347    pub boundary_policy: String,
348    pub budget_impact: String,
349    #[serde(default = "v11_activation_reserved_draft")]
350    pub activation_level: V11ActivationLevelV1,
351    #[serde(default = "bool_true")]
352    pub advisory_only: bool,
353    #[serde(default, skip_serializing_if = "Vec::is_empty")]
354    pub reason_codes: Vec<String>,
355    #[serde(default, skip_serializing_if = "Vec::is_empty")]
356    pub canonical_backpointers: Vec<CanonicalBackpointerV1>,
357    pub emitted_at: DateTime<Utc>,
358}
359
360impl RegionBoundaryMessageV1 {
361    pub fn seed(
362        source_region_id: ArtifactId,
363        destination_region_id: ArtifactId,
364        artifact_family: impl Into<String>,
365        payload_ref: ArtifactId,
366        payload_digest: DisplayDigestV1,
367    ) -> Self {
368        let artifact_family = artifact_family.into();
369        let material = format!(
370            "{}|{}|{}|{}|{}",
371            source_region_id.0,
372            destination_region_id.0,
373            artifact_family,
374            payload_ref.0,
375            payload_digest.digest
376        );
377        Self {
378            message_id: generated_artifact_id_from_material("boundary-message", &material),
379            kind: ArtifactKindV1::BoundaryMessage,
380            source_region_id,
381            destination_region_id,
382            artifact_family,
383            payload_ref,
384            payload_digest,
385            boundary_policy: "receipt-required-advisory-seed".into(),
386            budget_impact: "declared-no-runtime-debit".into(),
387            activation_level: V11ActivationLevelV1::AdvisoryOnly,
388            advisory_only: true,
389            reason_codes: vec![
390                "v11b-boundary-message-executable-seed".into(),
391                "boundary-message-advisory-only".into(),
392            ],
393            canonical_backpointers: canonical_owner_backpointer(
394                "kernel-execution",
395                "BoundaryMessage",
396                "canonical-region-boundary-owner",
397            ),
398            emitted_at: Utc::now(),
399        }
400    }
401
402    pub fn can_cross_runtime_boundary(&self) -> bool {
403        self.activation_level.is_active() && !self.advisory_only
404    }
405}
406
407#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
408pub struct RegionBoundaryReceiptV1 {
409    pub receipt_id: ArtifactId,
410    pub kind: ArtifactKindV1,
411    pub message_id: ArtifactId,
412    #[serde(default = "region_boundary_receipt_disposition_accepted")]
413    pub disposition: RegionBoundaryReceiptDispositionV1,
414    pub accepted: bool,
415    pub replay_required: bool,
416    pub canonicalization_profile: String,
417    #[serde(default = "v11_activation_reserved_draft")]
418    pub activation_level: V11ActivationLevelV1,
419    #[serde(default = "bool_true")]
420    pub advisory_only: bool,
421    #[serde(default, skip_serializing_if = "Vec::is_empty")]
422    pub reason_codes: Vec<String>,
423    #[serde(default, skip_serializing_if = "Option::is_none")]
424    pub quarantine_reason: Option<String>,
425    #[serde(default, skip_serializing_if = "Vec::is_empty")]
426    pub canonical_backpointers: Vec<CanonicalBackpointerV1>,
427    pub recorded_at: DateTime<Utc>,
428}
429
430#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
431#[serde(rename_all = "kebab-case")]
432pub enum RegionBoundaryReceiptDispositionV1 {
433    Accepted,
434    Rejected,
435    Quarantined,
436}
437
438fn region_boundary_receipt_disposition_accepted() -> RegionBoundaryReceiptDispositionV1 {
439    RegionBoundaryReceiptDispositionV1::Accepted
440}
441
442impl RegionBoundaryReceiptV1 {
443    pub fn seed(message: &RegionBoundaryMessageV1, accepted: bool) -> Self {
444        let material = format!("{}|accepted={accepted}", message.message_id.0);
445        Self {
446            receipt_id: generated_artifact_id_from_material("boundary-receipt", &material),
447            kind: ArtifactKindV1::BoundaryReceipt,
448            message_id: message.message_id.clone(),
449            disposition: if accepted {
450                RegionBoundaryReceiptDispositionV1::Accepted
451            } else {
452                RegionBoundaryReceiptDispositionV1::Rejected
453            },
454            accepted,
455            replay_required: true,
456            canonicalization_profile: "stack-ids-json-c14n-v1".into(),
457            activation_level: V11ActivationLevelV1::AdvisoryOnly,
458            advisory_only: true,
459            reason_codes: if accepted {
460                vec![
461                    "v11b-boundary-receipt-executable-seed".into(),
462                    "boundary-message-admitted-advisory-only".into(),
463                ]
464            } else {
465                vec![
466                    "v11b-boundary-receipt-executable-seed".into(),
467                    "boundary-message-rejected-advisory-only".into(),
468                ]
469            },
470            quarantine_reason: None,
471            canonical_backpointers: canonical_owner_backpointer(
472                "kernel-execution",
473                "BoundaryReceipt",
474                "canonical-region-boundary-receipt-owner",
475            ),
476            recorded_at: Utc::now(),
477        }
478    }
479
480    pub fn quarantined(message: &RegionBoundaryMessageV1, reason: impl Into<String>) -> Self {
481        let reason = reason.into();
482        let material = format!("{}|quarantined|{reason}", message.message_id.0);
483        Self {
484            receipt_id: generated_artifact_id_from_material("boundary-receipt", &material),
485            kind: ArtifactKindV1::BoundaryReceipt,
486            message_id: message.message_id.clone(),
487            disposition: RegionBoundaryReceiptDispositionV1::Quarantined,
488            accepted: false,
489            replay_required: true,
490            canonicalization_profile: "stack-ids-json-c14n-v1".into(),
491            activation_level: V11ActivationLevelV1::Quarantined,
492            advisory_only: true,
493            reason_codes: vec![
494                "v11b-boundary-receipt-executable-seed".into(),
495                "boundary-message-quarantined-advisory-only".into(),
496            ],
497            quarantine_reason: Some(reason),
498            canonical_backpointers: canonical_owner_backpointer(
499                "kernel-execution",
500                "BoundaryReceipt",
501                "canonical-region-boundary-receipt-owner",
502            ),
503            recorded_at: Utc::now(),
504        }
505    }
506
507    pub fn can_admit_runtime_payload(&self) -> bool {
508        self.disposition == RegionBoundaryReceiptDispositionV1::Accepted
509            && self.accepted
510            && self.activation_level.is_active()
511            && !self.advisory_only
512    }
513}
514
515#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
516pub struct CompiledRegionGraphV1 {
517    pub graph_id: ArtifactId,
518    pub kind: ArtifactKindV1,
519    pub graph_kind: RegionGraphKindV1,
520    #[serde(default, skip_serializing_if = "Option::is_none")]
521    pub source_graph_kind: Option<RegionGraphKindV1>,
522    pub max_region_size: u32,
523    pub right_graph_law_satisfied: bool,
524    pub nodes: Vec<RegionNodeV1>,
525    #[serde(default, skip_serializing_if = "Vec::is_empty")]
526    pub edges: Vec<RegionEdgeV1>,
527    #[serde(default, skip_serializing_if = "Vec::is_empty")]
528    pub hyperedges: Vec<RegionHyperedgeV1>,
529    #[serde(default, skip_serializing_if = "Vec::is_empty")]
530    pub factors: Vec<RegionFactorV1>,
531    #[serde(default, skip_serializing_if = "Vec::is_empty")]
532    pub nuisance_node_ids: Vec<ArtifactId>,
533    #[serde(default, skip_serializing_if = "Vec::is_empty")]
534    pub boundary_node_ids: Vec<ArtifactId>,
535    pub regions: Vec<RegionContractV1>,
536    #[serde(default, skip_serializing_if = "Vec::is_empty")]
537    pub source_claim_ids: Vec<ArtifactId>,
538    #[serde(default, skip_serializing_if = "Vec::is_empty")]
539    pub source_projection_ids: Vec<ArtifactId>,
540    #[serde(default = "v11_activation_reserved_draft")]
541    pub activation_level: V11ActivationLevelV1,
542    #[serde(default = "bool_true")]
543    pub advisory_only: bool,
544    #[serde(default, skip_serializing_if = "Vec::is_empty")]
545    pub reason_codes: Vec<String>,
546    #[serde(default, skip_serializing_if = "Vec::is_empty")]
547    pub canonical_backpointers: Vec<CanonicalBackpointerV1>,
548    pub compiled_at: DateTime<Utc>,
549}
550
551impl CompiledRegionGraphV1 {
552    #[allow(clippy::too_many_arguments)]
553    pub fn new(
554        graph_id: ArtifactId,
555        graph_kind: RegionGraphKindV1,
556        source_graph_kind: Option<RegionGraphKindV1>,
557        max_region_size: u32,
558        nodes: Vec<RegionNodeV1>,
559        edges: Vec<RegionEdgeV1>,
560        hyperedges: Vec<RegionHyperedgeV1>,
561        factors: Vec<RegionFactorV1>,
562        nuisance_node_ids: Vec<ArtifactId>,
563        boundary_node_ids: Vec<ArtifactId>,
564        regions: Vec<RegionContractV1>,
565        source_claim_ids: Vec<ArtifactId>,
566        source_projection_ids: Vec<ArtifactId>,
567    ) -> Self {
568        let right_graph_law_satisfied =
569            graph_kind.can_execute_kernel() && regions.iter().all(RegionContractV1::is_bounded);
570        let mut reason_codes = vec![
571            "compiled-region-graph".into(),
572            format!("graph-kind:{graph_kind}"),
573            "v11b-region-graph-reserved-draft".into(),
574        ];
575        if source_graph_kind == Some(RegionGraphKindV1::Storage) {
576            reason_codes.push("storage-evidence-compiled-not-used-directly".into());
577        }
578        if !right_graph_law_satisfied {
579            reason_codes.push("right-graph-law-blocked".into());
580        }
581        Self {
582            graph_id,
583            kind: ArtifactKindV1::CompiledRegionGraph,
584            graph_kind,
585            source_graph_kind,
586            max_region_size,
587            right_graph_law_satisfied,
588            nodes,
589            edges,
590            hyperedges,
591            factors,
592            nuisance_node_ids,
593            boundary_node_ids,
594            regions,
595            source_claim_ids,
596            source_projection_ids,
597            activation_level: V11ActivationLevelV1::ReservedDraft,
598            advisory_only: true,
599            reason_codes,
600            canonical_backpointers: canonical_owner_backpointer(
601                "constraint-compiler",
602                "CompileOutput",
603                "canonical-region-graph-owner",
604            ),
605            compiled_at: Utc::now(),
606        }
607    }
608
609    pub fn is_bounded_region_graph(&self) -> bool {
610        self.right_graph_law_satisfied && self.regions.iter().all(RegionContractV1::is_bounded)
611    }
612
613    pub fn can_claim_active_v11b_runtime(&self) -> bool {
614        self.activation_level.is_active() && !self.advisory_only
615    }
616}
617
618#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
619pub struct KernelStopRuleReportV1 {
620    pub stop_state: CanonicalKernelStopReason,
621    pub iteration: u32,
622    pub max_iterations: u32,
623    pub residual_threshold: f64,
624    pub damping: f64,
625    pub observed_residual: f64,
626    #[serde(default, skip_serializing_if = "Vec::is_empty")]
627    pub reason_codes: Vec<String>,
628}
629
630impl KernelStopRuleReportV1 {
631    pub fn new(
632        stop_state: CanonicalKernelStopReason,
633        iteration: u32,
634        max_iterations: u32,
635        residual_threshold: f64,
636        damping: f64,
637        observed_residual: f64,
638    ) -> Self {
639        Self {
640            stop_state,
641            iteration,
642            max_iterations,
643            residual_threshold,
644            damping,
645            observed_residual,
646            reason_codes: vec!["kernel-stop-rule-evidence".into()],
647        }
648    }
649
650    pub fn is_terminal(&self) -> bool {
651        true
652    }
653}
654
655#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
656pub struct KernelResidualReportV1 {
657    pub residual_id: ArtifactId,
658    pub kind: ArtifactKindV1,
659    pub graph_id: ArtifactId,
660    pub region_id: ArtifactId,
661    #[serde(default, skip_serializing_if = "Option::is_none")]
662    pub canonical_residual_id: Option<StackResidualId>,
663    #[serde(default, skip_serializing_if = "Option::is_none")]
664    pub canonical_region_id: Option<StackRegionId>,
665    pub iteration: u32,
666    pub previous_value: f64,
667    pub current_value: f64,
668    pub delta: f64,
669    pub threshold: f64,
670    pub converged: bool,
671    pub stop_rule_evidence: KernelStopRuleReportV1,
672    #[serde(default, skip_serializing_if = "Vec::is_empty")]
673    pub reason_codes: Vec<String>,
674    #[serde(default, skip_serializing_if = "Vec::is_empty")]
675    pub canonical_backpointers: Vec<CanonicalBackpointerV1>,
676    pub measured_at: DateTime<Utc>,
677}
678
679impl KernelResidualReportV1 {
680    pub fn new(
681        graph_id: ArtifactId,
682        region_id: ArtifactId,
683        iteration: u32,
684        previous_value: f64,
685        current_value: f64,
686        threshold: f64,
687        stop_rule_evidence: KernelStopRuleReportV1,
688    ) -> Self {
689        let delta = (previous_value - current_value).abs();
690        let converged = current_value <= threshold;
691        Self {
692            residual_id: display_only_unstable_id("residual"),
693            kind: ArtifactKindV1::Residual,
694            graph_id,
695            region_id,
696            canonical_residual_id: None,
697            canonical_region_id: None,
698            iteration,
699            previous_value,
700            current_value,
701            delta,
702            threshold,
703            converged,
704            stop_rule_evidence,
705            reason_codes: if converged {
706                vec!["residual-within-threshold".into()]
707            } else {
708                vec!["residual-above-threshold".into()]
709            },
710            canonical_backpointers: vec![
711                CanonicalBackpointerV1::owner_type(
712                    "recursive-kernel-core",
713                    "ResidualArtifact",
714                    "canonical-residual-owner",
715                ),
716                CanonicalBackpointerV1::owner_type(
717                    "kernel-execution",
718                    "ResidualSample",
719                    "canonical-residual-execution-owner",
720                ),
721            ],
722            measured_at: Utc::now(),
723        }
724    }
725
726    pub fn blocks_promotion_as_exact(&self) -> bool {
727        !self.converged
728    }
729}
730
731#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
732#[serde(rename_all = "kebab-case")]
733pub enum KernelSyndromeKindDisplayV1 {
734    Contradiction,
735    HighResidual,
736    BoundaryViolation,
737}
738
739#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
740pub struct KernelSyndromeReportV1 {
741    pub syndrome_id: ArtifactId,
742    pub kind: ArtifactKindV1,
743    pub syndrome_kind: KernelSyndromeKindDisplayV1,
744    pub graph_id: ArtifactId,
745    pub region_id: ArtifactId,
746    #[serde(default, skip_serializing_if = "Option::is_none")]
747    pub canonical_syndrome_id: Option<StackSyndromeId>,
748    #[serde(default, skip_serializing_if = "Option::is_none")]
749    pub canonical_region_id: Option<StackRegionId>,
750    #[serde(default, skip_serializing_if = "Option::is_none")]
751    pub contradiction_witness_id: Option<ArtifactId>,
752    #[serde(default, skip_serializing_if = "Option::is_none")]
753    pub residual_id: Option<ArtifactId>,
754    #[serde(default, skip_serializing_if = "Vec::is_empty")]
755    pub affected_claim_ids: Vec<ArtifactId>,
756    #[serde(default, skip_serializing_if = "Vec::is_empty")]
757    pub canonical_repair_record_ids: Vec<StackBoundaryRepairRecordId>,
758    pub global_recompute_required: bool,
759    #[serde(default, skip_serializing_if = "Vec::is_empty")]
760    pub reason_codes: Vec<String>,
761    #[serde(default, skip_serializing_if = "Vec::is_empty")]
762    pub canonical_backpointers: Vec<CanonicalBackpointerV1>,
763    pub emitted_at: DateTime<Utc>,
764}
765
766impl KernelSyndromeReportV1 {
767    pub fn contradiction(
768        graph_id: ArtifactId,
769        region_id: ArtifactId,
770        contradiction_witness_id: ArtifactId,
771        affected_claim_ids: Vec<ArtifactId>,
772    ) -> Self {
773        Self {
774            syndrome_id: display_only_unstable_id("syndrome"),
775            kind: ArtifactKindV1::Syndrome,
776            syndrome_kind: KernelSyndromeKindDisplayV1::Contradiction,
777            graph_id,
778            region_id,
779            canonical_syndrome_id: None,
780            canonical_region_id: None,
781            contradiction_witness_id: Some(contradiction_witness_id),
782            residual_id: None,
783            affected_claim_ids,
784            canonical_repair_record_ids: Vec::new(),
785            global_recompute_required: true,
786            reason_codes: vec!["canonical-repair-required".into()],
787            canonical_backpointers: vec![
788                CanonicalBackpointerV1::owner_type(
789                    "recursive-kernel-core",
790                    "Syndrome",
791                    "canonical-syndrome-owner",
792                ),
793                CanonicalBackpointerV1::owner_type(
794                    "verification-control",
795                    "SyndromeRouteRecord",
796                    "canonical-syndrome-repair-routing-owner",
797                ),
798            ],
799            emitted_at: Utc::now(),
800        }
801    }
802
803    pub fn requires_canonical_repair(&self) -> bool {
804        self.global_recompute_required && self.canonical_repair_record_ids.is_empty()
805    }
806
807    pub fn blocks_promotion_as_exact(&self) -> bool {
808        self.global_recompute_required || self.canonical_repair_record_ids.is_empty()
809    }
810}
811
812#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
813#[serde(rename_all = "kebab-case")]
814pub enum OracleAgreementV1 {
815    Agrees,
816    BoundedDisagreement,
817}
818
819#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
820pub struct OracleSliceRequestV1 {
821    pub request_id: ArtifactId,
822    pub kind: ArtifactKindV1,
823    pub graph_id: ArtifactId,
824    pub region_id: ArtifactId,
825    pub requested_node_ids: Vec<ArtifactId>,
826    pub max_exact_nodes: u32,
827    pub approximate_digest: DisplayDigestV1,
828    pub oracle_digest: DisplayDigestV1,
829    pub agreement: OracleAgreementV1,
830    #[serde(default, skip_serializing_if = "Option::is_none")]
831    pub disagreement_bound: Option<f64>,
832    #[serde(default, skip_serializing_if = "Vec::is_empty")]
833    pub reason_codes: Vec<String>,
834    pub requested_at: DateTime<Utc>,
835}
836
837impl OracleSliceRequestV1 {
838    pub fn new(
839        graph_id: ArtifactId,
840        region_id: ArtifactId,
841        mut requested_node_ids: Vec<ArtifactId>,
842        max_exact_nodes: u32,
843        approximate_digest: DisplayDigestV1,
844        oracle_digest: DisplayDigestV1,
845        disagreement_bound: Option<f64>,
846    ) -> Self {
847        requested_node_ids.sort();
848        requested_node_ids.dedup();
849        let agreement = if approximate_digest == oracle_digest {
850            OracleAgreementV1::Agrees
851        } else {
852            OracleAgreementV1::BoundedDisagreement
853        };
854        Self {
855            request_id: display_only_unstable_id("oracle-slice-request"),
856            kind: ArtifactKindV1::OracleSliceRequest,
857            graph_id,
858            region_id,
859            requested_node_ids,
860            max_exact_nodes,
861            approximate_digest,
862            oracle_digest,
863            agreement,
864            disagreement_bound,
865            reason_codes: if agreement == OracleAgreementV1::Agrees {
866                vec!["oracle-slice-agrees-with-approximate-path".into()]
867            } else {
868                vec!["oracle-slice-bounded-disagreement".into()]
869            },
870            requested_at: Utc::now(),
871        }
872    }
873
874    pub fn has_bounded_semantic_diff(&self) -> bool {
875        self.agreement == OracleAgreementV1::Agrees
876            || self
877                .disagreement_bound
878                .map(|bound| bound.is_finite() && bound >= 0.0)
879                .unwrap_or(false)
880    }
881}
882
883#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
884pub struct ConvergenceReportV1 {
885    pub report_id: ArtifactId,
886    pub kind: ArtifactKindV1,
887    pub graph_id: ArtifactId,
888    pub graph_kind: RegionGraphKindV1,
889    pub converged: bool,
890    pub degraded: bool,
891    pub stop_state: CanonicalKernelStopReason,
892    pub iteration_count: u32,
893    pub max_iterations: u32,
894    pub residual_threshold: f64,
895    pub damping: f64,
896    pub final_residual: f64,
897    pub residual_ids: Vec<ArtifactId>,
898    pub stop_rule_evidence: KernelStopRuleReportV1,
899    pub oscillation_detected: bool,
900    #[serde(default, skip_serializing_if = "Vec::is_empty")]
901    pub reason_codes: Vec<String>,
902    pub recorded_at: DateTime<Utc>,
903}
904
905impl ConvergenceReportV1 {
906    #[allow(clippy::too_many_arguments)]
907    pub fn new(
908        graph_id: ArtifactId,
909        graph_kind: RegionGraphKindV1,
910        stop_state: CanonicalKernelStopReason,
911        iteration_count: u32,
912        max_iterations: u32,
913        residual_threshold: f64,
914        damping: f64,
915        final_residual: f64,
916        residual_ids: Vec<ArtifactId>,
917        stop_rule_evidence: KernelStopRuleReportV1,
918        oscillation_detected: bool,
919    ) -> Self {
920        let converged = matches!(
921            stop_state,
922            CanonicalKernelStopReason::FixedPoint | CanonicalKernelStopReason::AcyclicCompletion
923        );
924        let residual_degraded = final_residual.is_finite()
925            && residual_threshold.is_finite()
926            && final_residual > residual_threshold;
927        let degraded = !converged || oscillation_detected || residual_degraded;
928        let mut reason_codes = if degraded {
929            vec!["kernel-convergence-degraded".into()]
930        } else {
931            vec!["kernel-converged-with-stop-rule".into()]
932        };
933        if !converged {
934            reason_codes.push("kernel-non-converged-stop-state".into());
935        }
936        if oscillation_detected {
937            reason_codes.push("kernel-oscillation-detected".into());
938        }
939        if residual_degraded {
940            reason_codes.push("kernel-residual-above-threshold".into());
941        }
942        Self {
943            report_id: display_only_unstable_id("convergence-report"),
944            kind: ArtifactKindV1::ConvergenceReport,
945            graph_id,
946            graph_kind,
947            converged,
948            degraded,
949            stop_state,
950            iteration_count,
951            max_iterations,
952            residual_threshold,
953            damping,
954            final_residual,
955            residual_ids,
956            stop_rule_evidence,
957            oscillation_detected,
958            reason_codes,
959            recorded_at: Utc::now(),
960        }
961    }
962
963    pub fn has_explicit_stop_rule_evidence(&self) -> bool {
964        self.stop_rule_evidence.is_terminal()
965    }
966
967    pub fn blocks_promotion_as_exact(&self) -> bool {
968        self.degraded
969    }
970}
971
972#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
973pub struct KernelRunDisplayReportV1 {
974    pub receipt_id: ArtifactId,
975    pub kind: ArtifactKindV1,
976    pub graph_id: ArtifactId,
977    pub graph_kind: RegionGraphKindV1,
978    pub region_ids: Vec<ArtifactId>,
979    pub converged: bool,
980    pub degraded: bool,
981    pub convergence_report_id: ArtifactId,
982    #[serde(default, skip_serializing_if = "Vec::is_empty")]
983    pub syndrome_ids: Vec<ArtifactId>,
984    #[serde(default, skip_serializing_if = "Vec::is_empty")]
985    pub residual_ids: Vec<ArtifactId>,
986    #[serde(default, skip_serializing_if = "Vec::is_empty")]
987    pub oracle_request_ids: Vec<ArtifactId>,
988    #[serde(default, skip_serializing_if = "Vec::is_empty")]
989    pub canonical_repair_record_ids: Vec<StackBoundaryRepairRecordId>,
990    pub stop_rule_evidence: KernelStopRuleReportV1,
991    pub used_global_recompute: bool,
992    #[serde(default, skip_serializing_if = "Vec::is_empty")]
993    pub reason_codes: Vec<String>,
994    #[serde(default, skip_serializing_if = "Vec::is_empty")]
995    pub canonical_backpointers: Vec<CanonicalBackpointerV1>,
996    pub recorded_at: DateTime<Utc>,
997}
998
999impl KernelRunDisplayReportV1 {
1000    pub fn new(
1001        graph: &CompiledRegionGraphV1,
1002        convergence_report: &ConvergenceReportV1,
1003        syndromes: &[KernelSyndromeReportV1],
1004        oracle_requests: &[OracleSliceRequestV1],
1005    ) -> Self {
1006        let mut region_ids = graph
1007            .regions
1008            .iter()
1009            .map(|region| region.region_id.clone())
1010            .collect::<Vec<_>>();
1011        region_ids.sort();
1012        region_ids.dedup();
1013        let mut canonical_repair_record_ids = syndromes
1014            .iter()
1015            .flat_map(|syndrome| syndrome.canonical_repair_record_ids.iter().cloned())
1016            .collect::<Vec<_>>();
1017        canonical_repair_record_ids.sort();
1018        canonical_repair_record_ids.dedup();
1019        let used_global_recompute = syndromes
1020            .iter()
1021            .any(|syndrome| syndrome.global_recompute_required);
1022        let mut reason_codes = if convergence_report.converged {
1023            vec!["kernel-run-converged".into()]
1024        } else {
1025            vec!["kernel-run-degraded".into()]
1026        };
1027        if !syndromes.is_empty() {
1028            reason_codes.push("kernel-run-has-syndrome-evidence".into());
1029        }
1030        Self {
1031            receipt_id: display_only_unstable_id("kernel-run-report"),
1032            kind: ArtifactKindV1::KernelRunReport,
1033            graph_id: graph.graph_id.clone(),
1034            graph_kind: graph.graph_kind,
1035            region_ids,
1036            converged: convergence_report.converged,
1037            degraded: convergence_report.degraded,
1038            convergence_report_id: convergence_report.report_id.clone(),
1039            syndrome_ids: syndromes
1040                .iter()
1041                .map(|syndrome| syndrome.syndrome_id.clone())
1042                .collect(),
1043            residual_ids: convergence_report.residual_ids.clone(),
1044            oracle_request_ids: oracle_requests
1045                .iter()
1046                .map(|request| request.request_id.clone())
1047                .collect(),
1048            canonical_repair_record_ids,
1049            stop_rule_evidence: convergence_report.stop_rule_evidence.clone(),
1050            used_global_recompute,
1051            reason_codes,
1052            canonical_backpointers: vec![
1053                CanonicalBackpointerV1::owner_type(
1054                    "recursive-kernel-core",
1055                    "KernelRun",
1056                    "canonical-kernel-run-owner",
1057                ),
1058                CanonicalBackpointerV1::owner_type(
1059                    "kernel-execution",
1060                    "ExecutionReport",
1061                    "canonical-kernel-execution-owner",
1062                ),
1063            ],
1064            recorded_at: Utc::now(),
1065        }
1066    }
1067
1068    pub fn convergence_is_evidenced(&self) -> bool {
1069        !self.converged
1070            || matches!(
1071                self.stop_rule_evidence.stop_state,
1072                CanonicalKernelStopReason::FixedPoint
1073                    | CanonicalKernelStopReason::AcyclicCompletion
1074            )
1075    }
1076
1077    pub fn can_promote_as_exact_seed_result(&self) -> bool {
1078        self.converged && !self.degraded && self.syndrome_ids.is_empty()
1079    }
1080}
1081
1082#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1083#[serde(rename_all = "kebab-case")]
1084pub enum SubtractionOperatorV1 {
1085    Dedupe,
1086    Summarize,
1087    Compact,
1088    Retire,
1089    Quarantine,
1090    Minimize,
1091    SupportCoreExtraction,
1092}
1093
1094#[derive(
1095    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema,
1096)]
1097#[serde(rename_all = "kebab-case")]
1098pub enum PreservedInvariantV1 {
1099    Replay,
1100    AsOfQuery,
1101    ClaimSupport,
1102    ReceiptLineage,
1103    LegalRetention,
1104    OperatorProof,
1105}
1106
1107#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1108pub struct InvariantBudgetV1 {
1109    pub budget_id: ArtifactId,
1110    pub kind: ArtifactKindV1,
1111    pub history_budget_label: String,
1112    pub as_of_query_horizon: String,
1113    pub preserved_invariants: Vec<PreservedInvariantV1>,
1114    pub max_removed_claims: u32,
1115    pub max_removed_evidence_records: u32,
1116    pub max_compacted_receipts: u32,
1117    pub requires_receipt_retention: bool,
1118    #[serde(default, skip_serializing_if = "Option::is_none")]
1119    pub receipt_retention_policy_id: Option<ArtifactId>,
1120    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1121    pub approval_receipt_ids: Vec<ArtifactId>,
1122    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1123    pub reason_codes: Vec<String>,
1124    pub declared_at: DateTime<Utc>,
1125}
1126
1127impl InvariantBudgetV1 {
1128    pub fn full_history() -> Self {
1129        Self {
1130            budget_id: display_only_unstable_id("invariant-budget"),
1131            kind: ArtifactKindV1::InvariantBudget,
1132            history_budget_label: "full-history-preserved".into(),
1133            as_of_query_horizon: "all-recorded-history".into(),
1134            preserved_invariants: vec![
1135                PreservedInvariantV1::Replay,
1136                PreservedInvariantV1::AsOfQuery,
1137                PreservedInvariantV1::ClaimSupport,
1138                PreservedInvariantV1::ReceiptLineage,
1139                PreservedInvariantV1::LegalRetention,
1140                PreservedInvariantV1::OperatorProof,
1141            ],
1142            max_removed_claims: 0,
1143            max_removed_evidence_records: 0,
1144            max_compacted_receipts: 0,
1145            requires_receipt_retention: true,
1146            receipt_retention_policy_id: None,
1147            approval_receipt_ids: Vec::new(),
1148            reason_codes: vec!["full-history-budget-preserves-as-of-queries".into()],
1149            declared_at: Utc::now(),
1150        }
1151    }
1152
1153    pub fn with_removal_limits(
1154        mut self,
1155        max_removed_claims: u32,
1156        max_removed_evidence_records: u32,
1157        max_compacted_receipts: u32,
1158    ) -> Self {
1159        self.max_removed_claims = max_removed_claims;
1160        self.max_removed_evidence_records = max_removed_evidence_records;
1161        self.max_compacted_receipts = max_compacted_receipts;
1162        self.reason_codes
1163            .push("explicit-reduction-limits-declared".into());
1164        self.reason_codes.sort();
1165        self.reason_codes.dedup();
1166        self
1167    }
1168
1169    pub fn with_receipt_retention_policy(
1170        mut self,
1171        retention_policy_id: ArtifactId,
1172        approval_receipt_ids: Vec<ArtifactId>,
1173    ) -> Self {
1174        self.receipt_retention_policy_id = Some(retention_policy_id);
1175        self.approval_receipt_ids = sorted_unique_artifact_ids(approval_receipt_ids);
1176        self.reason_codes
1177            .push("receipt-retention-policy-and-approval-declared".into());
1178        self.reason_codes.sort();
1179        self.reason_codes.dedup();
1180        self
1181    }
1182
1183    pub fn preserves(&self, invariant: PreservedInvariantV1) -> bool {
1184        self.preserved_invariants.contains(&invariant)
1185    }
1186
1187    pub fn preserves_as_of_queries(&self) -> bool {
1188        self.preserves(PreservedInvariantV1::AsOfQuery)
1189    }
1190
1191    pub fn preserves_claim_support(&self) -> bool {
1192        self.preserves(PreservedInvariantV1::ClaimSupport)
1193    }
1194
1195    pub fn allows_receipt_compaction(&self) -> bool {
1196        self.max_compacted_receipts > 0
1197            && (!self.requires_receipt_retention
1198                || (self.receipt_retention_policy_id.is_some()
1199                    && !self.approval_receipt_ids.is_empty()))
1200    }
1201}
1202
1203#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1204pub struct SupportCoreV1 {
1205    pub support_core_id: ArtifactId,
1206    pub kind: ArtifactKindV1,
1207    pub accepted_claim_ids: Vec<ArtifactId>,
1208    pub support_claim_ids: Vec<ArtifactId>,
1209    pub support_evidence_ids: Vec<ArtifactId>,
1210    pub support_receipt_ids: Vec<ArtifactId>,
1211    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1212    pub superseded_claim_ids: Vec<ArtifactId>,
1213    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1214    pub quarantined_claim_ids: Vec<ArtifactId>,
1215    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1216    pub reason_codes: Vec<String>,
1217    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1218    pub canonical_backpointers: Vec<CanonicalBackpointerV1>,
1219    pub computed_at: DateTime<Utc>,
1220}
1221
1222impl SupportCoreV1 {
1223    pub fn new(
1224        accepted_claim_ids: Vec<ArtifactId>,
1225        support_claim_ids: Vec<ArtifactId>,
1226        support_evidence_ids: Vec<ArtifactId>,
1227        support_receipt_ids: Vec<ArtifactId>,
1228        superseded_claim_ids: Vec<ArtifactId>,
1229        quarantined_claim_ids: Vec<ArtifactId>,
1230    ) -> Self {
1231        let accepted_claim_ids = sorted_unique_artifact_ids(accepted_claim_ids);
1232        let mut all_support_claim_ids = support_claim_ids;
1233        all_support_claim_ids.extend(accepted_claim_ids.clone());
1234        Self {
1235            support_core_id: display_only_unstable_id("support-core"),
1236            kind: ArtifactKindV1::SupportCore,
1237            accepted_claim_ids,
1238            support_claim_ids: sorted_unique_artifact_ids(all_support_claim_ids),
1239            support_evidence_ids: sorted_unique_artifact_ids(support_evidence_ids),
1240            support_receipt_ids: sorted_unique_artifact_ids(support_receipt_ids),
1241            superseded_claim_ids: sorted_unique_artifact_ids(superseded_claim_ids),
1242            quarantined_claim_ids: sorted_unique_artifact_ids(quarantined_claim_ids),
1243            reason_codes: vec!["accepted-claim-support-core".into()],
1244            canonical_backpointers: canonical_owner_backpointer(
1245                "semantic-memory-forge",
1246                "SupportSetV1",
1247                "canonical-support-expression-owner",
1248            ),
1249            computed_at: Utc::now(),
1250        }
1251    }
1252
1253    pub fn claim_is_support(&self, claim_id: &ArtifactId) -> bool {
1254        self.support_claim_ids.contains(claim_id)
1255    }
1256
1257    pub fn claim_is_superseded_or_quarantined(&self, claim_id: &ArtifactId) -> bool {
1258        self.superseded_claim_ids.contains(claim_id)
1259            || self.quarantined_claim_ids.contains(claim_id)
1260    }
1261
1262    pub fn claim_can_be_removed_without_support_loss(&self, claim_id: &ArtifactId) -> bool {
1263        !self.claim_is_support(claim_id) || self.claim_is_superseded_or_quarantined(claim_id)
1264    }
1265}
1266
1267#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1268pub struct RemovalFrontierV1 {
1269    pub frontier_id: ArtifactId,
1270    pub kind: ArtifactKindV1,
1271    pub support_core_id: ArtifactId,
1272    pub candidate_claim_ids: Vec<ArtifactId>,
1273    pub candidate_evidence_ids: Vec<ArtifactId>,
1274    pub candidate_receipt_ids: Vec<ArtifactId>,
1275    pub removable_claim_ids: Vec<ArtifactId>,
1276    pub blocked_claim_ids: Vec<ArtifactId>,
1277    pub removable_evidence_ids: Vec<ArtifactId>,
1278    pub blocked_evidence_ids: Vec<ArtifactId>,
1279    pub removable_receipt_ids: Vec<ArtifactId>,
1280    pub blocked_receipt_ids: Vec<ArtifactId>,
1281    pub invariant_checks: Vec<String>,
1282    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1283    pub reason_codes: Vec<String>,
1284    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1285    pub canonical_backpointers: Vec<CanonicalBackpointerV1>,
1286    pub computed_at: DateTime<Utc>,
1287}
1288
1289impl RemovalFrontierV1 {
1290    pub fn new(
1291        support_core: &SupportCoreV1,
1292        candidate_claim_ids: Vec<ArtifactId>,
1293        candidate_evidence_ids: Vec<ArtifactId>,
1294        candidate_receipt_ids: Vec<ArtifactId>,
1295        budget: &InvariantBudgetV1,
1296    ) -> Self {
1297        let candidate_claim_ids = sorted_unique_artifact_ids(candidate_claim_ids);
1298        let candidate_evidence_ids = sorted_unique_artifact_ids(candidate_evidence_ids);
1299        let candidate_receipt_ids = sorted_unique_artifact_ids(candidate_receipt_ids);
1300
1301        let mut removable_claim_ids = Vec::new();
1302        let mut blocked_claim_ids = Vec::new();
1303        for claim_id in &candidate_claim_ids {
1304            if support_core.claim_can_be_removed_without_support_loss(claim_id) {
1305                removable_claim_ids.push(claim_id.clone());
1306            } else {
1307                blocked_claim_ids.push(claim_id.clone());
1308            }
1309        }
1310
1311        let mut removable_evidence_ids = Vec::new();
1312        let mut blocked_evidence_ids = Vec::new();
1313        for evidence_id in &candidate_evidence_ids {
1314            if budget.preserves_claim_support()
1315                && support_core.support_evidence_ids.contains(evidence_id)
1316            {
1317                blocked_evidence_ids.push(evidence_id.clone());
1318            } else {
1319                removable_evidence_ids.push(evidence_id.clone());
1320            }
1321        }
1322
1323        let mut removable_receipt_ids = Vec::new();
1324        let mut blocked_receipt_ids = Vec::new();
1325        for receipt_id in &candidate_receipt_ids {
1326            let receipt_lineage_blocks = budget.preserves(PreservedInvariantV1::ReceiptLineage)
1327                && support_core.support_receipt_ids.contains(receipt_id)
1328                && !budget.allows_receipt_compaction();
1329            let retention_policy_blocks =
1330                budget.requires_receipt_retention && !budget.allows_receipt_compaction();
1331            if receipt_lineage_blocks || retention_policy_blocks {
1332                blocked_receipt_ids.push(receipt_id.clone());
1333            } else {
1334                removable_receipt_ids.push(receipt_id.clone());
1335            }
1336        }
1337
1338        let mut invariant_checks = Vec::new();
1339        if budget.preserves_claim_support() {
1340            invariant_checks.push("claim-support-preserved".into());
1341        }
1342        if budget.preserves_as_of_queries() {
1343            invariant_checks.push("as-of-query-preserved".into());
1344        }
1345        if budget.preserves(PreservedInvariantV1::ReceiptLineage) {
1346            invariant_checks.push("receipt-lineage-preserved".into());
1347        }
1348        if budget.preserves(PreservedInvariantV1::LegalRetention) {
1349            invariant_checks.push("legal-retention-preserved".into());
1350        }
1351        let blocked = !blocked_claim_ids.is_empty()
1352            || !blocked_evidence_ids.is_empty()
1353            || !blocked_receipt_ids.is_empty();
1354
1355        Self {
1356            frontier_id: display_only_unstable_id("removal-frontier"),
1357            kind: ArtifactKindV1::RemovalFrontier,
1358            support_core_id: support_core.support_core_id.clone(),
1359            candidate_claim_ids,
1360            candidate_evidence_ids,
1361            candidate_receipt_ids,
1362            removable_claim_ids: sorted_unique_artifact_ids(removable_claim_ids),
1363            blocked_claim_ids: sorted_unique_artifact_ids(blocked_claim_ids),
1364            removable_evidence_ids: sorted_unique_artifact_ids(removable_evidence_ids),
1365            blocked_evidence_ids: sorted_unique_artifact_ids(blocked_evidence_ids),
1366            removable_receipt_ids: sorted_unique_artifact_ids(removable_receipt_ids),
1367            blocked_receipt_ids: sorted_unique_artifact_ids(blocked_receipt_ids),
1368            invariant_checks,
1369            reason_codes: if blocked {
1370                vec!["removal-frontier-blocked-by-invariant".into()]
1371            } else {
1372                vec!["removal-frontier-lawful".into()]
1373            },
1374            canonical_backpointers: canonical_owner_backpointer(
1375                "semantic-memory-forge",
1376                "SupportSetV1",
1377                "canonical-removal-support-owner",
1378            ),
1379            computed_at: Utc::now(),
1380        }
1381    }
1382
1383    pub fn is_blocked(&self) -> bool {
1384        !self.blocked_claim_ids.is_empty()
1385            || !self.blocked_evidence_ids.is_empty()
1386            || !self.blocked_receipt_ids.is_empty()
1387    }
1388}
1389
1390#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1391pub struct SubtractionPlanV1 {
1392    pub plan_id: ArtifactId,
1393    pub kind: ArtifactKindV1,
1394    pub operator: SubtractionOperatorV1,
1395    pub support_core_id: ArtifactId,
1396    pub removal_frontier_id: ArtifactId,
1397    pub invariant_budget_id: ArtifactId,
1398    pub target_claim_ids: Vec<ArtifactId>,
1399    pub target_evidence_ids: Vec<ArtifactId>,
1400    pub target_receipt_ids: Vec<ArtifactId>,
1401    pub dry_run: bool,
1402    pub approved_for_mutation: bool,
1403    pub destructive_deletion: bool,
1404    pub blocked: bool,
1405    #[serde(default = "v11_activation_reserved_draft")]
1406    pub activation_level: V11ActivationLevelV1,
1407    #[serde(default = "bool_true")]
1408    pub advisory_only: bool,
1409    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1410    pub reason_codes: Vec<String>,
1411    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1412    pub canonical_backpointers: Vec<CanonicalBackpointerV1>,
1413    pub planned_at: DateTime<Utc>,
1414}
1415
1416impl SubtractionPlanV1 {
1417    pub fn dry_run(
1418        operator: SubtractionOperatorV1,
1419        support_core: &SupportCoreV1,
1420        frontier: &RemovalFrontierV1,
1421        budget: &InvariantBudgetV1,
1422    ) -> Self {
1423        let mut reason_codes = vec![
1424            "subtraction-plan-dry-run-before-mutation".into(),
1425            "v11b-subtraction-plan-advisory-only".into(),
1426        ];
1427        if frontier.is_blocked() {
1428            reason_codes.push("subtraction-plan-blocked-by-frontier".into());
1429        }
1430        if !frontier.candidate_receipt_ids.is_empty() && !budget.allows_receipt_compaction() {
1431            reason_codes.push("receipt-compaction-requires-retention-policy-and-approval".into());
1432        }
1433        let blocked = frontier.is_blocked()
1434            || (!frontier.candidate_receipt_ids.is_empty() && !budget.allows_receipt_compaction());
1435        Self {
1436            plan_id: display_only_unstable_id("subtraction-plan"),
1437            kind: ArtifactKindV1::SubtractionPlan,
1438            operator,
1439            support_core_id: support_core.support_core_id.clone(),
1440            removal_frontier_id: frontier.frontier_id.clone(),
1441            invariant_budget_id: budget.budget_id.clone(),
1442            target_claim_ids: frontier.candidate_claim_ids.clone(),
1443            target_evidence_ids: frontier.candidate_evidence_ids.clone(),
1444            target_receipt_ids: frontier.candidate_receipt_ids.clone(),
1445            dry_run: true,
1446            approved_for_mutation: false,
1447            destructive_deletion: false,
1448            blocked,
1449            activation_level: V11ActivationLevelV1::AdvisoryOnly,
1450            advisory_only: true,
1451            reason_codes,
1452            canonical_backpointers: canonical_owner_backpointer(
1453                "semantic-memory-forge",
1454                "RetractionRecordV1",
1455                "canonical-subtraction-retraction-owner",
1456            ),
1457            planned_at: Utc::now(),
1458        }
1459    }
1460
1461    pub fn is_lawful_append_only_reduction(&self) -> bool {
1462        self.dry_run && !self.blocked && !self.destructive_deletion
1463    }
1464
1465    pub fn can_mutate_runtime_state(&self) -> bool {
1466        self.activation_level.is_active()
1467            && !self.advisory_only
1468            && self.approved_for_mutation
1469            && !self.dry_run
1470            && !self.blocked
1471            && !self.destructive_deletion
1472    }
1473}
1474
1475#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1476pub struct HistoryPreservationReportV1 {
1477    pub report_id: ArtifactId,
1478    pub kind: ArtifactKindV1,
1479    pub plan_id: ArtifactId,
1480    pub invariant_budget_id: ArtifactId,
1481    pub before_digest: DisplayDigestV1,
1482    pub after_digest: DisplayDigestV1,
1483    pub checked_query_receipt_ids: Vec<ArtifactId>,
1484    pub preserved_invariants: Vec<PreservedInvariantV1>,
1485    pub as_of_queries_preserved: bool,
1486    pub claim_support_preserved: bool,
1487    pub receipt_lineage_preserved: bool,
1488    pub legal_retention_preserved: bool,
1489    pub degraded: bool,
1490    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1491    pub reason_codes: Vec<String>,
1492    pub recorded_at: DateTime<Utc>,
1493}
1494
1495impl HistoryPreservationReportV1 {
1496    pub fn new(
1497        plan: &SubtractionPlanV1,
1498        budget: &InvariantBudgetV1,
1499        before_digest: DisplayDigestV1,
1500        after_digest: DisplayDigestV1,
1501        checked_query_receipt_ids: Vec<ArtifactId>,
1502    ) -> Self {
1503        let digest_stable = before_digest == after_digest;
1504        let as_of_queries_preserved = !budget.preserves_as_of_queries()
1505            || digest_stable
1506            || !checked_query_receipt_ids.is_empty();
1507        let claim_support_preserved = !budget.preserves_claim_support() || !plan.blocked;
1508        let receipt_lineage_preserved = plan.target_receipt_ids.is_empty()
1509            || !budget.preserves(PreservedInvariantV1::ReceiptLineage)
1510            || budget.allows_receipt_compaction();
1511        let legal_retention_preserved = plan.target_receipt_ids.is_empty()
1512            || !budget.preserves(PreservedInvariantV1::LegalRetention)
1513            || budget.allows_receipt_compaction();
1514        let degraded = !(as_of_queries_preserved
1515            && claim_support_preserved
1516            && receipt_lineage_preserved
1517            && legal_retention_preserved);
1518        let mut reason_codes = if degraded {
1519            vec!["history-preservation-degraded".into()]
1520        } else {
1521            vec!["history-preservation-passed".into()]
1522        };
1523        if digest_stable {
1524            reason_codes.push("before-after-digests-stable".into());
1525        }
1526        Self {
1527            report_id: display_only_unstable_id("history-preservation-report"),
1528            kind: ArtifactKindV1::HistoryPreservationReport,
1529            plan_id: plan.plan_id.clone(),
1530            invariant_budget_id: budget.budget_id.clone(),
1531            before_digest,
1532            after_digest,
1533            checked_query_receipt_ids: sorted_unique_artifact_ids(checked_query_receipt_ids),
1534            preserved_invariants: sorted_unique_invariants(budget.preserved_invariants.clone()),
1535            as_of_queries_preserved,
1536            claim_support_preserved,
1537            receipt_lineage_preserved,
1538            legal_retention_preserved,
1539            degraded,
1540            reason_codes,
1541            recorded_at: Utc::now(),
1542        }
1543    }
1544
1545    pub fn preserves_declared_history(&self) -> bool {
1546        !self.degraded
1547    }
1548}
1549
1550#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1551pub struct CompactionReportV1 {
1552    pub receipt_id: ArtifactId,
1553    pub kind: ArtifactKindV1,
1554    pub plan_id: ArtifactId,
1555    pub support_core_id: ArtifactId,
1556    pub removal_frontier_id: ArtifactId,
1557    pub invariant_budget_id: ArtifactId,
1558    pub history_report_id: ArtifactId,
1559    pub operator: SubtractionOperatorV1,
1560    pub before_digest: DisplayDigestV1,
1561    pub after_digest: DisplayDigestV1,
1562    pub compacted: bool,
1563    pub destructive_deletion: bool,
1564    pub receipt_compaction_approved: bool,
1565    #[serde(default, skip_serializing_if = "Option::is_none")]
1566    pub run_id: Option<ArtifactId>,
1567    #[serde(default, skip_serializing_if = "Option::is_none")]
1568    pub attempt_id: Option<ArtifactId>,
1569    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1570    pub source_receipt_ids: Vec<ArtifactId>,
1571    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1572    pub reason_codes: Vec<String>,
1573    pub compacted_at: DateTime<Utc>,
1574}
1575
1576impl CompactionReportV1 {
1577    pub fn append_only(
1578        plan: &SubtractionPlanV1,
1579        report: &HistoryPreservationReportV1,
1580        source_receipt_ids: Vec<ArtifactId>,
1581    ) -> Self {
1582        let compacted =
1583            plan.is_lawful_append_only_reduction() && report.preserves_declared_history();
1584        Self {
1585            receipt_id: display_only_unstable_id("compaction-report"),
1586            kind: ArtifactKindV1::CompactionReport,
1587            plan_id: plan.plan_id.clone(),
1588            support_core_id: plan.support_core_id.clone(),
1589            removal_frontier_id: plan.removal_frontier_id.clone(),
1590            invariant_budget_id: plan.invariant_budget_id.clone(),
1591            history_report_id: report.report_id.clone(),
1592            operator: plan.operator,
1593            before_digest: report.before_digest.clone(),
1594            after_digest: report.after_digest.clone(),
1595            compacted,
1596            destructive_deletion: false,
1597            receipt_compaction_approved: plan.target_receipt_ids.is_empty()
1598                || report.receipt_lineage_preserved,
1599            run_id: None,
1600            attempt_id: None,
1601            source_receipt_ids: sorted_unique_artifact_ids(source_receipt_ids),
1602            reason_codes: if compacted {
1603                vec!["append-only-compaction-recorded".into()]
1604            } else {
1605                vec!["compaction-blocked-by-history-preservation".into()]
1606            },
1607            compacted_at: Utc::now(),
1608        }
1609    }
1610}
1611
1612#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1613#[serde(rename_all = "kebab-case")]
1614pub enum CapabilityStateV1 {
1615    Configured,
1616    Available,
1617    Healthy,
1618    Registered,
1619    ExposedThisTurn,
1620    ExecutableThisTurn,
1621    Attempted,
1622    Succeeded,
1623    Failed,
1624    Declared,
1625    Invoked,
1626    Hidden,
1627    Degraded,
1628    FallbackOnly,
1629    Unavailable,
1630    Deferred,
1631    Disabled,
1632    BlockedByPolicy,
1633    RequiresApproval,
1634}
1635
1636#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1637pub struct RuntimeCapabilityTruthV1 {
1638    pub capability_id: String,
1639    pub states: Vec<CapabilityStateV1>,
1640    pub reason: Option<String>,
1641}
1642
1643#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1644pub struct ToolSchemaV1 {
1645    pub input_schema: serde_json::Value,
1646    pub output_schema: serde_json::Value,
1647    pub parser_fallback_hint: String,
1648}
1649
1650impl ToolSchemaV1 {
1651    pub fn canonical_backpointer(&self) -> CanonicalBackpointerV1 {
1652        CanonicalBackpointerV1::owner_type(
1653            "llm-tool-runtime",
1654            "ToolDescriptor.input_schema",
1655            "canonical-tool-schema-owner",
1656        )
1657    }
1658}
1659
1660#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1661pub struct ToolDescriptorV1 {
1662    pub namespace: String,
1663    pub name: String,
1664    pub version: String,
1665    pub description: String,
1666    pub risk_class: CanonicalToolSideEffectClass,
1667    pub read_only: bool,
1668    pub hidden: bool,
1669    #[serde(default)]
1670    pub requires_native_tool_loop: bool,
1671    pub schema: ToolSchemaV1,
1672}
1673
1674impl ToolDescriptorV1 {
1675    pub fn tool_id(&self) -> String {
1676        format!("{}:{}:{}", self.namespace, self.name, self.version)
1677    }
1678
1679    pub fn canonical_backpointer(&self) -> CanonicalBackpointerV1 {
1680        CanonicalBackpointerV1::external(
1681            "llm-tool-runtime",
1682            "ToolDescriptor",
1683            "canonical-tool-descriptor-owner",
1684            self.tool_id(),
1685        )
1686    }
1687}
1688
1689#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1690pub struct ToolProviderSchemaV1 {
1691    pub tool_id: String,
1692    pub name: String,
1693    pub description: String,
1694    pub input_schema: serde_json::Value,
1695    pub output_schema: serde_json::Value,
1696    pub parser_fallback_hint: String,
1697}
1698
1699impl ToolProviderSchemaV1 {
1700    pub fn from_descriptor(descriptor: &ToolDescriptorV1) -> Self {
1701        Self {
1702            tool_id: descriptor.tool_id(),
1703            name: descriptor.name.clone(),
1704            description: descriptor.description.clone(),
1705            input_schema: descriptor.schema.input_schema.clone(),
1706            output_schema: descriptor.schema.output_schema.clone(),
1707            parser_fallback_hint: descriptor.schema.parser_fallback_hint.clone(),
1708        }
1709    }
1710
1711    pub fn canonical_backpointer(&self) -> CanonicalBackpointerV1 {
1712        CanonicalBackpointerV1::external(
1713            "llm-tool-runtime",
1714            "ToolDescriptor",
1715            "canonical-provider-schema-source",
1716            self.tool_id.clone(),
1717        )
1718    }
1719}
1720
1721#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
1722pub struct ToolExposurePlanV1 {
1723    pub exposure_id: ArtifactId,
1724    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1725    pub declared_tool_ids: Vec<String>,
1726    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1727    pub registered_tool_ids: Vec<String>,
1728    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1729    pub executable_tool_ids: Vec<String>,
1730    pub exposed_tool_ids: Vec<String>,
1731    pub hidden_tool_ids: Vec<String>,
1732    pub blocked_tool_ids: Vec<String>,
1733    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1734    pub decisions: Vec<CapabilityGateDecisionV1>,
1735    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1736    pub approval_requests: Vec<ApprovalRequestV1>,
1737    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1738    pub permit_use_receipts: Vec<PermitUseReportV1>,
1739    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1740    pub provider_tool_schemas: Vec<ToolProviderSchemaV1>,
1741    #[serde(default, skip_serializing_if = "Option::is_none")]
1742    pub sandbox_root: Option<String>,
1743    #[serde(default)]
1744    pub degraded: bool,
1745    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1746    pub reason_codes: Vec<String>,
1747    #[serde(default, skip_serializing_if = "Vec::is_empty")]
1748    pub canonical_backpointers: Vec<CanonicalBackpointerV1>,
1749    pub reason: Option<String>,
1750}
1751
1752pub type ToolExposurePlanV2 = ToolExposurePlanV1;
1753pub type ToolExposureSetV1 = ToolExposurePlanV1;