1use super::*;
2
3#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
4pub struct AiDENsAppPlanV1 {
5 pub app_id: String,
6 pub profile_id: String,
7 pub provider_required: bool,
8 pub memory_mode: MemoryModeV1,
9 pub receipt_level: ReportLevelV1,
10 pub dangerous_auto_approval: bool,
11 #[serde(default, skip_serializing_if = "Vec::is_empty")]
12 pub risk_disclosures: Vec<RiskDisclosureV1>,
13 pub enabled_tool_bundles: Vec<String>,
14 pub disabled_tool_bundles: Vec<String>,
15}
16
17#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
18pub struct AiDENsCompiledPlanV1 {
19 pub plan_id: ArtifactId,
20 pub plan: AiDENsAppPlanV1,
21 pub provider_route: ProviderRouteReportV1,
22 pub tool_exposure: ToolExposureSetV1,
23 pub doctor: AiDENsDoctorReportV1,
24 pub config_apply_receipt: ConfigApplyReportV1,
25 pub parity_report: PlanRuntimeParityReportV1,
26}
27
28#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
29pub struct AiDENsDoctorReportV1 {
30 pub report_id: ArtifactId,
31 pub app_id: String,
32 pub sections: BTreeMap<String, Vec<RuntimeCapabilityTruthV1>>,
33}
34
35impl AiDENsDoctorReportV1 {
36 pub fn new(
37 app_id: impl Into<String>,
38 sections: BTreeMap<String, Vec<RuntimeCapabilityTruthV1>>,
39 ) -> Self {
40 Self {
41 report_id: display_only_unstable_id("doctor-report"),
42 app_id: app_id.into(),
43 sections,
44 }
45 }
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
49#[serde(rename_all = "kebab-case")]
50pub enum ApiHonestyOutcomeV1 {
51 Honored,
52 Rejected,
53 Blocked,
54}
55
56#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
57pub struct ApiHonestyReportV1 {
58 pub receipt_id: ArtifactId,
59 pub kind: ArtifactKindV1,
60 pub surface: String,
61 pub accepted_inputs: Vec<String>,
62 pub honored_inputs: Vec<String>,
63 pub rejected_inputs: Vec<String>,
64 pub outcome: ApiHonestyOutcomeV1,
65 #[serde(default, skip_serializing_if = "Vec::is_empty")]
66 pub reason_codes: Vec<String>,
67 pub checked_at: DateTime<Utc>,
68}
69
70impl ApiHonestyReportV1 {
71 pub fn honored(
72 surface: impl Into<String>,
73 accepted_inputs: Vec<String>,
74 honored_inputs: Vec<String>,
75 ) -> Self {
76 Self {
77 receipt_id: display_only_unstable_id("api-honesty"),
78 kind: ArtifactKindV1::ApiHonesty,
79 surface: surface.into(),
80 accepted_inputs,
81 honored_inputs,
82 rejected_inputs: Vec::new(),
83 outcome: ApiHonestyOutcomeV1::Honored,
84 reason_codes: Vec::new(),
85 checked_at: Utc::now(),
86 }
87 }
88
89 pub fn rejected(
90 surface: impl Into<String>,
91 accepted_inputs: Vec<String>,
92 rejected_inputs: Vec<String>,
93 reason: impl Into<String>,
94 ) -> Self {
95 Self {
96 receipt_id: display_only_unstable_id("api-honesty"),
97 kind: ArtifactKindV1::ApiHonesty,
98 surface: surface.into(),
99 accepted_inputs,
100 honored_inputs: Vec::new(),
101 rejected_inputs,
102 outcome: ApiHonestyOutcomeV1::Rejected,
103 reason_codes: vec![reason.into()],
104 checked_at: Utc::now(),
105 }
106 }
107
108 pub fn blocked(
109 surface: impl Into<String>,
110 accepted_inputs: Vec<String>,
111 rejected_inputs: Vec<String>,
112 reason: impl Into<String>,
113 ) -> Self {
114 let mut receipt = Self::rejected(surface, accepted_inputs, rejected_inputs, reason);
115 receipt.outcome = ApiHonestyOutcomeV1::Blocked;
116 receipt
117 }
118
119 pub fn all_inputs_honored(&self) -> bool {
120 self.outcome == ApiHonestyOutcomeV1::Honored
121 && self
122 .accepted_inputs
123 .iter()
124 .all(|input| self.honored_inputs.contains(input))
125 && self.rejected_inputs.is_empty()
126 }
127}
128
129#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
130pub struct ConfigApplyReportV1 {
131 pub receipt_id: ArtifactId,
132 pub kind: ArtifactKindV1,
133 pub app_id: String,
134 pub config_source: String,
135 pub provider_route: ProviderRouteReportV1,
136 pub tool_exposure: ToolExposureSetV1,
137 pub memory_mode: MemoryModeV1,
138 pub receipt_level: ReportLevelV1,
139 pub enabled_tool_bundles: Vec<String>,
140 pub sandbox_root: Option<String>,
141 pub applied: bool,
142 #[serde(default, skip_serializing_if = "Vec::is_empty")]
143 pub reason_codes: Vec<String>,
144 pub applied_at: DateTime<Utc>,
145}
146
147#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
148pub struct ConfigApplyReportDraftV1 {
149 pub app_id: String,
150 pub config_source: String,
151 pub provider_route: ProviderRouteReportV1,
152 pub tool_exposure: ToolExposureSetV1,
153 pub memory_mode: MemoryModeV1,
154 pub receipt_level: ReportLevelV1,
155 pub enabled_tool_bundles: Vec<String>,
156 pub sandbox_root: Option<String>,
157 pub applied: bool,
158 #[serde(default, skip_serializing_if = "Vec::is_empty")]
159 pub reason_codes: Vec<String>,
160}
161
162impl ConfigApplyReportV1 {
163 pub fn new(draft: ConfigApplyReportDraftV1) -> Self {
164 Self {
165 receipt_id: display_only_unstable_id("config-apply"),
166 kind: ArtifactKindV1::ConfigApply,
167 app_id: draft.app_id,
168 config_source: draft.config_source,
169 provider_route: draft.provider_route,
170 tool_exposure: draft.tool_exposure,
171 memory_mode: draft.memory_mode,
172 receipt_level: draft.receipt_level,
173 enabled_tool_bundles: draft.enabled_tool_bundles,
174 sandbox_root: draft.sandbox_root,
175 applied: draft.applied,
176 reason_codes: draft.reason_codes,
177 applied_at: Utc::now(),
178 }
179 }
180}
181
182#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
183#[serde(rename_all = "kebab-case")]
184pub enum PlanRuntimeParityCheckKindV1 {
185 ProviderRoute,
186 ToolExposure,
187 MemoryMode,
188 ScaffoldState,
189}
190
191impl fmt::Display for PlanRuntimeParityCheckKindV1 {
192 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
193 f.write_str(match self {
194 Self::ProviderRoute => "provider-route",
195 Self::ToolExposure => "tool-exposure",
196 Self::MemoryMode => "memory-mode",
197 Self::ScaffoldState => "scaffold-state",
198 })
199 }
200}
201
202#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
203pub struct PlanRuntimeParityCheckV1 {
204 pub check: PlanRuntimeParityCheckKindV1,
205 pub expected: String,
206 pub observed: String,
207 pub passed: bool,
208}
209
210impl PlanRuntimeParityCheckV1 {
211 pub fn new(
212 check: PlanRuntimeParityCheckKindV1,
213 expected: impl Into<String>,
214 observed: impl Into<String>,
215 ) -> Self {
216 let expected = expected.into();
217 let observed = observed.into();
218 Self {
219 check,
220 passed: expected == observed,
221 expected,
222 observed,
223 }
224 }
225}
226
227#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
228pub struct PlanRuntimeParityReportV1 {
229 pub report_id: ArtifactId,
230 pub kind: ArtifactKindV1,
231 pub app_id: String,
232 pub checks: Vec<PlanRuntimeParityCheckV1>,
233 #[serde(default, skip_serializing_if = "Vec::is_empty")]
234 pub mismatches: Vec<String>,
235 pub generated_at: DateTime<Utc>,
236}
237
238impl PlanRuntimeParityReportV1 {
239 pub fn new(app_id: impl Into<String>, checks: Vec<PlanRuntimeParityCheckV1>) -> Self {
240 let mismatches = checks
241 .iter()
242 .filter(|check| !check.passed)
243 .map(|check| {
244 format!(
245 "{} expected '{}' but observed '{}'",
246 check.check, check.expected, check.observed
247 )
248 })
249 .collect();
250 Self {
251 report_id: display_only_unstable_id("plan-runtime-parity"),
252 kind: ArtifactKindV1::PlanRuntimeParity,
253 app_id: app_id.into(),
254 checks,
255 mismatches,
256 generated_at: Utc::now(),
257 }
258 }
259
260 pub fn is_passing(&self) -> bool {
261 self.mismatches.is_empty() && self.checks.iter().all(|check| check.passed)
262 }
263}
264
265#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
266#[serde(rename_all = "kebab-case")]
267pub enum CrateImplementationStatusV1 {
268 Implemented,
269 Partial,
270 ScaffoldOnly,
271}
272
273impl fmt::Display for CrateImplementationStatusV1 {
274 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
275 f.write_str(match self {
276 Self::Implemented => "implemented",
277 Self::Partial => "partial",
278 Self::ScaffoldOnly => "scaffold-only",
279 })
280 }
281}
282
283#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
284pub struct SourceBasisLockV1 {
285 pub lock_id: ArtifactId,
286 pub snapshot_date: String,
287 pub source_archive: String,
288 pub research_archive: String,
289 pub extraction_root: String,
290 pub workspace_crates: u32,
291 pub rust_files: u32,
292 pub approximate_rust_loc: u32,
293 pub scaffold_only_files: u32,
294 pub research_files: u32,
295 pub locked_at: DateTime<Utc>,
296}
297
298impl SourceBasisLockV1 {
299 pub fn current_20260426() -> Self {
300 Self {
301 lock_id: display_only_unstable_id("source-basis-lock"),
302 snapshot_date: "2026-04-26".into(),
303 source_archive: "libraries-source-clean-20260426.zip".into(),
304 research_archive: "Full Provenance+ Research 4/24/26.zip".into(),
305 extraction_root: "/mnt/data/aidens_20260426".into(),
306 workspace_crates: 31,
307 rust_files: 37,
308 approximate_rust_loc: 5126,
309 scaffold_only_files: 15,
310 research_files: 52,
311 locked_at: Utc::now(),
312 }
313 }
314}
315
316#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
317pub struct CrateSurfaceStatusV1 {
318 pub crate_name: String,
319 pub status: CrateImplementationStatusV1,
320 pub rust_files: u32,
321 pub approximate_rust_loc: u32,
322 pub note: String,
323}
324
325impl CrateSurfaceStatusV1 {
326 pub fn new(
327 crate_name: impl Into<String>,
328 status: CrateImplementationStatusV1,
329 rust_files: u32,
330 approximate_rust_loc: u32,
331 note: impl Into<String>,
332 ) -> Self {
333 Self {
334 crate_name: crate_name.into(),
335 status,
336 rust_files,
337 approximate_rust_loc,
338 note: note.into(),
339 }
340 }
341
342 pub fn allows_scaffold_marker(&self) -> bool {
343 self.status == CrateImplementationStatusV1::ScaffoldOnly
344 }
345}
346
347#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
348pub struct ScaffoldSurfaceReportV1 {
349 pub report_id: ArtifactId,
350 pub generated_at: DateTime<Utc>,
351 pub crates: Vec<CrateSurfaceStatusV1>,
352}
353
354impl ScaffoldSurfaceReportV1 {
355 pub fn new(crates: Vec<CrateSurfaceStatusV1>) -> Self {
356 Self {
357 report_id: display_only_unstable_id("scaffold-surface-report"),
358 generated_at: Utc::now(),
359 crates,
360 }
361 }
362
363 pub fn scaffold_only_crates(&self) -> Vec<&str> {
364 self.crates
365 .iter()
366 .filter(|surface| surface.status == CrateImplementationStatusV1::ScaffoldOnly)
367 .map(|surface| surface.crate_name.as_str())
368 .collect()
369 }
370
371 pub fn allows_scaffold_marker_for(&self, crate_name: &str) -> bool {
372 self.crates
373 .iter()
374 .find(|surface| surface.crate_name == crate_name)
375 .is_some_and(CrateSurfaceStatusV1::allows_scaffold_marker)
376 }
377}
378
379#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
380pub struct FakeReadyFindingV1 {
381 pub finding_id: ArtifactId,
382 pub surface: String,
383 pub pattern: String,
384 pub reason: String,
385 pub blocked: bool,
386 pub found_at: DateTime<Utc>,
387}
388
389impl FakeReadyFindingV1 {
390 pub fn blocking(
391 surface: impl Into<String>,
392 pattern: impl Into<String>,
393 reason: impl Into<String>,
394 ) -> Self {
395 Self {
396 finding_id: display_only_unstable_id("fake-ready-finding"),
397 surface: surface.into(),
398 pattern: pattern.into(),
399 reason: reason.into(),
400 blocked: true,
401 found_at: Utc::now(),
402 }
403 }
404}
405
406#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
407#[serde(rename_all = "kebab-case")]
408pub enum SuperPassDispositionV1 {
409 Pending,
410 InProgress,
411 Done,
412 Blocked,
413 Deferred,
414}
415
416#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
417pub struct SuperPassStatusV1 {
418 pub status_id: ArtifactId,
419 pub current_pass: String,
420 pub disposition: SuperPassDispositionV1,
421 pub source_basis: SourceBasisLockV1,
422 pub scaffold_report: ScaffoldSurfaceReportV1,
423 #[serde(default, skip_serializing_if = "Vec::is_empty")]
424 pub fake_ready_findings: Vec<FakeReadyFindingV1>,
425 #[serde(default, skip_serializing_if = "Vec::is_empty")]
426 pub blockers: Vec<String>,
427 pub updated_at: DateTime<Utc>,
428}
429
430impl SuperPassStatusV1 {
431 pub fn new(
432 current_pass: impl Into<String>,
433 disposition: SuperPassDispositionV1,
434 source_basis: SourceBasisLockV1,
435 scaffold_report: ScaffoldSurfaceReportV1,
436 fake_ready_findings: Vec<FakeReadyFindingV1>,
437 blockers: Vec<String>,
438 ) -> Self {
439 Self {
440 status_id: display_only_unstable_id("super-pass-status"),
441 current_pass: current_pass.into(),
442 disposition,
443 source_basis,
444 scaffold_report,
445 fake_ready_findings,
446 blockers,
447 updated_at: Utc::now(),
448 }
449 }
450
451 pub fn is_blocked(&self) -> bool {
452 self.disposition == SuperPassDispositionV1::Blocked
453 || !self.blockers.is_empty()
454 || self
455 .fake_ready_findings
456 .iter()
457 .any(|finding| finding.blocked)
458 }
459}
460
461impl AiDENsAppPlanV1 {
462 pub fn validate(&self) -> Result<(), String> {
463 if self.app_id.trim().is_empty() {
464 return Err("app_id must not be empty".into());
465 }
466 if self.dangerous_auto_approval {
467 return Err("dangerous_auto_approval requires explicit advanced override".into());
468 }
469 Ok(())
470 }
471
472 pub fn human_summary(&self) -> String {
473 format!(
474 "{} [{}]: provider_required={}, memory_mode={}, receipt_level={}",
475 self.app_id,
476 self.profile_id,
477 self.provider_required,
478 self.memory_mode,
479 self.receipt_level
480 )
481 }
482
483 pub fn risk_summary(&self) -> String {
484 if self.risk_disclosures.is_empty() {
485 return "No risky capabilities are granted by default.".into();
486 }
487 self.risk_disclosures
488 .iter()
489 .map(|risk| {
490 format!(
491 "{}: granted_by_default={}, permit_required={} ({})",
492 risk.risk_class, risk.granted_by_default, risk.permit_required, risk.reason
493 )
494 })
495 .collect::<Vec<_>>()
496 .join("; ")
497 }
498}
499
500#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
501pub struct RiskDisclosureV1 {
502 pub risk_class: CanonicalToolSideEffectClass,
503 pub granted_by_default: bool,
504 pub permit_required: bool,
505 pub reason: String,
506}
507
508#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
509#[serde(rename_all = "kebab-case")]
510pub enum MemoryModeV1 {
511 Disabled,
512 Optional,
513 Required,
514}
515
516impl fmt::Display for MemoryModeV1 {
517 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
518 f.write_str(match self {
519 Self::Disabled => "disabled",
520 Self::Optional => "optional",
521 Self::Required => "required",
522 })
523 }
524}
525
526#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
527#[serde(rename_all = "kebab-case")]
528pub enum ReportLevelV1 {
529 Minimal,
530 Standard,
531 Full,
532}
533
534impl fmt::Display for ReportLevelV1 {
535 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
536 f.write_str(match self {
537 Self::Minimal => "minimal",
538 Self::Standard => "standard",
539 Self::Full => "full",
540 })
541 }
542}