Skip to main content

organism_pack/
lib.rs

1//! # Organism Pack
2//!
3//! The public contract for Organism's planning loop.
4//! One import — the full pipeline from intent to learning.
5//!
6//! ```text
7//! IntentPacket → Admission → Plan → Challenge → Simulate → Learn → Commit
8//! ```
9//!
10//! # Quick start
11//!
12//! ```rust,ignore
13//! use organism_pack::*;
14//!
15//! // 1. Create an intent
16//! let intent = IntentPacket::new("Approve $2,500 expense", expires);
17//!
18//! // 2. Check admission (4 dimensions)
19//! let admission = my_controller.evaluate(&intent);
20//!
21//! // 3. Plan (multi-model huddle)
22//! let plan = Plan::new(&intent, "route to 3 approvers");
23//!
24//! // 4. Challenge (5 skepticism kinds)
25//! let challenge = Challenge::new(
26//!     SkepticismKind::EconomicSkepticism,
27//!     plan.id,
28//!     "entertainment spend is high",
29//!     Severity::Warning,
30//! );
31//!
32//! // 5. Simulate (5 dimensions)
33//! let result = DimensionResult {
34//!     dimension: SimulationDimension::Cost,
35//!     passed: true,
36//!     confidence: 0.95,
37//!     findings: vec!["within budget".into()],
38//!     samples: vec![],
39//! };
40//!
41//! // 6. Learn from outcomes
42//! let lesson = Lesson {
43//!     insight: "score 0.88 → approved".into(),
44//!     context: "expense approval".into(),
45//!     confidence: 0.9,
46//!     planning_adjustment: "none".into(),
47//! };
48//! ```
49
50// ── Converge confidence vocabulary ────────────────────────────────
51// Named steps for adjust_confidence() — same values as converge_pack once
52// those constants land on main. Switch to re-exports when converge is updated.
53pub const CONFIDENCE_STEP_TINY: f64 = 0.05;
54pub const CONFIDENCE_STEP_MINOR: f64 = 0.1;
55pub const CONFIDENCE_STEP_MEDIUM: f64 = 0.15;
56pub const CONFIDENCE_STEP_MAJOR: f64 = 0.2;
57pub const CONFIDENCE_STEP_PRIMARY: f64 = 0.25;
58
59// ── Intent ─────────────────────────────────────────────────────────
60// The input: what the organization wants to achieve.
61
62pub use organism_intent::{
63    // Admission
64    AdmissionController,
65    AdmissionResult,
66    ExpiryAction,
67    FeasibilityAssessment,
68    FeasibilityDimension,
69    FeasibilityKind,
70    ForbiddenAction,
71    IntentError,
72    IntentNode,
73    IntentPacket,
74    Reversibility,
75    admission::DefaultAdmissionController,
76    // Resolution
77    resolution::{
78        CapabilityRequirement, DeclarativeBinding, IntentBinding, IntentResolver, PackRequirement,
79        ResolutionLevel, ResolutionTrace,
80    },
81};
82
83// ── Planning ───────────────────────────────────────────────────────
84// How the system reasons about the intent.
85
86pub use organism_planning::{
87    CollaborationCharter, CollaborationDiscipline, CollaborationMember, CollaborationRole,
88    CollaborationTopology, CollaborationValidationError, ConsensusRule, CostEstimate, Impact,
89    Likelihood, Plan, PlanAnnotation, PlanBundle, PlanContribution, PlanStep, Reasoner,
90    ReasoningSystem, Risk, RiskImpact, TeamFormation, TeamFormationMode, TurnCadence,
91    charter_derivation::{
92        DerivationRationale, DerivedCharter, IntentComplexity, derive_charter,
93        derive_charter_with_priors,
94    },
95    dd::{
96        BreadthResearchSuggestor, ContradictionFinderSuggestor, DdError, DdFactSummary, DdHooks,
97        DdLlm, DdSearch, DepthResearchSuggestor, FactExtractorSuggestor, FailoverDdLlm,
98        FailoverDdSearch, GapDetectorSuggestor, HookPatterns, SearchHit, SynthesisSuggestor,
99        consolidate_dd_hypotheses, extract_hooks_from_facts,
100    },
101    kb::{
102        HubCategory, KbConfig, RootPageDef, sanitize_filename, slugify, update_root_pages,
103        write_dd_to_vault, write_or_append_hub,
104    },
105    shape_hypothesis::{
106        ShapeCalibration, ShapeCandidate, ShapeCompetition, ShapeMetric, ShapeObservation,
107        calibrate_shape, classify_problem, generate_candidates, score_observation, select_winner,
108    },
109    suggestor::{HuddleSeedSuggestor, NamedPlan, SharedBudget},
110    topology_transition::{
111        CharterAdjustments, ConvergenceSignals, TransitionDecision, TransitionRule,
112        TransitionTrigger, apply_adjustments, default_transition_rules, evaluate_transitions,
113    },
114};
115
116// ── Adversarial ────────────────────────────────────────────────────
117// Adversarial agents are Suggestors — they participate in the convergence loop.
118// These types are the vocabulary for what adversarial agents produce.
119
120pub use organism_adversarial::{
121    AdversarialSignal, AdversarialVerdict, AgentId, Challenge, Complexity, Finding, Severity,
122    SkepticismKind,
123};
124
125// ── Simulation ─────────────────────────────────────────────────────
126// Simulation agents are Suggestors — they stress-test plans inside the loop.
127// These types describe simulation results and configuration.
128
129pub use organism_simulation::{
130    DimensionResult, OutcomeSimulationAgent, OutcomeSimulator, OutcomeSimulatorConfig,
131    RiskLikelihood, Sample, SimulationDimension, SimulationRecommendation, SimulationReport,
132    SimulationResult, SimulationVerdict,
133};
134
135// ── Learning ───────────────────────────────────────────────────────
136// Calibrate priors from execution outcomes.
137
138pub use organism_learning::{
139    AdversarialContext, ErrorDimension, LearningEpisode, LearningSignal, Lesson, PredictionError,
140    PriorCalibration, SignalKind,
141    adapter::{
142        build_episode, build_episode_from_run, calibrate_priors, extract_signals,
143        extract_signals_from_run, has_infra_failure,
144    },
145};
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150    use chrono::{Duration, Utc};
151
152    #[test]
153    fn intent_packet_roundtrip() {
154        let expires = Utc::now() + Duration::hours(1);
155        let intent = IntentPacket::new("ship feature X", expires);
156        let json = serde_json::to_string(&intent).unwrap();
157        let back: IntentPacket = serde_json::from_str(&json).unwrap();
158        assert_eq!(back.outcome, "ship feature X");
159        assert_eq!(back.reversibility, Reversibility::Reversible);
160        assert_eq!(back.expiry_action, ExpiryAction::Halt);
161    }
162
163    #[test]
164    fn intent_packet_builder_chain() {
165        let expires = Utc::now() + Duration::hours(2);
166        let intent = IntentPacket::new("approve expense", expires)
167            .with_context(serde_json::json!({"amount": 2500}))
168            .with_authority(vec!["finance".into()])
169            .with_reversibility(Reversibility::Irreversible)
170            .with_expiry_action(ExpiryAction::Escalate);
171
172        assert_eq!(intent.outcome, "approve expense");
173        assert_eq!(intent.context["amount"], 2500);
174        assert_eq!(intent.authority, vec!["finance"]);
175        assert_eq!(intent.reversibility, Reversibility::Irreversible);
176        assert_eq!(intent.expiry_action, ExpiryAction::Escalate);
177    }
178
179    #[test]
180    fn intent_packet_expired_detection() {
181        let past = Utc::now() - Duration::seconds(10);
182        let intent = IntentPacket::new("too late", past);
183        assert!(intent.is_expired(Utc::now()));
184
185        let future = Utc::now() + Duration::hours(1);
186        let intent2 = IntentPacket::new("still valid", future);
187        assert!(!intent2.is_expired(Utc::now()));
188    }
189
190    #[test]
191    fn reversibility_all_variants_serialize() {
192        for variant in [
193            Reversibility::Reversible,
194            Reversibility::Partial,
195            Reversibility::Irreversible,
196        ] {
197            let json = serde_json::to_string(&variant).unwrap();
198            let back: Reversibility = serde_json::from_str(&json).unwrap();
199            assert_eq!(variant, back);
200        }
201    }
202
203    #[test]
204    fn expiry_action_all_variants_serialize() {
205        for variant in [
206            ExpiryAction::Halt,
207            ExpiryAction::Escalate,
208            ExpiryAction::CompleteAndHalt,
209        ] {
210            let json = serde_json::to_string(&variant).unwrap();
211            let back: ExpiryAction = serde_json::from_str(&json).unwrap();
212            assert_eq!(variant, back);
213        }
214    }
215
216    #[test]
217    fn declarative_binding_empty() {
218        let binding = DeclarativeBinding::new().build();
219        assert!(binding.packs.is_empty());
220        assert!(binding.capabilities.is_empty());
221        assert!(binding.invariants.is_empty());
222        assert_eq!(
223            binding.resolution.levels_attempted,
224            vec![ResolutionLevel::Declarative]
225        );
226    }
227
228    #[test]
229    fn declarative_binding_full() {
230        let binding = DeclarativeBinding::new()
231            .pack("customers", "qualification")
232            .pack("knowledge", "enrichment")
233            .capability("web", "scraping")
234            .capability("ocr", "documents")
235            .invariant("lead_has_source")
236            .invariant("claim_has_provenance")
237            .build();
238
239        assert_eq!(binding.packs.len(), 2);
240        assert_eq!(binding.capabilities.len(), 2);
241        assert_eq!(binding.invariants.len(), 2);
242        assert!((binding.packs[0].confidence - 1.0).abs() < f64::EPSILON);
243        assert_eq!(binding.packs[1].pack_name, "knowledge");
244        assert_eq!(binding.capabilities[0].capability, "web");
245    }
246
247    #[test]
248    fn intent_binding_default() {
249        let binding = IntentBinding::default();
250        assert!(binding.packs.is_empty());
251        assert!(binding.capabilities.is_empty());
252        assert!(binding.invariants.is_empty());
253        assert_eq!(binding.resolution.prior_episodes_consulted, 0);
254        assert!((binding.resolution.completeness_confidence - 0.0).abs() < f64::EPSILON);
255    }
256
257    #[test]
258    fn resolution_level_serde() {
259        for level in [
260            ResolutionLevel::Declarative,
261            ResolutionLevel::Structural,
262            ResolutionLevel::Semantic,
263            ResolutionLevel::Learned,
264        ] {
265            let json = serde_json::to_string(&level).unwrap();
266            let back: ResolutionLevel = serde_json::from_str(&json).unwrap();
267            assert_eq!(level, back);
268        }
269    }
270
271    #[test]
272    fn resolution_trace_default() {
273        let trace = ResolutionTrace::default();
274        assert!(trace.levels_attempted.is_empty());
275        assert!(trace.levels_contributed.is_empty());
276        assert_eq!(trace.prior_episodes_consulted, 0);
277    }
278
279    #[test]
280    fn intent_binding_serde_roundtrip() {
281        let binding = DeclarativeBinding::new()
282            .pack("test_pack", "test reason")
283            .capability("web", "needed for scraping")
284            .invariant("my_invariant")
285            .build();
286
287        let json = serde_json::to_string(&binding).unwrap();
288        let back: IntentBinding = serde_json::from_str(&json).unwrap();
289        assert_eq!(back.packs.len(), 1);
290        assert_eq!(back.capabilities.len(), 1);
291        assert_eq!(back.invariants, vec!["my_invariant"]);
292    }
293
294    #[test]
295    fn intent_node_leaf() {
296        let expires = Utc::now() + Duration::hours(1);
297        let intent = IntentPacket::new("leaf task", expires);
298        let node = IntentNode::leaf(intent);
299        assert!(node.is_leaf());
300        assert!(node.children.is_empty());
301        assert_eq!(node.intent.outcome, "leaf task");
302    }
303
304    #[test]
305    fn feasibility_dimension_serde() {
306        for dim in [
307            FeasibilityDimension::Capability,
308            FeasibilityDimension::Context,
309            FeasibilityDimension::Resources,
310            FeasibilityDimension::Authority,
311        ] {
312            let json = serde_json::to_string(&dim).unwrap();
313            let back: FeasibilityDimension = serde_json::from_str(&json).unwrap();
314            assert_eq!(dim, back);
315        }
316    }
317
318    #[test]
319    fn feasibility_kind_serde() {
320        for kind in [
321            FeasibilityKind::Feasible,
322            FeasibilityKind::FeasibleWithConstraints,
323            FeasibilityKind::Uncertain,
324            FeasibilityKind::Infeasible,
325        ] {
326            let json = serde_json::to_string(&kind).unwrap();
327            let back: FeasibilityKind = serde_json::from_str(&json).unwrap();
328            assert_eq!(kind, back);
329        }
330    }
331
332    #[test]
333    fn admission_result_serde_roundtrip() {
334        let result = AdmissionResult {
335            feasible: true,
336            dimensions: vec![FeasibilityAssessment {
337                dimension: FeasibilityDimension::Capability,
338                kind: FeasibilityKind::Feasible,
339                reason: "all good".into(),
340            }],
341            rejection_reason: None,
342        };
343        let json = serde_json::to_string(&result).unwrap();
344        let back: AdmissionResult = serde_json::from_str(&json).unwrap();
345        assert!(back.feasible);
346        assert_eq!(back.dimensions.len(), 1);
347        assert!(back.rejection_reason.is_none());
348    }
349
350    #[test]
351    fn forbidden_action_serde_roundtrip() {
352        let fa = ForbiddenAction {
353            action: "delete_all".into(),
354            reason: "too destructive".into(),
355        };
356        let json = serde_json::to_string(&fa).unwrap();
357        let back: ForbiddenAction = serde_json::from_str(&json).unwrap();
358        assert_eq!(back.action, "delete_all");
359        assert_eq!(back.reason, "too destructive");
360    }
361
362    #[test]
363    fn challenge_fields() {
364        let challenge = Challenge::new(
365            SkepticismKind::EconomicSkepticism,
366            uuid::Uuid::new_v4(),
367            "spend too high",
368            Severity::Warning,
369        );
370        assert_eq!(challenge.kind, SkepticismKind::EconomicSkepticism);
371        assert_eq!(challenge.severity, Severity::Warning);
372        assert_eq!(challenge.description, "spend too high");
373    }
374
375    #[test]
376    fn challenge_is_blocking() {
377        let blocker = Challenge::new(
378            SkepticismKind::ConstraintChecking,
379            uuid::Uuid::new_v4(),
380            "hard stop",
381            Severity::Blocker,
382        );
383        let warning = Challenge::new(
384            SkepticismKind::ConstraintChecking,
385            uuid::Uuid::new_v4(),
386            "soft",
387            Severity::Warning,
388        );
389        assert!(blocker.is_blocking());
390        assert!(!warning.is_blocking());
391    }
392
393    #[test]
394    fn severity_all_variants_accessible() {
395        let variants = [Severity::Advisory, Severity::Warning, Severity::Blocker];
396        assert_eq!(variants.len(), 3);
397    }
398
399    #[test]
400    fn skepticism_kind_all_variants_accessible() {
401        let variants = [
402            SkepticismKind::AssumptionBreaking,
403            SkepticismKind::ConstraintChecking,
404            SkepticismKind::CausalSkepticism,
405            SkepticismKind::EconomicSkepticism,
406            SkepticismKind::OperationalSkepticism,
407        ];
408        assert_eq!(variants.len(), 5);
409    }
410
411    #[test]
412    fn simulation_dimension_all_variants_accessible() {
413        let variants = [
414            SimulationDimension::Outcome,
415            SimulationDimension::Cost,
416            SimulationDimension::Policy,
417            SimulationDimension::Causal,
418            SimulationDimension::Operational,
419        ];
420        assert_eq!(variants.len(), 5);
421    }
422
423    #[test]
424    fn dimension_result_construction() {
425        let result = DimensionResult {
426            dimension: SimulationDimension::Cost,
427            passed: true,
428            confidence: 0.95,
429            findings: vec!["within budget".into()],
430            samples: vec![],
431        };
432        assert!(result.passed);
433        assert!((result.confidence - 0.95).abs() < f64::EPSILON);
434        assert_eq!(result.findings.len(), 1);
435    }
436
437    #[test]
438    fn lesson_construction() {
439        let lesson = Lesson {
440            insight: "score 0.88 → approved".into(),
441            context: "expense approval".into(),
442            confidence: 0.9,
443            planning_adjustment: "none".into(),
444        };
445        assert_eq!(lesson.insight, "score 0.88 → approved");
446        assert!((lesson.confidence - 0.9).abs() < f64::EPSILON);
447    }
448}