Skip to main content

exo_dag_db_api/
lib.rs

1//! ExoChain DAG DB API DTOs.
2//!
3//! `exo-dag-db-api` owns these wire shapes. `exo-api::dagdb` re-exports them
4//! as the stable compatibility path for gateway, node, SDK, and external
5//! callers.
6//!
7//! Every consumer-facing REST **response** DTO carries a stable
8//! `schema_version` string constant so a non-Rust integrator can detect the
9//! wire-contract version directly from a response body. The constants below are
10//! the single source of truth; the checked-in machine contract under
11//! `docs/dagdb/api/` and the fixture round-trip tests are asserted against them.
12
13#![cfg_attr(test, allow(clippy::expect_used))]
14
15use serde::{Deserialize, Serialize};
16
17/// Schema version emitted on every `DagDbIntakeResponse`.
18pub const DAGDB_INTAKE_RESPONSE_SCHEMA_VERSION: &str = "dagdb_intake_response_v1";
19/// Schema version emitted on every `DagDbRouteResponse`.
20pub const DAGDB_ROUTE_RESPONSE_SCHEMA_VERSION: &str = "dagdb_route_response_v1";
21/// Schema version emitted on every `DagDbContextPacketResponse`.
22pub const DAGDB_CONTEXT_PACKET_RESPONSE_SCHEMA_VERSION: &str = "dagdb_context_packet_response_v1";
23/// Schema version emitted on every `DagDbValidateResponse`.
24pub const DAGDB_VALIDATE_RESPONSE_SCHEMA_VERSION: &str = "dagdb_validate_response_v1";
25/// Schema version emitted on every `DagDbWritebackResponse`.
26pub const DAGDB_WRITEBACK_RESPONSE_SCHEMA_VERSION: &str = "dagdb_writeback_response_v1";
27/// Schema version emitted on every `DagDbImportResponse`.
28pub const DAGDB_IMPORT_RESPONSE_SCHEMA_VERSION: &str = "dagdb_import_response_v1";
29/// Schema version emitted on every `DagDbExportResponse`.
30pub const DAGDB_EXPORT_RESPONSE_SCHEMA_VERSION: &str = "dagdb_export_response_v1";
31/// Schema version emitted on every `DagDbTrustCheckResponse`.
32pub const DAGDB_TRUST_CHECK_RESPONSE_SCHEMA_VERSION: &str = "dagdb_trust_check_response_v1";
33/// Schema version emitted on every `DagDbCouncilDecisionResponse`.
34pub const DAGDB_COUNCIL_DECISION_RESPONSE_SCHEMA_VERSION: &str =
35    "dagdb_council_decision_response_v1";
36/// Schema version emitted on every `DagDbReceiptLookupResponse`.
37pub const DAGDB_RECEIPT_LOOKUP_RESPONSE_SCHEMA_VERSION: &str = "dagdb_receipt_lookup_response_v1";
38/// Schema version emitted on every `DagDbCatalogLookupResponse`.
39pub const DAGDB_CATALOG_LOOKUP_RESPONSE_SCHEMA_VERSION: &str = "dagdb_catalog_lookup_response_v1";
40/// Schema version emitted on every `DagDbRouteLookupResponse`.
41pub const DAGDB_ROUTE_LOOKUP_RESPONSE_SCHEMA_VERSION: &str = "dagdb_route_lookup_response_v1";
42
43/// Safe metadata decision recorded after server-side sanitization.
44#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
45#[serde(rename_all = "snake_case")]
46pub enum SafeMetadataDecision {
47    /// Metadata is safe and stored as-is within the configured length bound.
48    Allow,
49    /// Metadata was deterministically redacted before storage or response.
50    Redact,
51    /// Metadata must not persist.
52    Reject,
53}
54
55/// Deterministic metadata redaction code.
56#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
57#[serde(rename_all = "snake_case")]
58pub enum RedactionCode {
59    /// US Social Security number marker.
60    Ssn,
61    /// Payment card number marker.
62    Card,
63    /// NDA or confidential marker.
64    ConfidentialMarker,
65    /// Protected health information marker.
66    Phi,
67    /// Private customer marker.
68    CustomerPrivate,
69    /// Raw source code excerpt marker.
70    CodeExcerpt,
71    /// Length truncation marker.
72    LengthTruncation,
73}
74
75/// Trusted stored metadata shape.
76#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
77#[serde(deny_unknown_fields)]
78pub struct SafeMetadata {
79    pub decision: SafeMetadataDecision,
80    pub text: String,
81    pub redaction_codes: Vec<RedactionCode>,
82    pub original_hash: String,
83    pub truncated: bool,
84    pub byte_len: u32,
85}
86
87/// Memory node type.
88#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
89#[serde(rename_all = "snake_case")]
90pub enum MemoryNodeType {
91    Source,
92    Excerpt,
93    Embedding,
94    Summary,
95    Answer,
96    ValidationReport,
97    Catalog,
98    Route,
99    ContextPacket,
100}
101
102/// Inbound memory source type.
103#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
104#[serde(rename_all = "snake_case")]
105pub enum SourceType {
106    PublicWeb,
107    PrivateCustomer,
108    IpSensitive,
109    Generated,
110    OpenSource,
111    UnknownProvenance,
112    BenchmarkFixture,
113}
114
115/// Consent purpose for the requested DAG DB action.
116#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
117#[serde(rename_all = "snake_case")]
118pub enum ConsentPurpose {
119    Retrieval,
120    Validation,
121    Writeback,
122    Import,
123    Export,
124    Benchmark,
125    TrustCheck,
126}
127
128/// Memory status.
129#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
130#[serde(rename_all = "snake_case")]
131pub enum MemoryStatus {
132    Pending,
133    Approved,
134    Routable,
135    Blocked,
136    Revoked,
137    Superseded,
138    Rejected,
139}
140
141/// Validation status.
142#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
143#[serde(rename_all = "snake_case")]
144pub enum ValidationStatus {
145    NotRequired,
146    Pending,
147    Passed,
148    Failed,
149    Contradictory,
150    Expired,
151    NeedsCouncil,
152}
153
154/// Route status.
155#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
156#[serde(rename_all = "snake_case")]
157pub enum RouteStatus {
158    Pending,
159    Active,
160    Stale,
161    Invalidated,
162    Blocked,
163}
164
165/// Council review status.
166#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
167#[serde(rename_all = "snake_case")]
168pub enum CouncilReviewStatus {
169    NotRequired,
170    Required,
171    Pending,
172    Approved,
173    Denied,
174    Expired,
175    Escalated,
176}
177
178/// Durable council decision status.
179#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
180#[serde(rename_all = "snake_case")]
181pub enum CouncilDecisionStatus {
182    Approved,
183    Denied,
184    Expired,
185    Escalated,
186    Revoked,
187}
188
189/// DAG finality status for route/context eligibility.
190#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
191#[serde(rename_all = "snake_case")]
192pub enum DagFinalityStatus {
193    Pending,
194    Committed,
195    Failed,
196    Compensated,
197}
198
199/// Risk class.
200#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
201pub enum RiskClass {
202    R0,
203    R1,
204    R2,
205    R3,
206    R4,
207    R5,
208}
209
210/// DAG DB subject kind.
211#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
212#[serde(rename_all = "snake_case")]
213pub enum SubjectKind {
214    Memory,
215    Catalog,
216    Route,
217    ContextPacket,
218    ValidationReport,
219    AgentSafetyScore,
220    InboundAgentCredential,
221    CouncilDecision,
222}
223
224/// Receipt event type.
225#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
226#[serde(rename_all = "snake_case")]
227pub enum ReceiptEventType {
228    IntakeCreated,
229    DuplicateRejected,
230    ValidationCreated,
231    ValidationPassed,
232    ValidationFailed,
233    MemoryApproved,
234    MemoryRoutable,
235    MemoryRevoked,
236    MemorySuperseded,
237    RouteCreated,
238    RouteActivated,
239    RouteStale,
240    RouteInvalidated,
241    ContextPacketCreated,
242    WritebackCreated,
243    TrustCheckCreated,
244    CouncilDecisionRecorded,
245    DagFinalityCommitted,
246    DagFinalityFailed,
247    DagFinalityCompensated,
248    DagdbApprovalRequestSubmitted,
249    DagdbApprovalGranted,
250    DagdbApprovalDenied,
251    DagdbRecordAccepted,
252    DagdbImportCompleted,
253    DagdbExportCompleted,
254    DagdbReplayDetected,
255    DagdbIdempotencyConflict,
256    DagdbRlsTenantViolation,
257    DagdbSignatureFailure,
258    DagdbCouncilOperatorDecision,
259}
260
261/// Decision source for a durable council decision.
262#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
263#[serde(rename_all = "snake_case")]
264pub enum DecisionSource {
265    Human,
266    Council,
267    Policy,
268}
269
270/// Memory edge type.
271#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
272#[serde(rename_all = "snake_case")]
273pub enum MemoryEdgeType {
274    Parent,
275    DerivedFrom,
276    Cites,
277    Contradicts,
278    Supersedes,
279    Validates,
280}
281
282/// Validation decision.
283#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
284#[serde(rename_all = "snake_case")]
285pub enum ValidationDecision {
286    Allow,
287    Block,
288    NeedsCouncil,
289    Invalidate,
290    Revoke,
291    Supersede,
292}
293
294/// Credential status.
295#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
296#[serde(rename_all = "snake_case")]
297pub enum CredentialStatus {
298    Pending,
299    Active,
300    Expired,
301    Revoked,
302    Blocked,
303}
304
305/// Memory graph style names used by the graph-organization layer.
306#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
307#[serde(rename_all = "snake_case")]
308pub enum MemoryGraphStyle {
309    ProvenanceReceiptDag,
310    CanonicalMemoryGraph,
311    SemanticCatalogGraph,
312    SimilarityOverlayGraph,
313    DependencyDag,
314    RoutingViewGraph,
315    ContradictionSupersessionGraph,
316    ContextPacketGraph,
317}
318
319/// Memory graph node taxonomy.
320#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
321#[serde(rename_all = "snake_case")]
322pub enum MemoryNodeKind {
323    Raw,
324    Chunk,
325    Summary,
326    Concept,
327    Canonical,
328    DuplicateReference,
329    Related,
330    Replacement,
331    Contradiction,
332    Supersession,
333    AlternateSummary,
334    Decision,
335    Route,
336    ValidationReport,
337    SavingsReport,
338}
339
340/// Memory graph edge taxonomy.
341#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
342#[serde(rename_all = "snake_case")]
343pub enum MemoryEdgeKind {
344    DerivedFrom,
345    Summarizes,
346    Supports,
347    Contradicts,
348    Supersedes,
349    Replaces,
350    DuplicateOf,
351    NearDuplicateOf,
352    RelatedTo,
353    AlternativeSummaryOf,
354    DependsOn,
355    PartOf,
356    OwnedBy,
357    AccessGrantedBy,
358    VerifiedBy,
359    UsedByRoute,
360    IncludedInContextPacket,
361    RevokedBy,
362}
363
364/// Compact memory-candidate kind emitted after task completion.
365#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
366#[serde(rename_all = "snake_case")]
367pub enum MemoryCandidateKind {
368    Decision,
369    Summary,
370    Plan,
371    Schema,
372    RouteFeedback,
373    Contradiction,
374    Preference,
375    SavingsObservation,
376}
377
378/// Allowed future use for a compact memory candidate.
379#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
380#[serde(rename_all = "snake_case")]
381pub enum MemoryCandidateUse {
382    Inference,
383    Routing,
384    Audit,
385}
386
387/// Compact system-side writeback signal.
388#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
389#[serde(deny_unknown_fields)]
390pub struct MemoryCandidate {
391    #[serde(rename = "type")]
392    pub candidate_type: String,
393    pub source_request_id: String,
394    pub candidate_kind: MemoryCandidateKind,
395    pub summary: String,
396    pub full_output_hash: String,
397    pub parent_context_packet_id: String,
398    pub evidence_receipts: Vec<String>,
399    pub risk_hint: RiskClass,
400    pub allowed_future_uses: Vec<MemoryCandidateUse>,
401    pub reason_to_remember: String,
402}
403
404/// Similarity class found before canonicalization.
405#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
406#[serde(rename_all = "snake_case")]
407pub enum SimilarityType {
408    ExactHash,
409    NearDuplicate,
410    ConceptOverlap,
411    WeakRelated,
412}
413
414/// Similarity result for one candidate-memory comparison.
415#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
416#[serde(deny_unknown_fields)]
417pub struct SimilarityResult {
418    pub candidate_memory_id: String,
419    pub similarity_type: SimilarityType,
420    pub similarity_bp: u16,
421    pub matched_fields: Vec<String>,
422    pub reason: String,
423}
424
425/// Canonicalization decision values.
426#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
427#[serde(rename_all = "snake_case")]
428pub enum CanonicalizationDecisionKind {
429    NewCanonical,
430    ExactDuplicate,
431    NearDuplicate,
432    Related,
433    Replacement,
434    Contradiction,
435    Supersession,
436    AlternateSummary,
437    RejectedNeedsReview,
438}
439
440/// Edge the canonicalization decision requires.
441#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
442#[serde(deny_unknown_fields)]
443pub struct GraphEdgeRef {
444    pub from_memory_id: String,
445    pub to_memory_id: String,
446    pub edge_kind: MemoryEdgeKind,
447}
448
449/// Explicit canonicalization decision.
450#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
451#[serde(deny_unknown_fields)]
452pub struct CanonicalizationDecision {
453    pub decision_id: String,
454    pub input_memory_id: String,
455    pub canonical_memory_id: Option<String>,
456    pub matched_memory_ids: Vec<String>,
457    pub decision_kind: CanonicalizationDecisionKind,
458    pub decision_reason: String,
459    pub confidence_bp: u16,
460    pub risk_class: RiskClass,
461    pub validator_status: ValidationStatus,
462    pub required_edges_to_create: Vec<GraphEdgeRef>,
463    pub receipt_intent: String,
464    pub receipt_id: Option<String>,
465}
466
467/// Rebuildable graph view kind.
468#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
469#[serde(rename_all = "snake_case")]
470pub enum GraphViewType {
471    FullProvenance,
472    RoutingView,
473    CanonicalView,
474    DependencyView,
475    ContradictionView,
476    ContextPacketView,
477}
478
479/// Rebuildable graph view artifact.
480#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
481#[serde(deny_unknown_fields)]
482pub struct GraphView {
483    pub view_id: String,
484    pub graph_style: MemoryGraphStyle,
485    pub source_root_id: String,
486    pub included_node_ids: Vec<String>,
487    pub included_edge_ids: Vec<String>,
488    pub view_type: GraphViewType,
489    pub topological_order: Vec<String>,
490    pub transitive_reduction_edges: Vec<GraphEdgeRef>,
491    pub omitted_edges: Vec<GraphEdgeRef>,
492    pub reason_edges_omitted: Vec<String>,
493}
494
495/// Route invalidation trigger.
496#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
497#[serde(rename_all = "snake_case")]
498pub enum RouteInvalidationTrigger {
499    Revoked,
500    Superseded,
501    Contradicted,
502    Replaced,
503    PermissionChanged,
504    RiskChanged,
505}
506
507/// Route invalidation target status.
508#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
509#[serde(rename_all = "snake_case")]
510pub enum RouteInvalidationStatus {
511    Stale,
512    Invalidated,
513    NeedsReview,
514    Superseded,
515}
516
517/// Governed route invalidation event payload.
518#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
519#[serde(deny_unknown_fields)]
520pub struct RouteInvalidationReceipt {
521    pub route_id: String,
522    pub affected_memory_ids: Vec<String>,
523    pub trigger_type: RouteInvalidationTrigger,
524    pub triggering_receipt_id: String,
525    pub prior_route_status: RouteStatus,
526    pub new_route_status: RouteInvalidationStatus,
527    pub invalidation_reason: String,
528    pub created_at: String,
529    pub validator_id: Option<String>,
530    pub validation_report_id: Option<String>,
531    pub receipt_intent: String,
532    pub receipt_id: Option<String>,
533}
534
535/// Placement result returned by the graph organization layer.
536#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
537#[serde(deny_unknown_fields)]
538pub struct PlacementResult {
539    pub input_memory_id: String,
540    pub canonicalization_decision: CanonicalizationDecision,
541    pub similarity_results: Vec<SimilarityResult>,
542    pub proposed_canonical_node: Option<String>,
543    pub edges_to_create: Vec<GraphEdgeRef>,
544    pub catalog_updates: Vec<String>,
545    pub graph_views_to_refresh: Vec<MemoryGraphStyle>,
546    pub route_invalidations: Vec<RouteInvalidationReceipt>,
547    pub validator_report: String,
548    pub receipt_id: Option<String>,
549    pub receipt_intent: Option<String>,
550}
551
552/// Shared error envelope for every DAG DB route.
553#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
554#[serde(deny_unknown_fields)]
555pub struct DagDbErrorEnvelope {
556    pub error_code: String,
557    pub message: String,
558    pub receipt_hash: Option<String>,
559    pub validation_report_id: Option<String>,
560    pub requires_council_review: bool,
561    #[serde(default, skip_serializing_if = "Option::is_none")]
562    pub operational_event_type: Option<String>,
563}
564
565/// Intake request.
566#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
567#[serde(deny_unknown_fields)]
568pub struct DagDbIntakeRequest {
569    pub tenant_id: String,
570    pub namespace: String,
571    pub idempotency_key: String,
572    pub source_type: SourceType,
573    pub source_hash: String,
574    pub payload_hash: String,
575    pub owner_did: String,
576    pub controller_did: String,
577    pub submitted_by_did: String,
578    pub consent_purpose: ConsentPurpose,
579    pub requested_action: String,
580    pub title_text: String,
581    pub summary_text: String,
582    pub payload_uri_hash: Option<String>,
583    pub parent_memory_ids: Option<Vec<String>>,
584    pub edge_types: Option<Vec<MemoryEdgeType>>,
585    pub access_policy_hash: Option<String>,
586    pub declared_rights_hash: Option<String>,
587    pub keyword_texts: Option<Vec<String>>,
588}
589
590/// Route request.
591#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
592#[serde(deny_unknown_fields)]
593pub struct DagDbRouteRequest {
594    pub tenant_id: String,
595    pub namespace: String,
596    pub idempotency_key: String,
597    pub requesting_agent_did: String,
598    pub task_signature_hash: String,
599    pub approved_scope_hash: String,
600    pub token_budget: u32,
601    pub start_catalog_id: Option<String>,
602    pub requested_memory_ids: Option<Vec<String>>,
603    pub credential_id: Option<String>,
604}
605
606/// Context-packet request.
607#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
608#[serde(deny_unknown_fields)]
609pub struct DagDbContextPacketRequest {
610    pub tenant_id: String,
611    pub namespace: String,
612    pub idempotency_key: String,
613    pub request_id: String,
614    pub route_id: String,
615    pub task_hash: String,
616    pub requesting_agent_did: String,
617    pub token_budget: u32,
618    pub force_revalidate: Option<bool>,
619    pub max_memory_refs: Option<u32>,
620    #[serde(default, skip_serializing_if = "Option::is_none")]
621    pub task: Option<String>,
622    #[serde(default, skip_serializing_if = "Option::is_none")]
623    pub layered_mode: Option<String>,
624    #[serde(default, skip_serializing_if = "Option::is_none")]
625    pub max_layer_depth: Option<u32>,
626    #[serde(default, skip_serializing_if = "Option::is_none")]
627    pub require_layer_evidence: Option<bool>,
628    /// Depth-on-demand reserve, in basis points of the token budget (D1-S4).
629    /// When set with a non-off `layered_mode`, the breadth pass runs at the
630    /// reserved (reduced) budget so membership-triggered drilldown has room to
631    /// spend depth-on-demand up to the full budget. Absent / `0` is byte-
632    /// identical to the prior leftover-budget behavior; the off-mode packet is
633    /// unaffected.
634    #[serde(default, skip_serializing_if = "Option::is_none")]
635    pub drilldown_reserve_bp: Option<u32>,
636}
637
638/// Validation request.
639#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
640#[serde(deny_unknown_fields)]
641pub struct DagDbValidateRequest {
642    pub tenant_id: String,
643    pub namespace: String,
644    pub idempotency_key: String,
645    pub subject_kind: SubjectKind,
646    pub subject_id: String,
647    pub validator_did: String,
648    pub requested_status: Option<ValidationStatus>,
649    pub council_decision_id: Option<String>,
650    pub validation_notes_text: Option<String>,
651}
652
653/// Writeback request.
654#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
655#[serde(deny_unknown_fields)]
656pub struct DagDbWritebackRequest {
657    pub tenant_id: String,
658    pub namespace: String,
659    pub idempotency_key: String,
660    pub requesting_agent_did: String,
661    pub parent_memory_ids: Vec<String>,
662    pub answer_hash: String,
663    pub route_id: String,
664    pub context_packet_id: String,
665    pub validation_report_id: String,
666    pub summary_text: Option<String>,
667    pub citation_hashes: Option<Vec<String>>,
668    pub safety_score_id: Option<String>,
669    pub keyword_texts: Option<Vec<String>>,
670    /// Optional typed-knowledge class (`decision`, `finding`, `fix`,
671    /// `constraint`, `handoff`). When absent the writeback is plain
672    /// usage-event telemetry and every existing client/signature path is
673    /// unchanged. When present it describes WHAT the memory is for later
674    /// recall; it never influences deterministic placement/organization.
675    #[serde(default, skip_serializing_if = "Option::is_none")]
676    pub knowledge_class: Option<String>,
677    #[serde(default, skip_serializing_if = "Option::is_none")]
678    pub layered_mode: Option<String>,
679    #[serde(default, skip_serializing_if = "Option::is_none")]
680    pub target_layer_path: Option<String>,
681    #[serde(default, skip_serializing_if = "Option::is_none")]
682    pub target_layer_depth: Option<u32>,
683    #[serde(default, skip_serializing_if = "Option::is_none")]
684    pub target_layer_reason: Option<String>,
685}
686
687/// Runtime import request.
688#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
689#[serde(deny_unknown_fields)]
690pub struct DagDbImportRequest {
691    pub tenant_id: String,
692    pub namespace: String,
693    pub idempotency_key: String,
694    pub db_set_version: String,
695    pub source_hash: String,
696    pub requester_did: String,
697    pub import_report: serde_json::Value,
698}
699
700/// Runtime export request.
701#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
702#[serde(deny_unknown_fields)]
703pub struct DagDbExportRequest {
704    pub tenant_id: String,
705    pub namespace: String,
706    pub idempotency_key: String,
707    pub db_set_version: String,
708    pub requester_did: String,
709    pub included_memory_ids: Vec<String>,
710    pub included_graph_styles: Vec<String>,
711    pub included_writeback_idempotency_keys: Vec<String>,
712    pub source_commit_or_repo_ref: Option<String>,
713    pub include_preview_context: bool,
714}
715
716/// Trust-check request.
717#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
718#[serde(deny_unknown_fields)]
719pub struct DagDbTrustCheckRequest {
720    pub tenant_id: String,
721    pub namespace: String,
722    pub idempotency_key: String,
723    pub agent_did: String,
724    pub operator_did: String,
725    pub model_name: String,
726    pub model_version: String,
727    pub provider_or_builder: String,
728    pub requested_action: String,
729    pub requested_scope_hash: String,
730    pub purpose: ConsentPurpose,
731    pub autonomy_level: String,
732    pub nonce: String,
733    pub expires_at: String,
734    pub signature: String,
735    pub checkpoint_hash: Option<String>,
736    pub attestation_hash: Option<String>,
737    pub evidence_receipt_hashes: Option<Vec<String>>,
738    pub prior_trust_receipt_hash: Option<String>,
739}
740
741/// Council decision request.
742#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
743#[serde(deny_unknown_fields)]
744pub struct DagDbCouncilDecisionRequest {
745    pub tenant_id: String,
746    pub namespace: String,
747    pub idempotency_key: String,
748    pub subject_kind: SubjectKind,
749    pub subject_id: String,
750    pub requested_action: String,
751    pub approved_scope_hash: String,
752    pub risk_class: RiskClass,
753    pub approver_did: String,
754    pub decision_source: DecisionSource,
755    pub decision_status: CouncilDecisionStatus,
756    pub reason_code: String,
757    pub created_at: String,
758    pub expires_at: String,
759    pub validation_report_id: Option<String>,
760    pub route_id: Option<String>,
761    pub context_packet_id: Option<String>,
762    pub notes_text: Option<String>,
763}
764
765/// Receipt lookup request.
766#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
767#[serde(deny_unknown_fields)]
768pub struct DagDbReceiptLookupRequest {
769    pub receipt_hash: String,
770    pub tenant_id: String,
771    pub namespace: String,
772    pub include_body: Option<bool>,
773}
774
775/// Catalog lookup request.
776#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
777#[serde(deny_unknown_fields)]
778pub struct DagDbCatalogLookupRequest {
779    pub catalog_id: String,
780    pub tenant_id: String,
781    pub namespace: String,
782    pub include_children: Option<bool>,
783    pub include_routes: Option<bool>,
784}
785
786/// Route lookup request.
787#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
788#[serde(deny_unknown_fields)]
789pub struct DagDbRouteLookupRequest {
790    pub route_id: String,
791    pub tenant_id: String,
792    pub namespace: String,
793    pub include_memory_refs: Option<bool>,
794    pub include_validation: Option<bool>,
795}
796
797/// Intake response.
798#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
799#[serde(deny_unknown_fields)]
800pub struct DagDbIntakeResponse {
801    /// Stable wire-contract version (`dagdb_intake_response_v1`).
802    pub schema_version: String,
803    pub tenant_id: String,
804    pub namespace: String,
805    pub idempotency_key: String,
806    pub memory_id: String,
807    pub receipt_hash: String,
808    pub validation_status: ValidationStatus,
809    pub council_status: CouncilReviewStatus,
810    pub dag_finality_status: DagFinalityStatus,
811    pub risk_class: RiskClass,
812    pub risk_bp: u16,
813    pub created_new: bool,
814    pub title: SafeMetadata,
815    pub summary: SafeMetadata,
816    pub keywords: Vec<SafeMetadata>,
817    pub validation_report_id: Option<String>,
818    pub council_decision_id: Option<String>,
819    pub duplicate_of_memory_id: Option<String>,
820}
821
822/// Route response.
823#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
824#[serde(deny_unknown_fields)]
825pub struct DagDbRouteResponse {
826    /// Stable wire-contract version (`dagdb_route_response_v1`).
827    pub schema_version: String,
828    pub tenant_id: String,
829    pub namespace: String,
830    pub idempotency_key: String,
831    pub route_id: String,
832    pub receipt_hash: String,
833    pub validation_status: ValidationStatus,
834    pub council_status: CouncilReviewStatus,
835    pub route_status: RouteStatus,
836    pub dag_finality_status: DagFinalityStatus,
837    pub selected_memory_ids: Vec<String>,
838    pub route_score_bp: u16,
839    pub token_budget: u32,
840    pub token_estimate: u32,
841    pub stale_at: String,
842    pub created_new: bool,
843    pub validation_report_id: Option<String>,
844    pub council_decision_id: Option<String>,
845    pub rejected_memory_ids: Option<Vec<String>>,
846}
847
848/// Context packet memory reference.
849#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
850#[serde(deny_unknown_fields)]
851pub struct ContextPacketMemoryRef {
852    pub memory_id: String,
853    pub title: SafeMetadata,
854    pub summary: SafeMetadata,
855    pub keywords: Vec<SafeMetadata>,
856    pub latest_receipt_hash: String,
857}
858
859/// Context packet layered graph reference.
860#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
861#[serde(deny_unknown_fields)]
862pub struct ContextPacketLayerRef {
863    pub layer_id: String,
864    pub layer_path: String,
865    pub layer_depth: u32,
866    pub layer_kind: String,
867    pub selected_ref_count: u32,
868}
869
870/// Context packet layered graph edge reference.
871#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
872#[serde(deny_unknown_fields)]
873pub struct ContextPacketLayerEdgeRef {
874    pub layer_edge_id: String,
875    pub from_layer_id: String,
876    pub to_layer_id: String,
877    pub edge_kind: String,
878}
879
880/// Context packet layered budget report.
881#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
882#[serde(deny_unknown_fields)]
883pub struct ContextPacketLayerBudgetReport {
884    pub layered_mode: String,
885    pub max_layer_depth: u32,
886    pub required_layer_evidence: bool,
887    pub budget_status: String,
888}
889
890/// Context packet response.
891#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
892#[serde(deny_unknown_fields)]
893pub struct DagDbContextPacketResponse {
894    /// Stable wire-contract version (`dagdb_context_packet_response_v1`).
895    pub schema_version: String,
896    pub tenant_id: String,
897    pub namespace: String,
898    pub idempotency_key: String,
899    pub context_packet_id: String,
900    pub route_id: String,
901    pub receipt_hash: String,
902    pub validation_status: ValidationStatus,
903    pub council_status: CouncilReviewStatus,
904    pub dag_finality_status: DagFinalityStatus,
905    pub memory_refs: Vec<ContextPacketMemoryRef>,
906    pub packet_hash: String,
907    pub token_budget: u32,
908    pub token_estimate: u32,
909    pub created_new: bool,
910    pub validation_report_id: Option<String>,
911    pub council_decision_id: Option<String>,
912    #[serde(default, skip_serializing_if = "Option::is_none")]
913    pub context_packet_mode: Option<String>,
914    #[serde(default, skip_serializing_if = "Option::is_none")]
915    pub selection_warning: Option<String>,
916    #[serde(default, skip_serializing_if = "Option::is_none")]
917    pub layered_mode: Option<String>,
918    #[serde(default, skip_serializing_if = "Option::is_none")]
919    pub selected_layers: Option<Vec<ContextPacketLayerRef>>,
920    #[serde(default, skip_serializing_if = "Option::is_none")]
921    pub selected_layer_edges: Option<Vec<ContextPacketLayerEdgeRef>>,
922    #[serde(default, skip_serializing_if = "Option::is_none")]
923    pub layer_budget_report: Option<ContextPacketLayerBudgetReport>,
924    #[serde(default, skip_serializing_if = "Option::is_none")]
925    pub flat_fallback_used: Option<bool>,
926    #[serde(default, skip_serializing_if = "Option::is_none")]
927    pub layered_status: Option<String>,
928    /// Selected graph edges surfaced from the internal context packet. Empty on
929    /// the no-database scaffold path; populated on the persistent (governed) path.
930    #[serde(default, skip_serializing_if = "Vec::is_empty")]
931    pub selected_graph_edges: Vec<DagDbSelectedGraphEdgeRef>,
932    /// Citation references surfaced from the internal context packet. Empty on
933    /// the no-database scaffold path; populated on the persistent (governed) path.
934    #[serde(default, skip_serializing_if = "Vec::is_empty")]
935    pub citation_refs: Vec<DagDbContextPacketCitationRef>,
936    /// Packet metrics surfaced from the internal context packet, when available.
937    #[serde(default, skip_serializing_if = "Option::is_none")]
938    pub packet_metrics: Option<DagDbContextPacketMetrics>,
939    /// Blocked-claim boundaries surfaced from the internal context packet, when available.
940    #[serde(default, skip_serializing_if = "Option::is_none")]
941    pub boundaries: Option<DagDbContextPacketBoundaries>,
942    /// Rendered agent-facing markdown surfaced from the internal context packet, when available.
943    #[serde(default, skip_serializing_if = "Option::is_none")]
944    pub packet_markdown: Option<String>,
945}
946
947/// Validate response.
948#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
949#[serde(deny_unknown_fields)]
950pub struct DagDbValidateResponse {
951    /// Stable wire-contract version (`dagdb_validate_response_v1`).
952    pub schema_version: String,
953    pub tenant_id: String,
954    pub namespace: String,
955    pub idempotency_key: String,
956    pub validation_report_id: String,
957    pub subject_kind: SubjectKind,
958    pub subject_id: String,
959    pub receipt_hash: String,
960    pub validation_status: ValidationStatus,
961    pub council_status: CouncilReviewStatus,
962    pub risk_class: RiskClass,
963    pub risk_bp: u16,
964    pub decision: ValidationDecision,
965    pub created_new: bool,
966    pub council_decision_id: Option<String>,
967    pub contradictory_report_ids: Option<Vec<String>>,
968    pub notes: Option<SafeMetadata>,
969}
970
971/// Writeback response.
972#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
973#[serde(deny_unknown_fields)]
974pub struct DagDbWritebackResponse {
975    /// Stable wire-contract version (`dagdb_writeback_response_v1`).
976    pub schema_version: String,
977    pub tenant_id: String,
978    pub namespace: String,
979    pub idempotency_key: String,
980    pub memory_id: String,
981    pub receipt_hash: String,
982    pub validation_status: ValidationStatus,
983    pub council_status: CouncilReviewStatus,
984    pub dag_finality_status: DagFinalityStatus,
985    pub risk_class: RiskClass,
986    pub risk_bp: u16,
987    pub created_new: bool,
988    pub validation_report_id: Option<String>,
989    pub council_decision_id: Option<String>,
990    pub summary: Option<SafeMetadata>,
991    pub keywords: Option<Vec<SafeMetadata>>,
992    #[serde(default, skip_serializing_if = "Option::is_none")]
993    pub target_layer_path: Option<String>,
994    #[serde(default, skip_serializing_if = "Option::is_none")]
995    pub target_layer_depth: Option<u32>,
996    #[serde(default, skip_serializing_if = "Option::is_none")]
997    pub target_layer_reason: Option<String>,
998    #[serde(default, skip_serializing_if = "Option::is_none")]
999    pub created_child_layer_id: Option<String>,
1000    #[serde(default, skip_serializing_if = "Option::is_none")]
1001    pub layered_writeback_status: Option<String>,
1002}
1003
1004/// Runtime import response.
1005#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1006#[serde(deny_unknown_fields)]
1007pub struct DagDbImportResponse {
1008    /// Stable wire-contract version (`dagdb_import_response_v1`).
1009    pub schema_version: String,
1010    pub operation_id: String,
1011    pub tenant_id: String,
1012    pub namespace: String,
1013    pub idempotency_key: String,
1014    pub db_set_version: String,
1015    pub import_status: String,
1016    pub import_receipt_id: Option<String>,
1017    pub source_hash: String,
1018    pub imported_record_count: u32,
1019    pub receipt_path: Option<String>,
1020    pub non_claims: Vec<String>,
1021    #[serde(default, skip_serializing_if = "Option::is_none")]
1022    pub idempotency_status: Option<String>,
1023}
1024
1025/// Runtime export response.
1026#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1027#[serde(deny_unknown_fields)]
1028pub struct DagDbExportResponse {
1029    /// Stable wire-contract version (`dagdb_export_response_v1`).
1030    pub schema_version: String,
1031    pub operation_id: String,
1032    pub tenant_id: String,
1033    pub namespace: String,
1034    pub idempotency_key: String,
1035    pub db_set_version: String,
1036    pub export_status: String,
1037    pub export_artifact_id: Option<String>,
1038    pub export_hash: Option<String>,
1039    pub exported_record_count: u32,
1040    pub report_path: Option<String>,
1041    pub non_claims: Vec<String>,
1042    #[serde(default, skip_serializing_if = "Option::is_none")]
1043    pub idempotency_status: Option<String>,
1044}
1045
1046/// Trust-check response.
1047#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1048#[serde(deny_unknown_fields)]
1049pub struct DagDbTrustCheckResponse {
1050    /// Stable wire-contract version (`dagdb_trust_check_response_v1`).
1051    pub schema_version: String,
1052    pub tenant_id: String,
1053    pub namespace: String,
1054    pub idempotency_key: String,
1055    pub credential_id: String,
1056    pub safety_score_id: String,
1057    pub receipt_hash: String,
1058    pub validation_status: ValidationStatus,
1059    pub council_status: CouncilReviewStatus,
1060    pub credential_status: CredentialStatus,
1061    pub total_score_bp: u16,
1062    pub created_new: bool,
1063    pub block_reason: Option<String>,
1064    pub expires_at: Option<String>,
1065}
1066
1067/// Council decision response.
1068#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1069#[serde(deny_unknown_fields)]
1070pub struct DagDbCouncilDecisionResponse {
1071    /// Stable wire-contract version (`dagdb_council_decision_response_v1`).
1072    pub schema_version: String,
1073    pub tenant_id: String,
1074    pub namespace: String,
1075    pub idempotency_key: String,
1076    pub decision_id: String,
1077    pub subject_kind: SubjectKind,
1078    pub subject_id: String,
1079    pub receipt_hash: String,
1080    pub validation_status: ValidationStatus,
1081    pub council_status: CouncilReviewStatus,
1082    pub decision_status: CouncilDecisionStatus,
1083    pub approved_scope_hash: String,
1084    pub risk_class: RiskClass,
1085    pub expires_at: String,
1086    pub created_new: bool,
1087    pub validation_report_id: Option<String>,
1088    pub route_id: Option<String>,
1089    pub context_packet_id: Option<String>,
1090    pub notes: Option<SafeMetadata>,
1091}
1092
1093/// Receipt lookup response.
1094#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1095#[serde(deny_unknown_fields)]
1096pub struct DagDbReceiptLookupResponse {
1097    /// Stable wire-contract version (`dagdb_receipt_lookup_response_v1`).
1098    pub schema_version: String,
1099    pub tenant_id: String,
1100    pub namespace: String,
1101    pub receipt_hash: String,
1102    pub subject_kind: SubjectKind,
1103    pub subject_id: String,
1104    pub prev_receipt_hash: String,
1105    pub seq: u64,
1106    pub event_type: ReceiptEventType,
1107    pub actor_did: String,
1108    pub event_hlc: String,
1109    pub created_at: String,
1110    pub receipt_body: Option<serde_json::Value>,
1111    pub validation_report_id: Option<String>,
1112}
1113
1114/// Child catalog entry returned by lookup responses.
1115#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1116#[serde(deny_unknown_fields)]
1117pub struct CatalogEntryResponse {
1118    pub catalog_id: String,
1119    pub title: SafeMetadata,
1120    pub summary: SafeMetadata,
1121}
1122
1123/// Catalog lookup response.
1124#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1125#[serde(deny_unknown_fields)]
1126pub struct DagDbCatalogLookupResponse {
1127    /// Stable wire-contract version (`dagdb_catalog_lookup_response_v1`).
1128    pub schema_version: String,
1129    pub tenant_id: String,
1130    pub namespace: String,
1131    pub catalog_id: String,
1132    pub catalog_level: u32,
1133    pub title: SafeMetadata,
1134    pub summary: SafeMetadata,
1135    pub keywords: Vec<SafeMetadata>,
1136    pub status: MemoryStatus,
1137    pub validation_status: ValidationStatus,
1138    pub council_status: CouncilReviewStatus,
1139    pub dag_finality_status: DagFinalityStatus,
1140    pub latest_receipt_hash: String,
1141    pub memory_id: Option<String>,
1142    pub parent_catalog_id: Option<String>,
1143    pub children: Option<Vec<CatalogEntryResponse>>,
1144    pub routes: Option<Vec<String>>,
1145}
1146
1147/// Graph context selection status.
1148#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
1149#[serde(rename_all = "snake_case")]
1150pub enum DagDbGraphContextSelectionStatus {
1151    Selected,
1152    Empty,
1153    Failed,
1154}
1155
1156/// Graph context selection request.
1157#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1158#[serde(deny_unknown_fields)]
1159pub struct DagDbGraphContextSelectionRequest {
1160    pub tenant_id: String,
1161    pub namespace: String,
1162    pub request_id: String,
1163    pub task: String,
1164    pub task_hash: String,
1165    pub token_budget: u32,
1166    pub max_memory_refs: u32,
1167    pub catalog_hints: Vec<String>,
1168    pub requested_memory_ids: Vec<String>,
1169    pub force_revalidate: bool,
1170}
1171
1172/// Selected memory reference returned by graph context selection.
1173#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1174#[serde(deny_unknown_fields)]
1175pub struct DagDbSelectedContextRef {
1176    pub memory_id: String,
1177    pub catalog_id: Option<String>,
1178    pub title: SafeMetadata,
1179    pub summary: SafeMetadata,
1180    pub catalog_path: Vec<String>,
1181    pub document_type: String,
1182    pub selection_reason: String,
1183    pub token_estimate: u32,
1184    pub validation_status: ValidationStatus,
1185    pub citation_ref: String,
1186    pub boundary_flags: Vec<String>,
1187}
1188
1189/// Selected graph edge reference returned by graph context selection.
1190#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1191#[serde(deny_unknown_fields)]
1192pub struct DagDbSelectedGraphEdgeRef {
1193    pub graph_edge_id: String,
1194    pub from_memory_id: String,
1195    pub to_memory_id: String,
1196    pub edge_kind: MemoryEdgeKind,
1197    pub graph_style: MemoryGraphStyle,
1198    pub selection_reason: String,
1199}
1200
1201/// Omitted memory reference returned by graph context selection.
1202#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1203#[serde(deny_unknown_fields)]
1204pub struct DagDbOmittedContextRef {
1205    pub memory_id: String,
1206    pub omission_reason: String,
1207    pub token_estimate_if_selected: u32,
1208}
1209
1210/// Route explanation step for graph context selection.
1211#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1212#[serde(deny_unknown_fields)]
1213pub struct DagDbGraphSelectionTraceStep {
1214    pub graph_style: MemoryGraphStyle,
1215    pub candidate_count_before: u32,
1216    pub candidate_count_after: u32,
1217    pub selected_count_after: u32,
1218    pub reason: String,
1219}
1220
1221/// Graph context selection response.
1222#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1223#[serde(deny_unknown_fields)]
1224pub struct DagDbGraphContextSelectionResponse {
1225    pub tenant_id: String,
1226    pub namespace: String,
1227    pub request_id: String,
1228    pub task_hash: String,
1229    pub selection_status: DagDbGraphContextSelectionStatus,
1230    pub selected_memory_refs: Vec<DagDbSelectedContextRef>,
1231    pub selected_graph_edges: Vec<DagDbSelectedGraphEdgeRef>,
1232    pub omitted_memory_refs: Vec<DagDbOmittedContextRef>,
1233    pub selection_trace: Vec<DagDbGraphSelectionTraceStep>,
1234    pub selected_token_estimate: u32,
1235    pub token_budget: u32,
1236    pub boundary_warnings: Vec<String>,
1237}
1238
1239/// Graph context packet build request.
1240#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1241#[serde(deny_unknown_fields)]
1242pub struct DagDbGraphContextPacketBuildRequest {
1243    pub tenant_id: String,
1244    pub namespace: String,
1245    pub request_id: String,
1246    pub task: String,
1247    pub task_hash: String,
1248    pub audit_id: String,
1249    pub token_budget: u32,
1250    pub selection: DagDbGraphContextSelectionResponse,
1251    pub import_tracking_status: Option<DagDbContextPacketImportTrackingStatus>,
1252}
1253
1254/// Bounded graph context packet emitted by the Rust packet builder.
1255#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1256#[serde(deny_unknown_fields)]
1257pub struct DagDbGraphContextPacket {
1258    pub schema_version: String,
1259    pub tenant_id: String,
1260    pub namespace: String,
1261    pub request_id: String,
1262    pub task: String,
1263    pub task_hash: String,
1264    pub packet_hash: String,
1265    pub selected_memory_refs: Vec<DagDbSelectedContextRef>,
1266    pub selected_graph_edges: Vec<DagDbSelectedGraphEdgeRef>,
1267    pub citation_refs: Vec<DagDbContextPacketCitationRef>,
1268    pub packet_metrics: DagDbContextPacketMetrics,
1269    pub boundaries: DagDbContextPacketBoundaries,
1270    pub agent_usage_instructions: Vec<String>,
1271    pub markdown: String,
1272}
1273
1274/// Citation reference included in a graph context packet.
1275#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1276#[serde(deny_unknown_fields)]
1277pub struct DagDbContextPacketCitationRef {
1278    pub citation_ref: String,
1279    pub memory_id: String,
1280    pub citation_status: String,
1281}
1282
1283/// Metrics recorded for a graph context packet.
1284#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1285#[serde(deny_unknown_fields)]
1286pub struct DagDbContextPacketMetrics {
1287    pub token_budget: u32,
1288    pub selected_token_estimate: u32,
1289    pub selected_memory_ref_count: u32,
1290    pub selected_graph_edge_count: u32,
1291    pub citation_ref_count: u32,
1292    pub end_to_end_savings_status: String,
1293    pub cost_savings_status: String,
1294}
1295
1296/// Blocked claim boundaries for a graph context packet.
1297#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1298#[serde(deny_unknown_fields)]
1299pub struct DagDbContextPacketBoundaries {
1300    pub repository_test_level_only: bool,
1301    pub production_runtime: String,
1302    pub default_context_replacement: String,
1303    pub citation_locator_status: String,
1304    pub billing_savings: String,
1305}
1306
1307/// Import-tracking summary status attached to a graph context packet.
1308#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1309#[serde(deny_unknown_fields)]
1310pub struct DagDbContextPacketImportTrackingStatus {
1311    pub manifest_json: String,
1312    pub manifest_status: String,
1313    pub tracked_clean_evidence_enforced: bool,
1314    pub source_path_status: String,
1315}
1316
1317/// Route lookup response.
1318#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1319#[serde(deny_unknown_fields)]
1320pub struct DagDbRouteLookupResponse {
1321    /// Stable wire-contract version (`dagdb_route_lookup_response_v1`).
1322    pub schema_version: String,
1323    pub tenant_id: String,
1324    pub namespace: String,
1325    pub route_id: String,
1326    pub route_status: RouteStatus,
1327    pub validation_status: ValidationStatus,
1328    pub council_status: CouncilReviewStatus,
1329    pub dag_finality_status: DagFinalityStatus,
1330    pub selected_memory_ids: Vec<String>,
1331    pub route_score_bp: u16,
1332    pub token_budget: u32,
1333    pub token_estimate: u32,
1334    pub stale_at: String,
1335    pub latest_receipt_hash: String,
1336    pub memory_refs: Option<Vec<ContextPacketMemoryRef>>,
1337    pub validation_report: Option<DagDbValidateResponse>,
1338}
1339
1340#[cfg(test)]
1341mod tests {
1342    use serde::{Serialize, de::DeserializeOwned};
1343
1344    use super::*;
1345
1346    #[test]
1347    fn dagdb_json_fixtures() {
1348        let fixtures: serde_json::Value =
1349            serde_json::from_str(include_str!("../fixtures/json/all_dto_fixtures.json"))
1350                .expect("parse complete DAG DB fixture set");
1351
1352        assert_fixture::<DagDbIntakeRequest>(&fixtures, "requests", "intake");
1353        assert_fixture::<DagDbRouteRequest>(&fixtures, "requests", "route");
1354        assert_fixture::<DagDbContextPacketRequest>(&fixtures, "requests", "context_packet");
1355        assert_fixture::<DagDbValidateRequest>(&fixtures, "requests", "validate");
1356        assert_fixture::<DagDbWritebackRequest>(&fixtures, "requests", "writeback");
1357        assert_fixture::<DagDbTrustCheckRequest>(&fixtures, "requests", "trust_check");
1358        assert_fixture::<DagDbCouncilDecisionRequest>(&fixtures, "requests", "council_decision");
1359        assert_fixture::<DagDbReceiptLookupRequest>(&fixtures, "requests", "receipt_lookup");
1360        assert_fixture::<DagDbCatalogLookupRequest>(&fixtures, "requests", "catalog_lookup");
1361        assert_fixture::<DagDbRouteLookupRequest>(&fixtures, "requests", "route_lookup");
1362
1363        assert_fixture::<DagDbIntakeResponse>(&fixtures, "responses", "intake");
1364        assert_fixture::<DagDbRouteResponse>(&fixtures, "responses", "route");
1365        assert_fixture::<DagDbContextPacketResponse>(&fixtures, "responses", "context_packet");
1366        assert_fixture::<DagDbValidateResponse>(&fixtures, "responses", "validate");
1367        assert_fixture::<DagDbWritebackResponse>(&fixtures, "responses", "writeback");
1368        assert_fixture::<DagDbImportResponse>(&fixtures, "responses", "import");
1369        assert_fixture::<DagDbExportResponse>(&fixtures, "responses", "export");
1370        assert_fixture::<DagDbTrustCheckResponse>(&fixtures, "responses", "trust_check");
1371        assert_fixture::<DagDbCouncilDecisionResponse>(&fixtures, "responses", "council_decision");
1372        assert_fixture::<DagDbReceiptLookupResponse>(&fixtures, "responses", "receipt_lookup");
1373        assert_fixture::<DagDbCatalogLookupResponse>(&fixtures, "responses", "catalog_lookup");
1374        assert_fixture::<DagDbRouteLookupResponse>(&fixtures, "responses", "route_lookup");
1375
1376        assert_fixture::<DagDbErrorEnvelope>(&fixtures, "errors", "tenant_scope_mismatch");
1377    }
1378
1379    #[test]
1380    fn receipt_event_type_serializes_operational_audit_categories() {
1381        for (event_type, expected) in [
1382            (
1383                ReceiptEventType::DagdbApprovalRequestSubmitted,
1384                "dagdb_approval_request_submitted",
1385            ),
1386            (
1387                ReceiptEventType::DagdbApprovalGranted,
1388                "dagdb_approval_granted",
1389            ),
1390            (
1391                ReceiptEventType::DagdbApprovalDenied,
1392                "dagdb_approval_denied",
1393            ),
1394            (
1395                ReceiptEventType::DagdbRecordAccepted,
1396                "dagdb_record_accepted",
1397            ),
1398            (
1399                ReceiptEventType::DagdbImportCompleted,
1400                "dagdb_import_completed",
1401            ),
1402            (
1403                ReceiptEventType::DagdbExportCompleted,
1404                "dagdb_export_completed",
1405            ),
1406            (
1407                ReceiptEventType::DagdbReplayDetected,
1408                "dagdb_replay_detected",
1409            ),
1410            (
1411                ReceiptEventType::DagdbIdempotencyConflict,
1412                "dagdb_idempotency_conflict",
1413            ),
1414            (
1415                ReceiptEventType::DagdbRlsTenantViolation,
1416                "dagdb_rls_tenant_violation",
1417            ),
1418            (
1419                ReceiptEventType::DagdbSignatureFailure,
1420                "dagdb_signature_failure",
1421            ),
1422            (
1423                ReceiptEventType::DagdbCouncilOperatorDecision,
1424                "dagdb_council_operator_decision",
1425            ),
1426        ] {
1427            assert_eq!(
1428                serde_json::to_value(event_type).expect("serialize event"),
1429                serde_json::json!(expected)
1430            );
1431        }
1432    }
1433
1434    #[test]
1435    fn dagdb_runtime_import_export_dtos_deny_unknown_fields() {
1436        let import_request = DagDbImportRequest {
1437            tenant_id: "tenant-a".into(),
1438            namespace: "primary".into(),
1439            idempotency_key: "idem-import-1".into(),
1440            db_set_version: "dag_db-project_memory_v3".into(),
1441            source_hash: "1111111111111111111111111111111111111111111111111111111111111111".into(),
1442            requester_did: "did:exo:importer".into(),
1443            import_report: serde_json::json!({
1444                "schema_version": "dagdb_kg_dry_run_import_report_v1",
1445                "tenant_id": "tenant-a",
1446                "namespace": "primary"
1447            }),
1448        };
1449        let parsed: DagDbImportRequest = serde_json::from_value(
1450            serde_json::to_value(&import_request).expect("serialize import request"),
1451        )
1452        .expect("deserialize import request");
1453        assert_eq!(parsed, import_request);
1454        let import_err = serde_json::from_str::<DagDbImportRequest>(
1455            r#"{
1456              "tenant_id": "tenant-a",
1457              "namespace": "primary",
1458              "idempotency_key": "idem-import-1",
1459              "db_set_version": "dag_db-project_memory_v3",
1460              "source_hash": "1111111111111111111111111111111111111111111111111111111111111111",
1461              "requester_did": "did:exo:importer",
1462              "import_report": {},
1463              "raw_source_body": "forbidden"
1464            }"#,
1465        )
1466        .expect_err("unknown import request field must fail");
1467        assert!(import_err.to_string().contains("unknown field"));
1468
1469        let export_request = DagDbExportRequest {
1470            tenant_id: "tenant-a".into(),
1471            namespace: "primary".into(),
1472            idempotency_key: "idem-export-1".into(),
1473            db_set_version: "dag_db-project_memory_v3".into(),
1474            requester_did: "did:exo:exporter".into(),
1475            included_memory_ids: vec![
1476                "2222222222222222222222222222222222222222222222222222222222222222".into(),
1477            ],
1478            included_graph_styles: vec!["semantic_catalog_graph".into()],
1479            included_writeback_idempotency_keys: Vec::new(),
1480            source_commit_or_repo_ref: Some("c706242d36f1c275e05d8a132778491da08f61c7".into()),
1481            include_preview_context: false,
1482        };
1483        let parsed: DagDbExportRequest = serde_json::from_value(
1484            serde_json::to_value(&export_request).expect("serialize export request"),
1485        )
1486        .expect("deserialize export request");
1487        assert_eq!(parsed, export_request);
1488        let export_err = serde_json::from_str::<DagDbExportRequest>(
1489            r#"{
1490              "tenant_id": "tenant-a",
1491              "namespace": "primary",
1492              "idempotency_key": "idem-export-1",
1493              "db_set_version": "dag_db-project_memory_v3",
1494              "requester_did": "did:exo:exporter",
1495              "included_memory_ids": [],
1496              "included_graph_styles": [],
1497              "included_writeback_idempotency_keys": [],
1498              "source_commit_or_repo_ref": null,
1499              "include_preview_context": false,
1500              "receipt_path": "/Users/example/private"
1501            }"#,
1502        )
1503        .expect_err("unknown export request field must fail");
1504        assert!(export_err.to_string().contains("unknown field"));
1505    }
1506
1507    #[test]
1508    fn dagdb_graph_json_fixtures() {
1509        let fixtures: serde_json::Value =
1510            serde_json::from_str(include_str!("../fixtures/json/all_dto_fixtures.json"))
1511                .expect("parse complete DAG DB fixture set");
1512
1513        assert_fixture::<MemoryCandidate>(&fixtures, "graph", "memory_candidate");
1514        assert_fixture::<SimilarityResult>(&fixtures, "graph", "similarity_result");
1515        assert_fixture::<CanonicalizationDecision>(&fixtures, "graph", "canonicalization_decision");
1516        assert_fixture::<GraphView>(&fixtures, "graph", "graph_view");
1517        assert_fixture::<RouteInvalidationReceipt>(
1518            &fixtures,
1519            "graph",
1520            "route_invalidation_receipt",
1521        );
1522        assert_fixture::<PlacementResult>(&fixtures, "graph", "placement_result");
1523
1524        let all_styles = [
1525            MemoryGraphStyle::ProvenanceReceiptDag,
1526            MemoryGraphStyle::CanonicalMemoryGraph,
1527            MemoryGraphStyle::SemanticCatalogGraph,
1528            MemoryGraphStyle::SimilarityOverlayGraph,
1529            MemoryGraphStyle::DependencyDag,
1530            MemoryGraphStyle::RoutingViewGraph,
1531            MemoryGraphStyle::ContradictionSupersessionGraph,
1532            MemoryGraphStyle::ContextPacketGraph,
1533        ];
1534        let encoded = serde_json::to_value(all_styles).expect("serialize graph styles");
1535        assert_eq!(
1536            encoded,
1537            serde_json::json!([
1538                "provenance_receipt_dag",
1539                "canonical_memory_graph",
1540                "semantic_catalog_graph",
1541                "similarity_overlay_graph",
1542                "dependency_dag",
1543                "routing_view_graph",
1544                "contradiction_supersession_graph",
1545                "context_packet_graph"
1546            ])
1547        );
1548    }
1549
1550    #[test]
1551    fn dagdb_graph_context_selection_dtos_deny_unknown_fields() {
1552        let request = DagDbGraphContextSelectionRequest {
1553            tenant_id: "tenant-a".into(),
1554            namespace: "primary".into(),
1555            request_id: "req-1".into(),
1556            task: "Select next implementation step".into(),
1557            task_hash: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".into(),
1558            token_budget: 1_000,
1559            max_memory_refs: 4,
1560            catalog_hints: vec!["04_Plans".into()],
1561            requested_memory_ids: Vec::new(),
1562            force_revalidate: false,
1563        };
1564        let serialized = serde_json::to_value(&request).expect("serialize request");
1565        let parsed: DagDbGraphContextSelectionRequest =
1566            serde_json::from_value(serialized).expect("deserialize request");
1567        assert_eq!(parsed, request);
1568
1569        let forged = r#"{
1570          "tenant_id": "tenant-a",
1571          "namespace": "primary",
1572          "request_id": "req-1",
1573          "task": "Select next implementation step",
1574          "task_hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
1575          "token_budget": 1000,
1576          "max_memory_refs": 4,
1577          "catalog_hints": [],
1578          "requested_memory_ids": [],
1579          "force_revalidate": false,
1580          "raw_markdown": "forbidden"
1581        }"#;
1582        let err = serde_json::from_str::<DagDbGraphContextSelectionRequest>(forged)
1583            .expect_err("unknown request field must fail");
1584        assert!(err.to_string().contains("unknown field"));
1585
1586        let response = DagDbGraphContextSelectionResponse {
1587            tenant_id: "tenant-a".into(),
1588            namespace: "primary".into(),
1589            request_id: "req-1".into(),
1590            task_hash: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".into(),
1591            selection_status: DagDbGraphContextSelectionStatus::Selected,
1592            selected_memory_refs: vec![DagDbSelectedContextRef {
1593                memory_id: "mem-plan".into(),
1594                catalog_id: Some("catalog-plan".into()),
1595                title: SafeMetadata {
1596                    decision: SafeMetadataDecision::Allow,
1597                    text: "Plan".into(),
1598                    redaction_codes: Vec::new(),
1599                    original_hash:
1600                        "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb".into(),
1601                    truncated: false,
1602                    byte_len: 4,
1603                },
1604                summary: SafeMetadata {
1605                    decision: SafeMetadataDecision::Allow,
1606                    text: "Safe summary".into(),
1607                    redaction_codes: Vec::new(),
1608                    original_hash:
1609                        "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc".into(),
1610                    truncated: false,
1611                    byte_len: 12,
1612                },
1613                catalog_path: vec!["04_Plans".into(), "Next Steps".into()],
1614                document_type: "plan".into(),
1615                selection_reason: "task_term_match".into(),
1616                token_estimate: 120,
1617                validation_status: ValidationStatus::Passed,
1618                citation_ref: "citation:plan".into(),
1619                boundary_flags: vec!["repository_test_only".into()],
1620            }],
1621            selected_graph_edges: Vec::new(),
1622            omitted_memory_refs: Vec::new(),
1623            selection_trace: vec![DagDbGraphSelectionTraceStep {
1624                graph_style: MemoryGraphStyle::SemanticCatalogGraph,
1625                candidate_count_before: 1,
1626                candidate_count_after: 1,
1627                selected_count_after: 1,
1628                reason: "semantic_catalog_graph_considered".into(),
1629            }],
1630            selected_token_estimate: 120,
1631            token_budget: 1_000,
1632            boundary_warnings: vec!["production_runtime_not_approved".into()],
1633        };
1634        let serialized = serde_json::to_value(&response).expect("serialize response");
1635        let parsed: DagDbGraphContextSelectionResponse =
1636            serde_json::from_value(serialized).expect("deserialize response");
1637        assert_eq!(parsed, response);
1638    }
1639
1640    #[test]
1641    fn dagdb_graph_context_packet_dtos_deny_unknown_fields() {
1642        let selection = DagDbGraphContextSelectionResponse {
1643            tenant_id: "tenant-a".into(),
1644            namespace: "primary".into(),
1645            request_id: "req-1".into(),
1646            task_hash: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".into(),
1647            selection_status: DagDbGraphContextSelectionStatus::Selected,
1648            selected_memory_refs: Vec::new(),
1649            selected_graph_edges: Vec::new(),
1650            omitted_memory_refs: Vec::new(),
1651            selection_trace: Vec::new(),
1652            selected_token_estimate: 0,
1653            token_budget: 1_000,
1654            boundary_warnings: vec!["production_runtime_not_approved".into()],
1655        };
1656        let request = DagDbGraphContextPacketBuildRequest {
1657            tenant_id: "tenant-a".into(),
1658            namespace: "primary".into(),
1659            request_id: "req-1".into(),
1660            task: "Build bounded context packet".into(),
1661            task_hash: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".into(),
1662            audit_id: "audit-1".into(),
1663            token_budget: 1_000,
1664            selection,
1665            import_tracking_status: None,
1666        };
1667        let serialized = serde_json::to_value(&request).expect("serialize request");
1668        let parsed: DagDbGraphContextPacketBuildRequest =
1669            serde_json::from_value(serialized).expect("deserialize request");
1670        assert_eq!(parsed, request);
1671
1672        let forged = r#"{
1673          "tenant_id": "tenant-a",
1674          "namespace": "primary",
1675          "request_id": "req-1",
1676          "task": "Build bounded context packet",
1677          "task_hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
1678          "audit_id": "audit-1",
1679          "token_budget": 1000,
1680          "selection": {
1681            "tenant_id": "tenant-a",
1682            "namespace": "primary",
1683            "request_id": "req-1",
1684            "task_hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
1685            "selection_status": "empty",
1686            "selected_memory_refs": [],
1687            "selected_graph_edges": [],
1688            "omitted_memory_refs": [],
1689            "selection_trace": [],
1690            "selected_token_estimate": 0,
1691            "token_budget": 1000,
1692            "boundary_warnings": []
1693          },
1694          "import_tracking_status": null,
1695          "raw_markdown": "forbidden"
1696        }"#;
1697        let err = serde_json::from_str::<DagDbGraphContextPacketBuildRequest>(forged)
1698            .expect_err("unknown request field must fail");
1699        assert!(err.to_string().contains("unknown field"));
1700    }
1701
1702    #[test]
1703    fn dagdb_graph_context_selection_status_serializes_all_variants() {
1704        let variants = [
1705            DagDbGraphContextSelectionStatus::Selected,
1706            DagDbGraphContextSelectionStatus::Empty,
1707            DagDbGraphContextSelectionStatus::Failed,
1708        ];
1709        let encoded = serde_json::to_value(variants).expect("serialize statuses");
1710        assert_eq!(encoded, serde_json::json!(["selected", "empty", "failed"]));
1711    }
1712
1713    #[test]
1714    fn dagdb_rejects_forged_safe_metadata() {
1715        let forged = r#"{
1716          "tenant_id": "tenant-a",
1717          "namespace": "primary",
1718          "idempotency_key": "idem-1",
1719          "source_type": "public_web",
1720          "source_hash": "1111111111111111111111111111111111111111111111111111111111111111",
1721          "payload_hash": "2222222222222222222222222222222222222222222222222222222222222222",
1722          "owner_did": "did:exo:owner",
1723          "controller_did": "did:exo:controller",
1724          "submitted_by_did": "did:exo:submitter",
1725          "consent_purpose": "retrieval",
1726          "requested_action": "memory:intake",
1727          "title_text": "Safe public title",
1728          "summary_text": "Safe public summary",
1729          "title": {
1730            "decision": "allow",
1731            "text": "forged trusted value",
1732            "redaction_codes": [],
1733            "original_hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
1734            "truncated": false,
1735            "byte_len": 20
1736          }
1737        }"#;
1738        let err = serde_json::from_str::<DagDbIntakeRequest>(forged)
1739            .expect_err("unknown trusted SafeMetadata field must fail");
1740        assert!(err.to_string().contains("unknown field"));
1741    }
1742
1743    fn assert_fixture<T>(fixtures: &serde_json::Value, section: &str, name: &str)
1744    where
1745        T: DeserializeOwned + Serialize,
1746    {
1747        let fixture = fixtures
1748            .get(section)
1749            .and_then(|section| section.get(name))
1750            .unwrap_or_else(|| panic!("missing fixture {section}.{name}"));
1751        let parsed: T = serde_json::from_value(fixture.clone())
1752            .unwrap_or_else(|err| panic!("parse fixture {section}.{name}: {err}"));
1753        let serialized = serde_json::to_value(parsed)
1754            .unwrap_or_else(|err| panic!("serialize fixture {section}.{name}: {err}"));
1755        assert_eq!(serialized, *fixture, "fixture {section}.{name} drifted");
1756    }
1757}