1pub mod verification;
4
5pub use verification::{
6 evaluate_promotion, ClaimEvidenceBundleV1, GovernanceAction, GovernanceDecisionV1, RiskClass,
7 VerificationDisposition, VerificationPlanV1,
8};
9
10pub mod canonical_stack {
11 pub use assurance_runtime;
12 pub use authority_delegation::{
13 ActingOnBehalfReceiptV1, AuthorityChainV1, AuthorityLeaseV1, DelegationBundleV1,
14 };
15 pub use stack_ids::{AttemptId, ClaimVersionId, ScopeKey, TraceCtx};
16 pub use verification_adjudication;
17 pub use verification_adjudication::{
18 AdjudicationResult, PromotionDecision, VerificationDisposition,
19 };
20 pub use verification_calibration::CalibrationSnapshot;
21 pub use verification_control::{
22 CaseRegion, CheckMethod, CheckPlan, ControlReceipt, PromotionClass, ReversibilityClass,
23 TerminalDisposition, VerificationAttempt, VerificationAttemptState, VerificationCase,
24 VerificationCaseClass,
25 };
26 pub use verification_policy::{
27 ApprovalRecord, ApprovalRequirement, ApprovalScope, AutonomyCeiling,
28 DelegationPolicyProfileV1, EffectPolicyProfileV1, ExecutionPermit, MethodPolicy,
29 PermitIssuanceError, PolicyDecision, PolicySnapshot, ReleasePolicyProfileV1,
30 };
31}
32
33pub use canonical_stack::{CheckPlan, VerificationCase};
34
35#[derive(Debug, Clone, Copy, Default)]
36pub struct CanonicalGovernanceAdapter;
37
38impl CanonicalGovernanceAdapter {
39 #[allow(clippy::too_many_arguments)]
40 pub fn verification_case(
41 &self,
42 class: canonical_stack::VerificationCaseClass,
43 namespace: impl Into<String>,
44 target_key: impl Into<String>,
45 trace_ctx: canonical_stack::TraceCtx,
46 attempt_id: canonical_stack::AttemptId,
47 opened_at: impl Into<String>,
48 degraded: bool,
49 advisory_only: bool,
50 ) -> canonical_stack::VerificationCase {
51 let namespace = namespace.into();
52 canonical_stack::VerificationCase::new(
53 class,
54 canonical_stack::CaseRegion {
55 scope_key: Some(canonical_stack::ScopeKey::namespace_only(namespace.clone())),
56 namespace,
57 target_key: target_key.into(),
58 region_id: None,
59 region_digest_id: None,
60 claim_version_id: None,
61 as_of_recorded_at: None,
62 },
63 trace_ctx,
64 attempt_id,
65 opened_at,
66 degraded,
67 advisory_only,
68 )
69 }
70
71 pub fn claim_version_case(
72 &self,
73 namespace: impl Into<String>,
74 claim_version_id: canonical_stack::ClaimVersionId,
75 trace_ctx: canonical_stack::TraceCtx,
76 attempt_id: canonical_stack::AttemptId,
77 opened_at: impl Into<String>,
78 ) -> canonical_stack::VerificationCase {
79 let namespace = namespace.into();
80 canonical_stack::VerificationCase::new(
81 canonical_stack::VerificationCaseClass::UnverifiedClaimVersion,
82 canonical_stack::CaseRegion {
83 scope_key: Some(canonical_stack::ScopeKey::namespace_only(namespace.clone())),
84 namespace,
85 target_key: claim_version_id.as_str().to_owned(),
86 region_id: None,
87 region_digest_id: None,
88 claim_version_id: Some(claim_version_id),
89 as_of_recorded_at: None,
90 },
91 trace_ctx,
92 attempt_id,
93 opened_at,
94 false,
95 false,
96 )
97 }
98
99 #[allow(clippy::too_many_arguments)]
100 pub fn check_plan(
101 &self,
102 case: &canonical_stack::VerificationCase,
103 method: canonical_stack::CheckMethod,
104 check_names: Vec<String>,
105 promotion_class: canonical_stack::PromotionClass,
106 reversibility_class: canonical_stack::ReversibilityClass,
107 promotable_if_completed: bool,
108 advisory_only: bool,
109 degraded: bool,
110 rationale: impl Into<String>,
111 input: serde_json::Value,
112 ) -> canonical_stack::CheckPlan {
113 canonical_stack::CheckPlan::new(
114 case.case_id.clone(),
115 method,
116 check_names,
117 promotion_class,
118 reversibility_class,
119 promotable_if_completed,
120 advisory_only,
121 degraded,
122 rationale,
123 input,
124 )
125 }
126
127 #[allow(clippy::too_many_arguments)]
128 pub fn completed_attempt(
129 &self,
130 case: &canonical_stack::VerificationCase,
131 plan: &canonical_stack::CheckPlan,
132 state: canonical_stack::VerificationAttemptState,
133 started_at: impl Into<String>,
134 finished_at: impl Into<String>,
135 outcome_signature: Option<String>,
136 ) -> canonical_stack::VerificationAttempt {
137 canonical_stack::VerificationAttempt::completed(
138 case.case_id.clone(),
139 plan.plan_id.clone(),
140 case.attempt_id.clone(),
141 None,
142 state,
143 plan.advisory_only,
144 plan.degraded,
145 started_at,
146 finished_at,
147 outcome_signature,
148 )
149 }
150
151 pub fn control_receipt_for_attempt(
152 &self,
153 case: &canonical_stack::VerificationCase,
154 plan: &canonical_stack::CheckPlan,
155 attempt: &canonical_stack::VerificationAttempt,
156 promotable: bool,
157 details: serde_json::Value,
158 ) -> canonical_stack::ControlReceipt {
159 canonical_stack::ControlReceipt::new_case_execution(
160 case, plan, attempt, promotable, details,
161 )
162 }
163
164 #[allow(clippy::too_many_arguments)]
165 pub fn approval_record(
166 &self,
167 policy_version: impl Into<String>,
168 case: Option<&canonical_stack::VerificationCase>,
169 plan: Option<&canonical_stack::CheckPlan>,
170 scope: canonical_stack::ApprovalScope,
171 approver: impl Into<String>,
172 approved_at: impl Into<String>,
173 reviewed_artifact_refs: Vec<String>,
174 rationale: impl Into<String>,
175 ) -> canonical_stack::ApprovalRecord {
176 canonical_stack::ApprovalRecord::new(
177 policy_version,
178 case.map(|case| case.case_id.clone()),
179 plan.map(|plan| plan.plan_id.clone()),
180 scope,
181 approver,
182 approved_at,
183 reviewed_artifact_refs,
184 rationale,
185 )
186 }
187
188 pub fn evaluate_policy(
189 &self,
190 snapshot: &canonical_stack::PolicySnapshot,
191 case: &canonical_stack::VerificationCase,
192 plan: &canonical_stack::CheckPlan,
193 approvals: &[canonical_stack::ApprovalRecord],
194 degraded: bool,
195 budget_exhausted: bool,
196 ) -> canonical_stack::PolicyDecision {
197 verification_policy::evaluate_policy(
198 snapshot,
199 case,
200 plan,
201 approvals,
202 degraded,
203 budget_exhausted,
204 )
205 }
206
207 #[allow(clippy::too_many_arguments)]
208 pub fn calibration_snapshot(
209 &self,
210 case: &canonical_stack::VerificationCase,
211 recorded_at: impl Into<String>,
212 comparability_available: bool,
213 oracle_calibrated: bool,
214 abstention_threshold_micros: u64,
215 observed_risk_micros: u64,
216 drift_markers: Vec<String>,
217 ) -> canonical_stack::CalibrationSnapshot {
218 canonical_stack::CalibrationSnapshot::evaluate(
219 case.case_id.clone(),
220 recorded_at,
221 comparability_available,
222 oracle_calibrated,
223 abstention_threshold_micros,
224 observed_risk_micros,
225 drift_markers,
226 )
227 }
228
229 #[allow(clippy::too_many_arguments)]
230 pub fn adjudicate_case(
231 &self,
232 case: &canonical_stack::VerificationCase,
233 plan: &canonical_stack::CheckPlan,
234 attempt: &canonical_stack::VerificationAttempt,
235 control_receipt: &canonical_stack::ControlReceipt,
236 policy_decision: &canonical_stack::PolicyDecision,
237 calibration_snapshot: &canonical_stack::CalibrationSnapshot,
238 refuted: bool,
239 budget_exhausted: bool,
240 currently_promoted: bool,
241 ) -> canonical_stack::AdjudicationResult {
242 verification_adjudication::adjudicate_case(
243 case,
244 plan,
245 attempt,
246 control_receipt,
247 policy_decision,
248 calibration_snapshot,
249 refuted,
250 budget_exhausted,
251 currently_promoted,
252 )
253 }
254
255 pub fn release_readiness_decision(
256 &self,
257 deployment_profile: &assurance_runtime::DeploymentProfileV1,
258 blocking_gaps: Vec<String>,
259 required_monitors: Vec<String>,
260 advisory_only: bool,
261 ) -> assurance_runtime::ReleaseReadinessDecisionV1 {
262 let decision_state = if !blocking_gaps.is_empty() {
263 assurance_runtime::ReleaseDecisionStateV1::Blocked
264 } else if !required_monitors.is_empty() {
265 assurance_runtime::ReleaseDecisionStateV1::ApprovedWithMonitoring
266 } else {
267 assurance_runtime::ReleaseDecisionStateV1::Approved
268 };
269
270 let decision_id = stack_ids::ArtifactId::generate().as_str().to_string();
271 let deployment_profile_id = deployment_profile.deployment_profile_id.clone();
272 let generated_at = chrono::Utc::now().to_rfc3339();
273
274 match assurance_runtime::ReleaseReadinessDecisionV1::new(
275 decision_id.clone(),
276 deployment_profile_id.clone(),
277 decision_state,
278 blocking_gaps,
279 required_monitors,
280 advisory_only,
281 generated_at.clone(),
282 ) {
283 Ok(decision) => decision,
284 Err(_) => assurance_runtime::ReleaseReadinessDecisionV1 {
285 schema_version: "ReleaseReadinessDecisionV1".to_string(),
286 release_readiness_decision_id: decision_id,
287 deployment_profile_id,
288 decision_state: assurance_runtime::ReleaseDecisionStateV1::Blocked,
289 blocking_gaps: vec!["construct-fallback".to_string()],
290 required_monitors: Vec::new(),
291 advisory_only,
292 generated_at,
293 },
294 }
295 }
296
297 pub fn create_verification_plan(
298 &self,
299 claim_id: &str,
300 risk_class: crate::RiskClass,
301 ) -> crate::VerificationPlanV1 {
302 crate::VerificationPlanV1::new(claim_id, risk_class)
303 }
304
305 pub fn create_claim_evidence_bundle(
306 &self,
307 claim_id: &str,
308 treatment: &str,
309 outcome: &str,
310 ) -> crate::ClaimEvidenceBundleV1 {
311 crate::ClaimEvidenceBundleV1::new(claim_id, treatment, outcome)
312 }
313
314 pub fn authority_chain(
315 &self,
316 lease: &canonical_stack::AuthorityLeaseV1,
317 acting_on_behalf: &canonical_stack::ActingOnBehalfReceiptV1,
318 ) -> canonical_stack::DelegationBundleV1 {
319 let delegation_bundle_id = stack_ids::ArtifactId::generate().as_str().to_string();
320
321 let mut delegated_rights = vec![acting_on_behalf.delegated_action.clone()];
322 if delegated_rights.iter().all(|value| value.trim().is_empty()) {
323 delegated_rights = vec!["delegated-action:fallback".to_string()];
324 }
325
326 let mut replay_requirements = vec![acting_on_behalf.effect_execution_receipt_id.clone()];
327 if replay_requirements
328 .iter()
329 .all(|value| value.trim().is_empty())
330 {
331 replay_requirements = vec!["replay-requirement:fallback".to_string()];
332 }
333
334 let authority_lease_id = if lease.authority_lease_id.is_empty() {
335 "authority-lease:unknown".to_string()
336 } else {
337 lease.authority_lease_id.clone()
338 };
339
340 match canonical_stack::DelegationBundleV1::new(
341 delegation_bundle_id.clone(),
342 authority_lease_id,
343 delegated_rights,
344 lease.hard_ceilings.clone(),
345 replay_requirements,
346 acting_on_behalf.within_lease,
347 ) {
348 Ok(bundle) => bundle,
349 Err(_) => canonical_stack::DelegationBundleV1 {
350 schema_version: "DelegationBundleV1".to_string(),
351 delegation_bundle_id,
352 authority_lease_id: lease.authority_lease_id.clone(),
353 delegated_rights: vec!["delegated-action:fallback".to_string()],
354 reduced_ceilings: lease.hard_ceilings.clone(),
355 replay_requirements: vec!["replay-requirement:fallback".to_string()],
356 further_delegation_permitted: false,
357 },
358 }
359 }
360}
361
362#[derive(Debug, Clone)]
363pub struct GovernanceContext {
364 adapter: CanonicalGovernanceAdapter,
365 policy_snapshot: canonical_stack::PolicySnapshot,
366}
367
368impl GovernanceContext {
369 pub fn new(policy: canonical_stack::PolicySnapshot) -> Self {
370 Self {
371 adapter: CanonicalGovernanceAdapter::default(),
372 policy_snapshot: policy,
373 }
374 }
375
376 pub fn open_case(
377 &self,
378 class: canonical_stack::VerificationCaseClass,
379 namespace: impl Into<String>,
380 target_key: impl Into<String>,
381 trace_ctx: canonical_stack::TraceCtx,
382 attempt_id: canonical_stack::AttemptId,
383 ) -> canonical_stack::VerificationCase {
384 self.adapter.verification_case(
385 class,
386 namespace,
387 target_key,
388 trace_ctx,
389 attempt_id,
390 chrono::Utc::now().to_rfc3339(),
391 false,
392 false,
393 )
394 }
395
396 pub fn check_permit(
397 &self,
398 case: &canonical_stack::VerificationCase,
399 plan: &canonical_stack::CheckPlan,
400 ) -> Result<canonical_stack::ExecutionPermit, canonical_stack::PermitIssuanceError> {
401 let decision =
402 self.adapter
403 .evaluate_policy(&self.policy_snapshot, case, plan, &[], false, false);
404 decision.issue_execution_permit(case, plan, &[])
405 }
406
407 pub fn adjudicate(
408 &self,
409 case: &canonical_stack::VerificationCase,
410 plan: &canonical_stack::CheckPlan,
411 attempt: &canonical_stack::VerificationAttempt,
412 control_receipt: &canonical_stack::ControlReceipt,
413 policy_decision: &canonical_stack::PolicyDecision,
414 calibration: &canonical_stack::CalibrationSnapshot,
415 refuted: bool,
416 budget_exhausted: bool,
417 currently_promoted: bool,
418 ) -> canonical_stack::AdjudicationResult {
419 self.adapter.adjudicate_case(
420 case,
421 plan,
422 attempt,
423 control_receipt,
424 policy_decision,
425 calibration,
426 refuted,
427 budget_exhausted,
428 currently_promoted,
429 )
430 }
431}
432
433#[cfg(test)]
434mod tests {
435 use super::*;
436
437 #[test]
438 fn creates_canonical_control_receipt() {
439 let adapter = CanonicalGovernanceAdapter;
440 let case = adapter.claim_version_case(
441 "aidens",
442 canonical_stack::ClaimVersionId::new("claim-version:governance"),
443 canonical_stack::TraceCtx::from_trace_id("trace:governance"),
444 canonical_stack::AttemptId::new("attempt:governance"),
445 "2026-04-28T00:00:00Z",
446 );
447 let plan = adapter.check_plan(
448 &case,
449 canonical_stack::CheckMethod::CausalRefuter,
450 vec!["causal-refuter".into()],
451 canonical_stack::PromotionClass::P1,
452 canonical_stack::ReversibilityClass::RequiresSupersession,
453 true,
454 false,
455 false,
456 "canonical verification-control check plan",
457 serde_json::json!({"source": "aidens-governance-kit"}),
458 );
459 let attempt = adapter.completed_attempt(
460 &case,
461 &plan,
462 canonical_stack::VerificationAttemptState::Succeeded,
463 "2026-04-28T00:00:00Z",
464 "2026-04-28T00:00:01Z",
465 Some("ok".into()),
466 );
467 let receipt = adapter.control_receipt_for_attempt(
468 &case,
469 &plan,
470 &attempt,
471 true,
472 serde_json::json!({"promotable": true}),
473 );
474
475 assert_eq!(
476 receipt.schema_version,
477 verification_control::CONTROL_RECEIPT_V1_SCHEMA
478 );
479 assert_eq!(receipt.case_id, Some(case.case_id));
480 assert_eq!(receipt.plan_id, Some(plan.plan_id));
481 }
482
483 #[test]
484 fn governance_context_open_case_and_issue_permit() {
485 let policy = canonical_stack::PolicySnapshot::permissive(
486 "policy:p30-permissive",
487 "2026-01-01T00:00:00Z",
488 );
489 let context = GovernanceContext::new(policy);
490
491 let case = context.open_case(
492 canonical_stack::VerificationCaseClass::UnverifiedClaimVersion,
493 "aidens",
494 "claim-version:governance",
495 canonical_stack::TraceCtx::from_trace_id("trace:governance"),
496 canonical_stack::AttemptId::new("attempt:governance"),
497 );
498 let plan = CanonicalGovernanceAdapter.check_plan(
499 &case,
500 canonical_stack::CheckMethod::CausalRefuter,
501 vec!["causal-refuter".into()],
502 canonical_stack::PromotionClass::P1,
503 canonical_stack::ReversibilityClass::RequiresSupersession,
504 true,
505 false,
506 false,
507 "permissive governance check plan",
508 serde_json::json!({"source": "aidens-governance-kit"}),
509 );
510
511 let permit = context
512 .check_permit(&case, &plan)
513 .expect("permissive policy should issue permit");
514
515 assert_eq!(permit.case_id(), &case.case_id);
516 assert_eq!(permit.plan_id(), &plan.plan_id);
517 }
518
519 #[test]
520 fn risk_bearing_claim_cannot_promote_without_verification_plan() {
521 let adapter = CanonicalGovernanceAdapter;
522 let plan = adapter.create_verification_plan("claim-01", RiskClass::High);
523 assert!(
525 !plan.can_promote(),
526 "high-risk claim must not promote with Pending plan"
527 );
528 assert!(
529 !plan.is_blocked(),
530 "plan should not be blocked, just pending"
531 );
532 }
533
534 #[test]
535 fn create_verification_plan_high_risk_requires_falsification() {
536 let adapter = CanonicalGovernanceAdapter;
537 let plan = adapter.create_verification_plan("claim-high", RiskClass::High);
538 assert!(!plan.required_falsification_checks.is_empty());
539 assert!(!plan.required_replay_checks.is_empty());
540 }
541
542 #[test]
543 fn create_claim_evidence_bundle_marks_support() {
544 let adapter = CanonicalGovernanceAdapter;
545 let bundle = adapter.create_claim_evidence_bundle("claim-01", "experiment", "pass");
546 assert!(bundle.supports);
547 assert_eq!(bundle.claim_id, "claim-01");
548 }
549
550 #[test]
551 fn create_claim_evidence_bundle_marks_refutation() {
552 let adapter = CanonicalGovernanceAdapter;
553 let bundle = adapter.create_claim_evidence_bundle("claim-01", "experiment", "fail");
554 assert!(!bundle.supports);
555 }
556}