1use schemars::JsonSchema;
8use serde::{Deserialize, Serialize};
9use stack_ids::{
10 ContentDigest, GeneratedConformanceCorpusId, GeneratedInterpreterBundleId,
11 GeneratedMigrationPlanId, GeneratedSchemaBundleId, HumanVetoBundleId, MetaChallengeBundleId,
12 NormativeAstId, ProofEvaluationReceiptId, ProofObligationSetId, SelfHostingBuildReceiptId,
13 SpecBundleId,
14};
15
16pub use stack_ids::SurfaceStatus;
17
18pub const SPEC_BUNDLE_V1_SCHEMA: &str = "spec_bundle_v1";
19pub const NORMATIVE_AST_V1_SCHEMA: &str = "normative_ast_v1";
20pub const GENERATED_SCHEMA_BUNDLE_V1_SCHEMA: &str = "generated_schema_bundle_v1";
21pub const GENERATED_INTERPRETER_BUNDLE_V1_SCHEMA: &str = "generated_interpreter_bundle_v1";
22pub const GENERATED_CONFORMANCE_CORPUS_V1_SCHEMA: &str = "generated_conformance_corpus_v1";
23pub const GENERATED_MIGRATION_PLAN_V1_SCHEMA: &str = "generated_migration_plan_v1";
24pub const PROOF_OBLIGATION_SET_V1_SCHEMA: &str = "proof_obligation_set_v1";
25pub const PROOF_EVALUATION_RECEIPT_V1_SCHEMA: &str = "proof_evaluation_receipt_v1";
26pub const HUMAN_VETO_BUNDLE_V1_SCHEMA: &str = "human_veto_bundle_v1";
27pub const META_CHALLENGE_BUNDLE_V1_SCHEMA: &str = "meta_challenge_bundle_v1";
28pub const SELF_HOSTING_BUILD_RECEIPT_V1_SCHEMA: &str = "self_hosting_build_receipt_v1";
29
30#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
31pub struct SpecBundleV1 {
32 pub schema_version: String,
33 pub spec_bundle_id: SpecBundleId,
34 pub spec_name: String,
35 pub canonical_owner: String,
36 pub publication_status: SurfaceStatus,
37}
38
39#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
40pub struct NormativeAstNodeV1 {
41 pub node_id: String,
42 pub kind: String,
43 pub normative_text: String,
44}
45
46#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
47pub struct NormativeASTV1 {
48 pub schema_version: String,
49 pub normative_ast_id: NormativeAstId,
50 pub spec_bundle_id: SpecBundleId,
51 #[serde(default)]
52 pub nodes: Vec<NormativeAstNodeV1>,
53 pub publication_status: SurfaceStatus,
54}
55
56#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
57pub struct GeneratedSchemaFileV1 {
58 pub path: String,
59 pub digest: ContentDigest,
60 #[serde(default)]
61 pub source_node_ids: Vec<String>,
62}
63
64#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
65#[serde(rename_all = "snake_case")]
66pub enum GeneratedAdmissionState {
67 AdvisoryOnly,
68 NonAdmitted,
69 HumanVetoed,
70}
71
72#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
73pub struct GeneratedSchemaBundleV1 {
74 pub schema_version: String,
75 pub generated_schema_bundle_id: GeneratedSchemaBundleId,
76 pub spec_bundle_id: SpecBundleId,
77 pub normative_ast_id: NormativeAstId,
78 pub proof_obligation_set_id: ProofObligationSetId,
79 pub schema_family: String,
80 #[serde(default)]
81 pub generated_files: Vec<GeneratedSchemaFileV1>,
82 pub admission_state: GeneratedAdmissionState,
83 pub advisory_only: bool,
84 pub generated_at: String,
85}
86
87#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
88pub struct GeneratedInterpreterBundleV1 {
89 pub schema_version: String,
90 pub generated_interpreter_bundle_id: GeneratedInterpreterBundleId,
91 pub spec_bundle_id: SpecBundleId,
92 pub normative_ast_id: NormativeAstId,
93 pub proof_obligation_set_id: ProofObligationSetId,
94 pub generated_schema_bundle_id: GeneratedSchemaBundleId,
95 pub horizon_only: bool,
96 pub generated_at: String,
97}
98
99#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
100pub struct GeneratedConformanceCorpusV1 {
101 pub schema_version: String,
102 pub generated_conformance_corpus_id: GeneratedConformanceCorpusId,
103 pub spec_bundle_id: SpecBundleId,
104 pub normative_ast_id: NormativeAstId,
105 pub proof_obligation_set_id: ProofObligationSetId,
106 pub generated_schema_bundle_id: GeneratedSchemaBundleId,
107 pub horizon_only: bool,
108 pub generated_at: String,
109}
110
111#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
112pub struct GeneratedMigrationPlanV1 {
113 pub schema_version: String,
114 pub generated_migration_plan_id: GeneratedMigrationPlanId,
115 pub spec_bundle_id: SpecBundleId,
116 pub normative_ast_id: NormativeAstId,
117 pub proof_obligation_set_id: ProofObligationSetId,
118 pub generated_schema_bundle_id: GeneratedSchemaBundleId,
119 pub horizon_only: bool,
120 pub generated_at: String,
121}
122
123#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
124pub struct ProofObligationV1 {
125 pub obligation_id: String,
126 pub description: String,
127 pub satisfied: bool,
128 pub blocking: bool,
129}
130
131#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
132pub struct ProofObligationSetV1 {
133 pub schema_version: String,
134 pub proof_obligation_set_id: ProofObligationSetId,
135 pub spec_bundle_id: SpecBundleId,
136 pub normative_ast_id: NormativeAstId,
137 #[serde(default)]
138 pub obligations: Vec<ProofObligationV1>,
139}
140
141#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
142pub struct ProofEvaluationReceiptV1 {
143 pub schema_version: String,
144 pub proof_evaluation_receipt_id: ProofEvaluationReceiptId,
145 pub proof_obligation_set_id: ProofObligationSetId,
146 #[serde(default, skip_serializing_if = "Option::is_none")]
147 pub generated_schema_bundle_id: Option<GeneratedSchemaBundleId>,
148 pub satisfied_count: usize,
149 #[serde(default)]
150 pub unsatisfied_blocking_obligations: Vec<String>,
151 pub admission_allowed: bool,
152 pub advisory_only: bool,
153 pub evaluated_at: String,
154}
155
156#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
157pub struct HumanVetoBundleV1 {
158 pub schema_version: String,
159 pub human_veto_bundle_id: HumanVetoBundleId,
160 pub generated_schema_bundle_id: GeneratedSchemaBundleId,
161 pub reason: String,
162}
163
164#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
165pub struct MetaChallengeBundleV1 {
166 pub schema_version: String,
167 pub meta_challenge_bundle_id: MetaChallengeBundleId,
168 pub generated_schema_bundle_id: GeneratedSchemaBundleId,
169 pub challenge_summary: String,
170 pub horizon_only: bool,
171}
172
173#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
174#[serde(rename_all = "snake_case")]
175pub enum GeneratedSurfaceGovernanceState {
176 AdvisoryBaseline,
177 ChallengePending,
178 HumanVetoed,
179}
180
181#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
182pub struct SelfHostingBuildReceiptV1 {
183 pub schema_version: String,
184 pub self_hosting_build_receipt_id: SelfHostingBuildReceiptId,
185 pub spec_bundle_id: SpecBundleId,
186 pub generated_schema_bundle_id: GeneratedSchemaBundleId,
187 pub generated_interpreter_bundle_id: GeneratedInterpreterBundleId,
188 pub generated_conformance_corpus_id: GeneratedConformanceCorpusId,
189 pub generated_migration_plan_id: GeneratedMigrationPlanId,
190 pub proof_evaluation_receipt_id: ProofEvaluationReceiptId,
191 #[serde(default, skip_serializing_if = "Option::is_none")]
192 pub human_veto_bundle_id: Option<HumanVetoBundleId>,
193 #[serde(default, skip_serializing_if = "Option::is_none")]
194 pub meta_challenge_bundle_id: Option<MetaChallengeBundleId>,
195 pub governance_state: GeneratedSurfaceGovernanceState,
196 pub rollback_required: bool,
197 pub admission_allowed: bool,
198 pub advisory_only: bool,
199 #[serde(default)]
200 pub notes: Vec<String>,
201 pub generated_at: String,
202}
203
204#[derive(Debug, Clone, PartialEq, Eq)]
205pub struct GeneratedCompanionBundles {
206 pub schema_bundle: GeneratedSchemaBundleV1,
207 pub interpreter_bundle: GeneratedInterpreterBundleV1,
208 pub conformance_corpus: GeneratedConformanceCorpusV1,
209 pub migration_plan: GeneratedMigrationPlanV1,
210 pub proof_evaluation_receipt: ProofEvaluationReceiptV1,
211 pub self_hosting_build_receipt: SelfHostingBuildReceiptV1,
212}
213
214pub fn generate_schema_bundle(
215 spec: &SpecBundleV1,
216 ast: &NormativeASTV1,
217 obligations: &ProofObligationSetV1,
218 schema_family: impl Into<String>,
219 generated_at: impl Into<String>,
220) -> (GeneratedSchemaBundleV1, ProofEvaluationReceiptV1) {
221 let generated_at = generated_at.into();
222 let evaluated_at = generated_at.clone();
223 let schema_family = schema_family.into();
224 let blocking = obligations
225 .obligations
226 .iter()
227 .filter(|obligation| obligation.blocking && !obligation.satisfied)
228 .map(|obligation| obligation.obligation_id.clone())
229 .collect::<Vec<_>>();
230 let file_payload = serde_json::json!({
231 "spec_bundle_id": spec.spec_bundle_id,
232 "normative_ast_id": ast.normative_ast_id,
233 "source_node_ids": ast.nodes.iter().map(|node| node.node_id.clone()).collect::<Vec<_>>(),
234 });
235 let file = GeneratedSchemaFileV1 {
236 path: format!("generated/{schema_family}/schema.json"),
237 digest: ContentDigest::compute_json(&file_payload)
238 .unwrap_or_else(|e| {
239 tracing::warn!(error = %e, "schema file payload digest failed; using empty-digest fallback");
240 ContentDigest::compute_str("")
241 }),
242 source_node_ids: ast.nodes.iter().map(|node| node.node_id.clone()).collect(),
243 };
244
245 let bundle = GeneratedSchemaBundleV1 {
246 schema_version: GENERATED_SCHEMA_BUNDLE_V1_SCHEMA.into(),
247 generated_schema_bundle_id: GeneratedSchemaBundleId::generate(),
248 spec_bundle_id: spec.spec_bundle_id.clone(),
249 normative_ast_id: ast.normative_ast_id.clone(),
250 proof_obligation_set_id: obligations.proof_obligation_set_id.clone(),
251 schema_family,
252 generated_files: vec![file],
253 admission_state: GeneratedAdmissionState::AdvisoryOnly,
254 advisory_only: true,
255 generated_at,
256 };
257
258 let receipt = ProofEvaluationReceiptV1 {
259 schema_version: PROOF_EVALUATION_RECEIPT_V1_SCHEMA.into(),
260 proof_evaluation_receipt_id: ProofEvaluationReceiptId::generate(),
261 proof_obligation_set_id: obligations.proof_obligation_set_id.clone(),
262 generated_schema_bundle_id: Some(bundle.generated_schema_bundle_id.clone()),
263 satisfied_count: obligations
264 .obligations
265 .iter()
266 .filter(|obligation| obligation.satisfied)
267 .count(),
268 unsatisfied_blocking_obligations: blocking.clone(),
269 admission_allowed: blocking.is_empty(),
270 advisory_only: true,
271 evaluated_at,
272 };
273
274 (bundle, receipt)
275}
276
277pub fn generate_companion_bundles(
278 spec: &SpecBundleV1,
279 ast: &NormativeASTV1,
280 obligations: &ProofObligationSetV1,
281 schema_family: impl Into<String>,
282 generated_at: impl Into<String>,
283) -> GeneratedCompanionBundles {
284 let generated_at = generated_at.into();
285 let (schema_bundle, proof_evaluation_receipt) =
286 generate_schema_bundle(spec, ast, obligations, schema_family, generated_at.clone());
287 let interpreter_bundle = GeneratedInterpreterBundleV1 {
288 schema_version: GENERATED_INTERPRETER_BUNDLE_V1_SCHEMA.into(),
289 generated_interpreter_bundle_id: GeneratedInterpreterBundleId::generate(),
290 spec_bundle_id: spec.spec_bundle_id.clone(),
291 normative_ast_id: ast.normative_ast_id.clone(),
292 proof_obligation_set_id: obligations.proof_obligation_set_id.clone(),
293 generated_schema_bundle_id: schema_bundle.generated_schema_bundle_id.clone(),
294 horizon_only: true,
295 generated_at: generated_at.clone(),
296 };
297 let conformance_corpus = GeneratedConformanceCorpusV1 {
298 schema_version: GENERATED_CONFORMANCE_CORPUS_V1_SCHEMA.into(),
299 generated_conformance_corpus_id: GeneratedConformanceCorpusId::generate(),
300 spec_bundle_id: spec.spec_bundle_id.clone(),
301 normative_ast_id: ast.normative_ast_id.clone(),
302 proof_obligation_set_id: obligations.proof_obligation_set_id.clone(),
303 generated_schema_bundle_id: schema_bundle.generated_schema_bundle_id.clone(),
304 horizon_only: true,
305 generated_at: generated_at.clone(),
306 };
307 let migration_plan = GeneratedMigrationPlanV1 {
308 schema_version: GENERATED_MIGRATION_PLAN_V1_SCHEMA.into(),
309 generated_migration_plan_id: GeneratedMigrationPlanId::generate(),
310 spec_bundle_id: spec.spec_bundle_id.clone(),
311 normative_ast_id: ast.normative_ast_id.clone(),
312 proof_obligation_set_id: obligations.proof_obligation_set_id.clone(),
313 generated_schema_bundle_id: schema_bundle.generated_schema_bundle_id.clone(),
314 horizon_only: true,
315 generated_at: generated_at.clone(),
316 };
317 let self_hosting_build_receipt = establish_veto_challenge_baseline(
318 spec,
319 &schema_bundle,
320 &interpreter_bundle,
321 &conformance_corpus,
322 &migration_plan,
323 &proof_evaluation_receipt,
324 None,
325 None,
326 generated_at.clone(),
327 );
328
329 GeneratedCompanionBundles {
330 schema_bundle,
331 interpreter_bundle,
332 conformance_corpus,
333 migration_plan,
334 proof_evaluation_receipt,
335 self_hosting_build_receipt,
336 }
337}
338
339#[allow(clippy::too_many_arguments)]
340pub fn establish_veto_challenge_baseline(
341 spec: &SpecBundleV1,
342 schema_bundle: &GeneratedSchemaBundleV1,
343 interpreter_bundle: &GeneratedInterpreterBundleV1,
344 conformance_corpus: &GeneratedConformanceCorpusV1,
345 migration_plan: &GeneratedMigrationPlanV1,
346 proof_evaluation_receipt: &ProofEvaluationReceiptV1,
347 human_veto: Option<&HumanVetoBundleV1>,
348 meta_challenge: Option<&MetaChallengeBundleV1>,
349 generated_at: impl Into<String>,
350) -> SelfHostingBuildReceiptV1 {
351 let governance_state = if human_veto.is_some() {
352 GeneratedSurfaceGovernanceState::HumanVetoed
353 } else if meta_challenge.is_some() {
354 GeneratedSurfaceGovernanceState::ChallengePending
355 } else {
356 GeneratedSurfaceGovernanceState::AdvisoryBaseline
357 };
358 let admission_allowed = proof_evaluation_receipt.admission_allowed && human_veto.is_none();
359
360 SelfHostingBuildReceiptV1 {
361 schema_version: SELF_HOSTING_BUILD_RECEIPT_V1_SCHEMA.into(),
362 self_hosting_build_receipt_id: SelfHostingBuildReceiptId::generate(),
363 spec_bundle_id: spec.spec_bundle_id.clone(),
364 generated_schema_bundle_id: schema_bundle.generated_schema_bundle_id.clone(),
365 generated_interpreter_bundle_id: interpreter_bundle.generated_interpreter_bundle_id.clone(),
366 generated_conformance_corpus_id: conformance_corpus.generated_conformance_corpus_id.clone(),
367 generated_migration_plan_id: migration_plan.generated_migration_plan_id.clone(),
368 proof_evaluation_receipt_id: proof_evaluation_receipt.proof_evaluation_receipt_id.clone(),
369 human_veto_bundle_id: human_veto.map(|bundle| bundle.human_veto_bundle_id.clone()),
370 meta_challenge_bundle_id: meta_challenge
371 .map(|bundle| bundle.meta_challenge_bundle_id.clone()),
372 governance_state,
373 rollback_required: human_veto.is_some() || meta_challenge.is_some(),
374 admission_allowed,
375 advisory_only: true,
376 notes: vec![
377 "generated surfaces remain non-admitted until proof obligations pass and a human lane accepts them".into(),
378 "self-hosting status does not override veto, challenge, or rollback obligations".into(),
379 ],
380 generated_at: generated_at.into(),
381 }
382}