Skip to main content

constitutional_memory/
lib.rs

1//! Typed charter and archive surface crate for constitutional artifact families.
2//!
3//! The compatibility name stays in place, but the crate is not a hidden
4//! governance runtime. It publishes typed constitutional surfaces and bounded
5//! reconstruction checks for amendment and archive flows.
6//!
7//! ## Integration Points
8//!
9//! - **forge-pilot observe phase:** `governance_gate.rs` (`#[cfg(feature = "governance")]`) reads
10//!   charter bundle publication status and doctrine snapshot advisory state from semantic-memory
11//!   projections.
12//! - **forge-pilot act phase:** amendment decision dispositions (blocked vs. approved) and
13//!   archive manifest replay guarantees constrain constitutional change execution.
14//! - **verification-control:** amendment proposal and amendment decision case types are affected;
15//!   archive compaction triggers historical query guarantee verification cases.
16//! - **Stack Arena scenarios:** governed lane exercises governance observation of charter
17//!   bundle state and amendment decision dispositions.
18//!
19//! ## Artifact Families
20//!
21//! | Artifact | Schema Version | Owner |
22//! |----------|---------------|-------|
23//! | [`CharterBundleV1`] | `charter_bundle_v1` | this crate |
24//! | [`DoctrineSnapshotV1`] | `doctrine_snapshot_v1` | this crate |
25//! | [`AmendmentProposalV1`] | `amendment_proposal_v1` | this crate |
26//! | [`AmendmentDecisionV1`] | `amendment_decision_v1` | this crate |
27//! | [`ArchiveManifestV1`] | `archive_manifest_v1` | this crate |
28//! | [`CompactionReceiptV1`] | `compaction_receipt_v1` | this crate |
29//! | [`HistoricalQueryGuaranteeV1`] | `historical_query_guarantee_v1` | this crate |
30//! | [`DeprecationBundleV1`] | `deprecation_bundle_v1` | this crate |
31//! | [`RetirementBundleV1`] | `retirement_bundle_v1` | this crate |
32
33pub mod error;
34pub use error::*;
35
36use schemars::JsonSchema;
37use serde::{Deserialize, Serialize};
38use stack_ids::{
39    AmendmentDecisionId, AmendmentProposalId, ArchiveManifestId, CharterBundleId,
40    CompactionReceiptId, DeprecationBundleId, DoctrineSnapshotId, HistoricalQueryGuaranteeId,
41    RetirementBundleId, SemanticDiffId, SurfaceStatus,
42};
43
44pub const CHARTER_BUNDLE_V1_SCHEMA: &str = "charter_bundle_v1";
45pub const DOCTRINE_SNAPSHOT_V1_SCHEMA: &str = "doctrine_snapshot_v1";
46pub const AMENDMENT_PROPOSAL_V1_SCHEMA: &str = "amendment_proposal_v1";
47pub const AMENDMENT_DECISION_V1_SCHEMA: &str = "amendment_decision_v1";
48pub const ARCHIVE_MANIFEST_V1_SCHEMA: &str = "archive_manifest_v1";
49pub const COMPACTION_RECEIPT_V1_SCHEMA: &str = "compaction_receipt_v1";
50pub const HISTORICAL_QUERY_GUARANTEE_V1_SCHEMA: &str = "historical_query_guarantee_v1";
51pub const DEPRECATION_BUNDLE_V1_SCHEMA: &str = "deprecation_bundle_v1";
52pub const RETIREMENT_BUNDLE_V1_SCHEMA: &str = "retirement_bundle_v1";
53
54#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
55pub struct CharterBundleV1 {
56    pub schema_version: String,
57    pub charter_bundle_id: CharterBundleId,
58    pub charter_name: String,
59    pub canonical_owner: String,
60    pub publication_status: SurfaceStatus,
61}
62
63#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
64pub struct DoctrineSnapshotV1 {
65    pub schema_version: String,
66    pub doctrine_snapshot_id: DoctrineSnapshotId,
67    pub charter_bundle_id: CharterBundleId,
68    pub doctrine_version: String,
69    pub advisory_only: bool,
70}
71
72#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
73pub struct AmendmentProposalV1 {
74    pub schema_version: String,
75    pub amendment_proposal_id: AmendmentProposalId,
76    pub charter_bundle_id: CharterBundleId,
77    pub doctrine_snapshot_id: DoctrineSnapshotId,
78    pub proposal_title: String,
79    #[serde(default)]
80    pub migration_obligations: Vec<String>,
81    #[serde(default, skip_serializing_if = "Option::is_none")]
82    pub semantic_diff_id: Option<SemanticDiffId>,
83    #[serde(default, skip_serializing_if = "Option::is_none")]
84    pub expected_archive_action: Option<String>,
85    #[serde(default)]
86    pub required_historical_query_modes: Vec<String>,
87    #[serde(default, skip_serializing_if = "Option::is_none")]
88    pub rollback_handle: Option<String>,
89    pub advisory_only: bool,
90}
91
92#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
93#[serde(rename_all = "snake_case")]
94pub enum AmendmentDisposition {
95    ApprovedWithRollback,
96    BlockedMissingRollback,
97    BlockedMissingSemanticDiff,
98    BlockedMissingArchiveGuarantee,
99    AdvisoryOnly,
100}
101
102#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
103pub struct AmendmentDecisionV1 {
104    pub schema_version: String,
105    pub amendment_decision_id: AmendmentDecisionId,
106    pub amendment_proposal_id: AmendmentProposalId,
107    pub disposition: AmendmentDisposition,
108    #[serde(default, skip_serializing_if = "Option::is_none")]
109    pub semantic_diff_id: Option<SemanticDiffId>,
110    #[serde(default, skip_serializing_if = "Option::is_none")]
111    pub archive_manifest_id: Option<ArchiveManifestId>,
112    #[serde(default, skip_serializing_if = "Option::is_none")]
113    pub historical_query_guarantee_id: Option<HistoricalQueryGuaranteeId>,
114    pub archive_consequence_summary: String,
115    #[serde(default)]
116    pub unresolved_obligations: Vec<String>,
117    pub advisory_only: bool,
118    pub rollback_ready: bool,
119    pub decided_at: String,
120}
121
122#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
123pub struct ArchiveManifestV1 {
124    pub schema_version: String,
125    pub archive_manifest_id: ArchiveManifestId,
126    pub charter_bundle_id: CharterBundleId,
127    #[serde(default)]
128    pub preserved_refs: Vec<String>,
129    pub replay_guarantee: String,
130}
131
132#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
133pub struct CompactionReceiptV1 {
134    pub schema_version: String,
135    pub compaction_receipt_id: CompactionReceiptId,
136    pub archive_manifest_id: ArchiveManifestId,
137    #[serde(default)]
138    pub dropped_detail_refs: Vec<String>,
139    pub queryable_degradation_note: String,
140}
141
142#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
143pub struct HistoricalQueryGuaranteeV1 {
144    pub schema_version: String,
145    pub historical_query_guarantee_id: HistoricalQueryGuaranteeId,
146    pub archive_manifest_id: ArchiveManifestId,
147    pub guarantee_text: String,
148    pub horizon_only: bool,
149    #[serde(default)]
150    pub guaranteed_query_modes: Vec<String>,
151}
152
153#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
154pub struct DeprecationBundleV1 {
155    pub schema_version: String,
156    pub deprecation_bundle_id: DeprecationBundleId,
157    pub charter_bundle_id: CharterBundleId,
158    pub target_surface: String,
159    pub replacement_surface: String,
160    pub advisory_only: bool,
161}
162
163#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
164pub struct RetirementBundleV1 {
165    pub schema_version: String,
166    pub retirement_bundle_id: RetirementBundleId,
167    pub deprecation_bundle_id: DeprecationBundleId,
168    pub horizon_only: bool,
169}
170
171pub fn evaluate_amendment(
172    proposal: &AmendmentProposalV1,
173    migration_obligations_satisfied: bool,
174    archive_manifest: Option<&ArchiveManifestV1>,
175    historical_query_guarantee: Option<&HistoricalQueryGuaranteeV1>,
176    decided_at: impl Into<String>,
177) -> AmendmentDecisionV1 {
178    let rollback_ready = proposal.rollback_handle.is_some();
179    let semantic_diff_ready = proposal.semantic_diff_id.is_some();
180    let archive_ready = archive_manifest.is_some() && historical_query_guarantee.is_some();
181    let disposition = if proposal.advisory_only {
182        AmendmentDisposition::AdvisoryOnly
183    } else if !rollback_ready {
184        AmendmentDisposition::BlockedMissingRollback
185    } else if !semantic_diff_ready {
186        AmendmentDisposition::BlockedMissingSemanticDiff
187    } else if !archive_ready {
188        AmendmentDisposition::BlockedMissingArchiveGuarantee
189    } else if migration_obligations_satisfied {
190        AmendmentDisposition::ApprovedWithRollback
191    } else {
192        AmendmentDisposition::BlockedMissingRollback
193    };
194
195    let mut unresolved_obligations =
196        if matches!(disposition, AmendmentDisposition::ApprovedWithRollback) {
197            vec![]
198        } else {
199            proposal.migration_obligations.clone()
200        };
201    if !rollback_ready {
202        unresolved_obligations.push("rollback handle must be present".into());
203    }
204    if !semantic_diff_ready {
205        unresolved_obligations.push("semantic diff linkage must remain explicit".into());
206    }
207    if !archive_ready {
208        unresolved_obligations
209            .push("archive manifest and historical query guarantee must be emitted".into());
210    }
211
212    AmendmentDecisionV1 {
213        schema_version: AMENDMENT_DECISION_V1_SCHEMA.into(),
214        amendment_decision_id: AmendmentDecisionId::generate(),
215        amendment_proposal_id: proposal.amendment_proposal_id.clone(),
216        disposition,
217        semantic_diff_id: proposal.semantic_diff_id.clone(),
218        archive_manifest_id: archive_manifest.map(|manifest| manifest.archive_manifest_id.clone()),
219        historical_query_guarantee_id: historical_query_guarantee
220            .map(|guarantee| guarantee.historical_query_guarantee_id.clone()),
221        archive_consequence_summary: proposal
222            .expected_archive_action
223            .clone()
224            .unwrap_or_else(|| "archive consequences were not described".into()),
225        unresolved_obligations,
226        advisory_only: !matches!(disposition, AmendmentDisposition::ApprovedWithRollback),
227        rollback_ready,
228        decided_at: decided_at.into(),
229    }
230}
231
232pub fn evaluate_archive_compaction(
233    charter_bundle_id: CharterBundleId,
234    preserved_refs: Vec<String>,
235    dropped_detail_refs: Vec<String>,
236    guaranteed_query_modes: Vec<String>,
237) -> (
238    ArchiveManifestV1,
239    HistoricalQueryGuaranteeV1,
240    CompactionReceiptV1,
241) {
242    let archive_manifest = ArchiveManifestV1 {
243        schema_version: ARCHIVE_MANIFEST_V1_SCHEMA.into(),
244        archive_manifest_id: ArchiveManifestId::generate(),
245        charter_bundle_id,
246        preserved_refs,
247        replay_guarantee: "historical replay remains reconstructable through the archive manifest"
248            .into(),
249    };
250    let historical_query_guarantee = HistoricalQueryGuaranteeV1 {
251        schema_version: HISTORICAL_QUERY_GUARANTEE_V1_SCHEMA.into(),
252        historical_query_guarantee_id: HistoricalQueryGuaranteeId::generate(),
253        archive_manifest_id: archive_manifest.archive_manifest_id.clone(),
254        guarantee_text:
255            "compaction may drop detail refs but historical constitutional queries stay answerable"
256                .into(),
257        horizon_only: false,
258        guaranteed_query_modes,
259    };
260    let compaction_receipt = CompactionReceiptV1 {
261        schema_version: COMPACTION_RECEIPT_V1_SCHEMA.into(),
262        compaction_receipt_id: CompactionReceiptId::generate(),
263        archive_manifest_id: archive_manifest.archive_manifest_id.clone(),
264        dropped_detail_refs,
265        queryable_degradation_note:
266            "detail refs were compacted, but the archive manifest and historical query guarantee remain authoritative for replay".into(),
267    };
268
269    (
270        archive_manifest,
271        historical_query_guarantee,
272        compaction_receipt,
273    )
274}