1use 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;