reasoninglayer 0.1.2

Rust client SDK for the Reasoning Layer API
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
//! Inference DTOs — rules, facts, backward / forward chaining, Bayesian prediction, NAF.

use std::collections::BTreeMap;

use serde::{Deserialize, Serialize};

use super::homoiconic::{PsiTermDto, TermInputDto};

// ─── Solutions and proofs ─────────────────────────────────────────────────────

/// A binding within a substitution, mapping a variable to its bound value.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BindingDto {
    pub variable_term_id: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub variable_name: Option<String>,
    pub bound_to_term_id: String,
    pub bound_to_display: String,
}

/// A substitution produced by backward chaining.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct HomoiconicSubstitutionDto {
    #[serde(default)]
    pub bindings: Vec<BindingDto>,
}

/// Recursive proof tree node.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProofDto {
    pub goal_term_id: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub rule_term_id: Option<String>,
    pub substitution: HomoiconicSubstitutionDto,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub subproofs: Vec<ProofDto>,
    pub certainty: f64,
}

/// Provenance tag from tagged forward chaining.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProvenanceTagDto {
    pub fact_index: u64,
    pub confidence: f64,
}

/// A solution from backward chaining.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SolutionDto {
    pub substitution: HomoiconicSubstitutionDto,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub proof: Option<ProofDto>,
    pub certainty: f64,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub evidence_matched: Option<u64>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub evidence_ratio: Option<f64>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub residuated_sorts: Vec<String>,
}

// ─── Constraint / literal inputs ──────────────────────────────────────────────

/// Guard constraint operators.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum GuardOp {
    Lt,
    Lte,
    Gt,
    Gte,
    Eq,
    Ne,
}

impl GuardOp {
    /// Get the operator's wire form as a string (`"lt"`, `"lte"`, …).
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::Lt => "lt",
            Self::Lte => "lte",
            Self::Gt => "gt",
            Self::Gte => "gte",
            Self::Eq => "eq",
            Self::Ne => "ne",
        }
    }
}

/// Allen temporal relation operators for interval algebra.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AllenRelation {
    Before,
    After,
    Meets,
    MetBy,
    Overlaps,
    OverlappedBy,
    During,
    Contains,
    Starts,
    StartedBy,
    Finishes,
    FinishedBy,
    Equals,
}

/// Constraint input for backward chaining (legacy; prefer `ConstrainedVariable` in the goal).
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum ConstraintInputDto {
    Guard {
        left: String,
        op: String,
        right: String,
    },
    Equality {
        var1: String,
        var2: String,
    },
    Disequality {
        var1: String,
        var2: String,
    },
    Allen {
        interval_a: String,
        interval_b_term_id: String,
        relation: String,
    },
}

/// A literal input for NAF proving.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LiteralInputDto {
    pub term: TermInputDto,
    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
    pub negated: bool,
}

// ─── Backward chaining ────────────────────────────────────────────────────────

/// Request for backward chaining. Specify either `goal` (inline) OR `goal_id` (saved), not both.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct BackwardChainRequest {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub goal: Option<TermInputDto>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub goal_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub save_goal: Option<bool>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub open_world: Option<bool>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub max_depth: Option<u32>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub max_solutions: Option<u32>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub min_certainty: Option<f64>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub timeout_ms: Option<u64>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub constraints: Vec<ConstraintInputDto>,
}

/// Response from backward chaining.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct BackwardChainResponse {
    #[serde(default)]
    pub solutions: Vec<SolutionDto>,
    #[serde(default)]
    pub query_time_ms: u64,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub goal_id: Option<String>,
}

// ─── Forward chaining ─────────────────────────────────────────────────────────

/// Request for forward chaining.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ForwardChainRequest {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub initial_facts: Option<Vec<TermInputDto>>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub persist_derived: Option<bool>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub enable_provenance_tags: Option<bool>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub max_iterations: Option<u32>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub max_facts: Option<u32>,
}

/// Response from forward chaining.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ForwardChainResponse {
    #[serde(default)]
    pub derived_facts: Vec<PsiTermDto>,
    #[serde(default)]
    pub derived_count: u64,
    #[serde(default)]
    pub iterations: u32,
    #[serde(default)]
    pub total_facts: u64,
    #[serde(default)]
    pub materialization_time_ms: u64,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub persisted_count: Option<u64>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub provenance_tags: Vec<ProvenanceTagDto>,
}

// ─── Rules / facts ────────────────────────────────────────────────────────────

/// Request to add an inference rule.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AddRuleRequest {
    /// The conclusion (head of the rule).
    pub term: TermInputDto,
    /// Antecedent terms (body of the rule).
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub antecedents: Vec<TermInputDto>,
    /// Certainty factor (default: 1.0).
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub certainty: Option<f64>,
}

/// Request to add a fact.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AddFactRequest {
    pub term: TermInputDto,
}

/// Request to bulk-add rules.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BulkAddRulesRequest {
    pub rules: Vec<AddRuleRequest>,
}

/// Request to bulk-add facts.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BulkAddFactsRequest {
    pub facts: Vec<TermInputDto>,
}

/// Response from adding a fact.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AddFactResponse {
    pub term: PsiTermDto,
}

/// Response from adding a rule.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AddRuleResponse {
    pub term: PsiTermDto,
}

/// Response from bulk-adding rules.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct BulkAddRulesResponse {
    #[serde(default)]
    pub rule_term_ids: Vec<String>,
    #[serde(default)]
    pub rules_added: u64,
}

/// Response from bulk-adding facts.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct BulkAddFactsResponse {
    #[serde(default)]
    pub facts_added: u64,
    #[serde(default)]
    pub term_ids: Vec<String>,
}

/// Response from getting all facts.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct GetFactsResponse {
    #[serde(default)]
    pub count: u64,
    #[serde(default)]
    pub facts: Vec<PsiTermDto>,
}

/// Response from clearing facts.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClearFactsResponse {
    pub facts_cleared: u64,
    pub message: String,
}

// ─── Goals ────────────────────────────────────────────────────────────────────

/// A goal definition used as input.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GoalDto {
    pub sort: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub sort_id: Option<String>,
    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
    pub features: BTreeMap<String, serde_json::Value>,
}

/// Request to create a new saved goal.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateGoalRequest {
    pub clauses: Vec<TermInputDto>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub constraints: Vec<ConstraintInputDto>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub min_degree: Option<f64>,
}

/// Response from creating a saved goal.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CreateGoalResponse {
    pub goal_id: String,
    pub goal: PsiTermDto,
    pub clause_count: u64,
    pub constraint_count: u64,
}

/// Response from deleting a saved goal.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeleteGoalResponse {
    pub deleted: bool,
    pub goal_id: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub references_deleted: Option<u64>,
}

/// Summary of a saved goal.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GoalSummaryDto {
    pub goal_id: String,
    pub clause_count: u64,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub min_degree: Option<f64>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub created_at: Option<String>,
}

/// Response from listing goals.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ListGoalsResponse {
    pub count: u64,
    #[serde(default)]
    pub goals: Vec<GoalSummaryDto>,
}

// ─── Fuzzy proving ────────────────────────────────────────────────────────────

/// Request for fuzzy proof search.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct FuzzyProveRequest {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub goal: Option<TermInputDto>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub goal_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub min_degree: Option<f64>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub max_solutions: Option<u32>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub open_world: Option<bool>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub save_goal: Option<bool>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub tnorm: Option<String>,
}

/// Response from fuzzy proof search.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct FuzzyProveResponse {
    #[serde(default)]
    pub solutions: Vec<SolutionDto>,
    #[serde(default)]
    pub query_time_ms: u64,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub goal_id: Option<String>,
}

/// Bulk fuzzy prove request.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct BulkFuzzyProveRequest {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub goal_ids: Option<Vec<String>>,
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub goals: Vec<TermInputDto>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub max_depth: Option<u32>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub max_solutions: Option<u32>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub min_degree: Option<f64>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub save_goals: Option<bool>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub tnorm: Option<String>,
}

/// Bulk fuzzy prove response.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct BulkFuzzyProveResponse {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub goal_ids: Option<Vec<String>>,
    #[serde(default)]
    pub results: Vec<FuzzyProveResponse>,
    #[serde(default)]
    pub total_time_ms: u64,
}

// ─── Bayesian prediction ──────────────────────────────────────────────────────

/// Bayesian effect shape.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "shape", rename_all = "lowercase")]
pub enum BayesianEffectDto {
    Gaussian { mean: f64, std_dev: f64 },
    Triangular { a: f64, b: f64, c: f64 },
    Trapezoidal { a: f64, b: f64, c: f64, d: f64 },
}

/// Bayesian prediction request.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BayesianPredictRequest {
    pub effect_var: String,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub goal: Option<TermInputDto>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub goal_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub max_depth: Option<u32>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub min_degree: Option<f64>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub conformal: Option<f64>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub robust: Option<bool>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub save_goal: Option<bool>,
}

/// Bayesian prediction response.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct BayesianPredictResponse {
    #[serde(default)]
    pub degree: f64,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub effect: Option<BayesianEffectDto>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub goal_id: Option<String>,
    #[serde(default)]
    pub proof_trace: Vec<String>,
    #[serde(default)]
    pub query_time_ms: u64,
    #[serde(default)]
    pub sources_merged: u64,
    #[serde(default)]
    pub success: bool,
}

// ─── NAF ──────────────────────────────────────────────────────────────────────

/// Request for negation-as-failure proof search.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct NafProveRequest {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub literals: Option<Vec<LiteralInputDto>>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub goal_id: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub max_depth: Option<u32>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub max_solutions: Option<u32>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub save_goal: Option<bool>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub timeout_ms: Option<u64>,
}

/// Response from NAF proof search (mirrors [`BackwardChainResponse`]).
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct NafProveResponse {
    #[serde(default)]
    pub solutions: Vec<SolutionDto>,
    #[serde(default)]
    pub query_time_ms: u64,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub goal_id: Option<String>,
}

// ─── Tagged forward chaining ──────────────────────────────────────────────────

/// A fact confidence entry.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FactConfidenceEntry {
    pub term_id: String,
    pub confidence: f64,
}

/// Tagged forward chaining request.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TaggedForwardChainRequest {
    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub fact_confidences: Vec<FactConfidenceEntry>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub max_facts: Option<u32>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub max_iterations: Option<u32>,
}

/// A derived fact with provenance confidence.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TaggedDerivedFact {
    pub term_id: String,
    pub confidence: f64,
    pub is_initial: bool,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub sort_name: Option<String>,
}

/// Tagged forward chaining response.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TaggedForwardChainResponse {
    #[serde(default)]
    pub derived_count: u64,
    #[serde(default)]
    pub execution_time_ms: u64,
    #[serde(default)]
    pub facts: Vec<TaggedDerivedFact>,
    #[serde(default)]
    pub fixpoint_reached: bool,
    #[serde(default)]
    pub iterations: u32,
    #[serde(default)]
    pub total_facts: u64,
}

/// Meta-sorts response — a free-form map of meta-sort names to their UUIDs.
///
/// The backend exposes 170+ meta-sort entries. Rather than enumerating each field, the SDK exposes
/// the raw map and provides helper accessors for common ones. New meta-sorts added server-side are
/// returned transparently without an SDK update.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(transparent)]
pub struct MetaSortsResponse {
    /// Name → UUID mapping.
    pub meta_sorts: BTreeMap<String, String>,
}

impl MetaSortsResponse {
    /// Look up a meta-sort UUID by name (e.g. `"guard_constraint"`).
    pub fn get(&self, name: &str) -> Option<&str> {
        self.meta_sorts.get(name).map(String::as_str)
    }
}