openlatch-client 0.1.13

The open-source security layer for AI agents — client forwarder
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
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
// AUTO-GENERATED by build.rs from schemas/*.schema.json
// DO NOT EDIT — changes will be overwritten on next build.
// To modify types, edit the JSON Schema files in schemas/ and rebuild.

/// Error types.
pub mod error {
    /// Error from a `TryFrom` or `FromStr` implementation.
    pub struct ConversionError(::std::borrow::Cow<'static, str>);
    impl ::std::error::Error for ConversionError {}
    impl ::std::fmt::Display for ConversionError {
        fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> {
            ::std::fmt::Display::fmt(&self.0, f)
        }
    }
    impl ::std::fmt::Debug for ConversionError {
        fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> Result<(), ::std::fmt::Error> {
            ::std::fmt::Debug::fmt(&self.0, f)
        }
    }
    impl From<&'static str> for ConversionError {
        fn from(value: &'static str) -> Self {
            Self(value.into())
        }
    }
    impl From<String> for ConversionError {
        fn from(value: String) -> Self {
            Self(value.into())
        }
    }
}
///Response from GET /api/v1/users/me for auth status validation (D-19) and PostHog identity stitching (telemetry Phase C). user_db_id, when present, is used as the persistent distinct_id and triggers a one-time $create_alias merging prior agent_id events into the platform person. Mirrors the `id` field on the platform side; both carry the stable better-auth user TEXT primary key.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
///  "description": "Response from GET /api/v1/users/me for auth status validation (D-19) and PostHog identity stitching (telemetry Phase C). user_db_id, when present, is used as the persistent distinct_id and triggers a one-time $create_alias merging prior agent_id events into the platform person. Mirrors the `id` field on the platform side; both carry the stable better-auth user TEXT primary key.",
///  "examples": [
///    {
///      "email": "alice@example.com",
///      "id": "usr_019d8af1-f8da-73b3-92eb-79a99e59b10b",
///      "organization_id": "a1b2c3d4-e5f6-4000-a000-000000000001",
///      "organization_name": "Acme Corp",
///      "user_db_id": "usr_019d8af1-f8da-73b3-92eb-79a99e59b10b"
///    }
///  ],
///  "type": "object",
///  "properties": {
///    "email": {
///      "description": "Email of the authenticated user.",
///      "type": "string"
///    },
///    "id": {
///      "description": "Stable database identifier for the authenticated user (better-auth user.id). Mirror of user_db_id — either field may be read; prefer user_db_id for telemetry alias semantics.",
///      "type": "string"
///    },
///    "organization_id": {
///      "description": "Organization id for the user's active organization.",
///      "type": "string"
///    },
///    "organization_name": {
///      "description": "Human-readable display name of the user's active organization. Surfaced by the client on re-runs of `openlatch init` so the user can confirm which org their cached credential belongs to. Optional — when absent, the client displays `Authenticated` without the parenthetical org suffix.",
///      "type": "string"
///    },
///    "user_db_id": {
///      "description": "Stable database identifier for the authenticated user. Used as the PostHog distinct_id post-auth and as the alias target for $create_alias. Optional in this client schema for backwards compatibility — older platforms may not return it; client falls back to agent_id and skips the alias when absent.",
///      "type": "string"
///    }
///  },
///  "additionalProperties": true,
///  "x-postgresql-skip": true
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug, PartialEq)]
pub struct AuthMeResponse {
    ///Email of the authenticated user.
    #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
    pub email: ::std::option::Option<::std::string::String>,
    ///Stable database identifier for the authenticated user (better-auth user.id). Mirror of user_db_id — either field may be read; prefer user_db_id for telemetry alias semantics.
    #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
    pub id: ::std::option::Option<::std::string::String>,
    ///Organization id for the user's active organization.
    #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
    pub organization_id: ::std::option::Option<::std::string::String>,
    ///Human-readable display name of the user's active organization. Surfaced by the client on re-runs of `openlatch init` so the user can confirm which org their cached credential belongs to. Optional — when absent, the client displays `Authenticated` without the parenthetical org suffix.
    #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
    pub organization_name: ::std::option::Option<::std::string::String>,
    ///Stable database identifier for the authenticated user. Used as the PostHog distinct_id post-auth and as the alias target for $create_alias. Optional in this client schema for backwards compatibility — older platforms may not return it; client falls back to agent_id and skips the alias when absent.
    #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
    pub user_db_id: ::std::option::Option<::std::string::String>,
}
impl ::std::default::Default for AuthMeResponse {
    fn default() -> Self {
        Self {
            email: Default::default(),
            id: Default::default(),
            organization_id: Default::default(),
            organization_name: Default::default(),
            user_db_id: Default::default(),
        }
    }
}
///HTTP request body for POST /api/v1/events/ingest sent by openlatch-client. CloudEvents v1.0.2 batch mode — a bare JSON array of EventEnvelope objects, sent with Content-Type: application/cloudevents-batch+json. Client-wide metadata (schema_version, agent_id) is carried on each CloudEvent via extension attributes rather than a wrapper object.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
///  "description": "HTTP request body for POST /api/v1/events/ingest sent by openlatch-client. CloudEvents v1.0.2 batch mode — a bare JSON array of EventEnvelope objects, sent with Content-Type: application/cloudevents-batch+json. Client-wide metadata (schema_version, agent_id) is carried on each CloudEvent via extension attributes rather than a wrapper object.",
///  "examples": [
///    [
///      {
///        "arch": "x86_64",
///        "clientversion": "0.2.0",
///        "data": {
///          "tool_input": {
///            "command": "ls -la"
///          },
///          "tool_name": "Bash"
///        },
///        "datacontenttype": "application/json",
///        "id": "evt_019d8af1-f8da-73b3-92eb-79a99e59b10b",
///        "os": "linux",
///        "source": "claude-code",
///        "specversion": "1.0",
///        "subject": "sess_abc123",
///        "time": "2026-04-16T12:00:00Z",
///        "type": "pre_tool_use"
///      }
///    ]
///  ],
///  "type": "array",
///  "items": {
///    "$ref": "#/$defs/EventEnvelope"
///  },
///  "maxItems": 100,
///  "minItems": 1,
///  "x-postgresql-skip": true
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug, PartialEq)]
#[serde(transparent)]
pub struct CloudIngestionRequest(pub ::std::vec::Vec<EventEnvelope>);
impl ::std::ops::Deref for CloudIngestionRequest {
    type Target = ::std::vec::Vec<EventEnvelope>;
    fn deref(&self) -> &::std::vec::Vec<EventEnvelope> {
        &self.0
    }
}
impl ::std::convert::From<CloudIngestionRequest> for ::std::vec::Vec<EventEnvelope> {
    fn from(value: CloudIngestionRequest) -> Self {
        value.0
    }
}
impl ::std::convert::From<::std::vec::Vec<EventEnvelope>> for CloudIngestionRequest {
    fn from(value: ::std::vec::Vec<EventEnvelope>) -> Self {
        Self(value)
    }
}
///HTTP response body for POST /api/v1/events/ingest returned to openlatch-client. The client uses status to determine whether to retry, and event_id to correlate verdicts.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
///  "description": "HTTP response body for POST /api/v1/events/ingest returned to openlatch-client. The client uses status to determine whether to retry, and event_id to correlate verdicts.",
///  "examples": [
///    {
///      "event_id": "019d8af1-f8da-73b3-92eb-79a99e59b10b",
///      "status": "accepted"
///    },
///    {
///      "error": "Envelope failed schema validation: missing required attribute 'specversion'",
///      "status": "rejected"
///    }
///  ],
///  "type": "object",
///  "required": [
///    "status"
///  ],
///  "properties": {
///    "error": {
///      "description": "Human-readable error description. Present when status is rejected.",
///      "type": "string"
///    },
///    "event_id": {
///      "description": "Server-assigned UUIDv7 event_id for the stored event. Present when status is accepted.",
///      "type": "string"
///    },
///    "status": {
///      "description": "Ingestion outcome — accepted means persisted (or duplicate), rejected means permanently invalid.",
///      "type": "string",
///      "enum": [
///        "accepted",
///        "rejected"
///      ]
///    }
///  },
///  "additionalProperties": false,
///  "x-postgresql-skip": true
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct CloudIngestionResponse {
    ///Human-readable error description. Present when status is rejected.
    #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
    pub error: ::std::option::Option<::std::string::String>,
    ///Server-assigned UUIDv7 event_id for the stored event. Present when status is accepted.
    #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
    pub event_id: ::std::option::Option<::std::string::String>,
    ///Ingestion outcome — accepted means persisted (or duplicate), rejected means permanently invalid.
    pub status: CloudIngestionResponseStatus,
}
///Ingestion outcome — accepted means persisted (or duplicate), rejected means permanently invalid.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
///  "description": "Ingestion outcome — accepted means persisted (or duplicate), rejected means permanently invalid.",
///  "type": "string",
///  "enum": [
///    "accepted",
///    "rejected"
///  ]
///}
/// ```
/// </details>
#[derive(
    ::serde::Deserialize,
    ::serde::Serialize,
    Clone,
    Copy,
    Debug,
    Eq,
    Hash,
    Ord,
    PartialEq,
    PartialOrd,
)]
pub enum CloudIngestionResponseStatus {
    #[serde(rename = "accepted")]
    Accepted,
    #[serde(rename = "rejected")]
    Rejected,
}
impl ::std::fmt::Display for CloudIngestionResponseStatus {
    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
        match *self {
            Self::Accepted => f.write_str("accepted"),
            Self::Rejected => f.write_str("rejected"),
        }
    }
}
impl ::std::str::FromStr for CloudIngestionResponseStatus {
    type Err = self::error::ConversionError;
    fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
        match value {
            "accepted" => Ok(Self::Accepted),
            "rejected" => Ok(Self::Rejected),
            _ => Err("invalid value".into()),
        }
    }
}
impl ::std::convert::TryFrom<&str> for CloudIngestionResponseStatus {
    type Error = self::error::ConversionError;
    fn try_from(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
        value.parse()
    }
}
impl ::std::convert::TryFrom<&::std::string::String> for CloudIngestionResponseStatus {
    type Error = self::error::ConversionError;
    fn try_from(
        value: &::std::string::String,
    ) -> ::std::result::Result<Self, self::error::ConversionError> {
        value.parse()
    }
}
impl ::std::convert::TryFrom<::std::string::String> for CloudIngestionResponseStatus {
    type Error = self::error::ConversionError;
    fn try_from(
        value: ::std::string::String,
    ) -> ::std::result::Result<Self, self::error::ConversionError> {
        value.parse()
    }
}
///CloudEvents v1.0.2 structured-mode envelope for agent hook events. Content-Type is application/cloudevents+json (single) or application/cloudevents-batch+json (batch). The 'data' field contains the raw agent payload untouched; all OpenLatch metadata lives in CloudEvents extension attributes. Extension attribute names MUST match ^[a-z0-9]+$ per the CloudEvents spec. verdict and latency_ms are NOT on the wire — they are produced by the daemon after processing and attached to stored events as the olverdict and ollatencyms extension attributes.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
///  "description": "CloudEvents v1.0.2 structured-mode envelope for agent hook events. Content-Type is application/cloudevents+json (single) or application/cloudevents-batch+json (batch). The 'data' field contains the raw agent payload untouched; all OpenLatch metadata lives in CloudEvents extension attributes. Extension attribute names MUST match ^[a-z0-9]+$ per the CloudEvents spec. verdict and latency_ms are NOT on the wire — they are produced by the daemon after processing and attached to stored events as the olverdict and ollatencyms extension attributes.",
///  "examples": [
///    {
///      "agentversion": "1.2.0",
///      "arch": "x86_64",
///      "clientversion": "0.2.0",
///      "data": {
///        "tool_input": {
///          "command": "ls -la"
///        },
///        "tool_name": "Bash"
///      },
///      "datacontenttype": "application/json",
///      "id": "evt_019d8af1-f8da-73b3-92eb-79a99e59b10b",
///      "localipv4": "192.168.1.42",
///      "os": "linux",
///      "publicipv4": "203.0.113.7",
///      "source": "claude-code",
///      "specversion": "1.0",
///      "subject": "sess_abc123",
///      "time": "2026-04-16T12:00:00Z",
///      "type": "pre_tool_use"
///    }
///  ],
///  "type": "object",
///  "required": [
///    "id",
///    "source",
///    "specversion",
///    "time",
///    "type"
///  ],
///  "properties": {
///    "agentid": {
///      "description": "OpenLatch extension. Stamped by the daemon on outbound events — identifies the AI agent install (one openlatch-client installation) that emitted this event (agt_<uuid>).",
///      "type": "string"
///    },
///    "agentversion": {
///      "description": "OpenLatch extension (was 'agent_version'). Agent software version, if reported by the hook.",
///      "type": "string"
///    },
///    "arch": {
///      "description": "OpenLatch extension. CPU architecture ('x86_64', 'aarch64').",
///      "type": "string"
///    },
///    "clientversion": {
///      "description": "OpenLatch extension (was 'client_version'). Semver of the openlatch-client that emitted the envelope.",
///      "type": "string"
///    },
///    "data": {
///      "description": "Raw agent payload, forwarded verbatim. Shape is agent-specific (e.g. Claude Code PreToolUse emits { tool_name, tool_input, … }; Cursor beforeShellExecution emits { command, … }). OpenLatch does NOT normalise this field."
///    },
///    "datacontenttype": {
///      "description": "Media type of the 'data' field. CloudEvents core optional attribute. Fixed to application/json for all OpenLatch events.",
///      "type": "string",
///      "const": "application/json"
///    },
///    "id": {
///      "description": "Unique event identifier — UUIDv7 with 'evt_' prefix. CloudEvents core attribute.",
///      "type": "string"
///    },
///    "localipv4": {
///      "description": "OpenLatch extension (was 'local_ipv4'). Machine's local IPv4 address. Detected once at daemon startup, cached for the process lifetime. Omitted when no non-loopback interface is available.",
///      "type": "string",
///      "format": "ipv4"
///    },
///    "localipv6": {
///      "description": "OpenLatch extension (was 'local_ipv6'). Machine's local IPv6 address.",
///      "type": "string",
///      "format": "ipv6"
///    },
///    "os": {
///      "description": "OpenLatch extension. Operating system ('linux', 'macos', 'windows'). CloudEvents extension — lowercase alphanumeric attribute name.",
///      "type": "string"
///    },
///    "publicipv4": {
///      "description": "OpenLatch extension (was 'public_ipv4'). Machine's public IPv4 address.",
///      "type": "string",
///      "format": "ipv4"
///    },
///    "publicipv6": {
///      "description": "OpenLatch extension (was 'public_ipv6'). Machine's public IPv6 address.",
///      "type": "string",
///      "format": "ipv6"
///    },
///    "source": {
///      "description": "Agent platform identifier (was 'agent_platform'). CloudEvents core attribute. Bare string per OpenLatch convention; any string is valid. See x-known-values in enums.schema.json#/$defs/AgentType for the canonical set.",
///      "$ref": "#/$defs/AgentType"
///    },
///    "specversion": {
///      "description": "CloudEvents spec version. MUST be '1.0' for CloudEvents v1.0.2.",
///      "type": "string",
///      "const": "1.0"
///    },
///    "subject": {
///      "description": "CloudEvents 'subject' attribute. OpenLatch uses this for the agent session identifier (was 'session_id'). Consumers can group events by subject for per-session analytics.",
///      "type": "string"
///    },
///    "time": {
///      "description": "Event creation timestamp (was 'timestamp'). RFC 3339 UTC with Z suffix. CloudEvents core attribute.",
///      "type": "string",
///      "format": "date-time"
///    },
///    "type": {
///      "description": "Hook event lifecycle name (was 'event_type'). CloudEvents core attribute. Any string is valid. See x-known-values in enums.schema.json#/$defs/HookEventType for the canonical set.",
///      "$ref": "#/$defs/HookEventType"
///    }
///  },
///  "additionalProperties": true,
///  "x-postgresql-skip": true
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug, PartialEq)]
pub struct EventEnvelope {
    ///OpenLatch extension. Stamped by the daemon on outbound events — identifies the AI agent install (one openlatch-client installation) that emitted this event (agt_<uuid>).
    #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
    pub agentid: ::std::option::Option<::std::string::String>,
    ///OpenLatch extension (was 'agent_version'). Agent software version, if reported by the hook.
    #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
    pub agentversion: ::std::option::Option<::std::string::String>,
    ///OpenLatch extension. CPU architecture ('x86_64', 'aarch64').
    #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
    pub arch: ::std::option::Option<::std::string::String>,
    ///OpenLatch extension (was 'client_version'). Semver of the openlatch-client that emitted the envelope.
    #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
    pub clientversion: ::std::option::Option<::std::string::String>,
    ///Raw agent payload, forwarded verbatim. Shape is agent-specific (e.g. Claude Code PreToolUse emits { tool_name, tool_input, … }; Cursor beforeShellExecution emits { command, … }). OpenLatch does NOT normalise this field.
    #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
    pub data: ::std::option::Option<::serde_json::Value>,
    ///Media type of the 'data' field. CloudEvents core optional attribute. Fixed to application/json for all OpenLatch events.
    #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
    pub datacontenttype: ::std::option::Option<::std::string::String>,
    ///Unique event identifier — UUIDv7 with 'evt_' prefix. CloudEvents core attribute.
    pub id: ::std::string::String,
    ///OpenLatch extension (was 'local_ipv4'). Machine's local IPv4 address. Detected once at daemon startup, cached for the process lifetime. Omitted when no non-loopback interface is available.
    #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
    pub localipv4: ::std::option::Option<::std::net::Ipv4Addr>,
    ///OpenLatch extension (was 'local_ipv6'). Machine's local IPv6 address.
    #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
    pub localipv6: ::std::option::Option<::std::net::Ipv6Addr>,
    ///OpenLatch extension. Operating system ('linux', 'macos', 'windows'). CloudEvents extension — lowercase alphanumeric attribute name.
    #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
    pub os: ::std::option::Option<::std::string::String>,
    ///OpenLatch extension (was 'public_ipv4'). Machine's public IPv4 address.
    #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
    pub publicipv4: ::std::option::Option<::std::net::Ipv4Addr>,
    ///OpenLatch extension (was 'public_ipv6'). Machine's public IPv6 address.
    #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
    pub publicipv6: ::std::option::Option<::std::net::Ipv6Addr>,
    ///Agent platform identifier (was 'agent_platform'). CloudEvents core attribute. Bare string per OpenLatch convention; any string is valid. See x-known-values in enums.schema.json#/$defs/AgentType for the canonical set.
    pub source: crate::core::envelope::known_types::AgentType,
    ///CloudEvents spec version. MUST be '1.0' for CloudEvents v1.0.2.
    pub specversion: ::std::string::String,
    ///CloudEvents 'subject' attribute. OpenLatch uses this for the agent session identifier (was 'session_id'). Consumers can group events by subject for per-session analytics.
    #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
    pub subject: ::std::option::Option<::std::string::String>,
    ///Event creation timestamp (was 'timestamp'). RFC 3339 UTC with Z suffix. CloudEvents core attribute.
    pub time: ::chrono::DateTime<::chrono::offset::Utc>,
    ///Hook event lifecycle name (was 'event_type'). CloudEvents core attribute. Any string is valid. See x-known-values in enums.schema.json#/$defs/HookEventType for the canonical set.
    #[serde(rename = "type")]
    pub type_: crate::core::envelope::known_types::HookEventType,
}
///Verdict returned to the agent hook. Closed enum — client must handle all three variants exhaustively. allow = proceed normally, approve = user-confirmed allow, deny = blocked.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
///  "description": "Verdict returned to the agent hook. Closed enum — client must handle all three variants exhaustively. allow = proceed normally, approve = user-confirmed allow, deny = blocked.",
///  "type": "string",
///  "enum": [
///    "allow",
///    "approve",
///    "deny"
///  ]
///}
/// ```
/// </details>
#[derive(
    ::serde::Deserialize,
    ::serde::Serialize,
    Clone,
    Copy,
    Debug,
    Eq,
    Hash,
    Ord,
    PartialEq,
    PartialOrd,
)]
pub enum Verdict {
    #[serde(rename = "allow")]
    Allow,
    #[serde(rename = "approve")]
    Approve,
    #[serde(rename = "deny")]
    Deny,
}
impl ::std::fmt::Display for Verdict {
    fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
        match *self {
            Self::Allow => f.write_str("allow"),
            Self::Approve => f.write_str("approve"),
            Self::Deny => f.write_str("deny"),
        }
    }
}
impl ::std::str::FromStr for Verdict {
    type Err = self::error::ConversionError;
    fn from_str(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
        match value {
            "allow" => Ok(Self::Allow),
            "approve" => Ok(Self::Approve),
            "deny" => Ok(Self::Deny),
            _ => Err("invalid value".into()),
        }
    }
}
impl ::std::convert::TryFrom<&str> for Verdict {
    type Error = self::error::ConversionError;
    fn try_from(value: &str) -> ::std::result::Result<Self, self::error::ConversionError> {
        value.parse()
    }
}
impl ::std::convert::TryFrom<&::std::string::String> for Verdict {
    type Error = self::error::ConversionError;
    fn try_from(
        value: &::std::string::String,
    ) -> ::std::result::Result<Self, self::error::ConversionError> {
        value.parse()
    }
}
impl ::std::convert::TryFrom<::std::string::String> for Verdict {
    type Error = self::error::ConversionError;
    fn try_from(
        value: ::std::string::String,
    ) -> ::std::result::Result<Self, self::error::ConversionError> {
        value.parse()
    }
}
///Verdict response returned to the agent hook after processing. The client passes this through to the agent hook without interpreting the verdict itself. Mirrors the cloud response schema.
///
/// <details><summary>JSON schema</summary>
///
/// ```json
///{
///  "description": "Verdict response returned to the agent hook after processing. The client passes this through to the agent hook without interpreting the verdict itself. Mirrors the cloud response schema.",
///  "examples": [
///    {
///      "event_id": "019d8af1-f8da-73b3-92eb-79a99e59b10b",
///      "latency_ms": 42,
///      "schema_version": "1.0",
///      "verdict": "allow"
///    },
///    {
///      "details_url": "https://app.openlatch.ai/events/019d8af1-f8da-73b3-92eb-000000000002",
///      "event_id": "019d8af1-f8da-73b3-92eb-000000000002",
///      "latency_ms": 88,
///      "reason": "Credential detected in tool output",
///      "rule_id": "rule_cred_001",
///      "schema_version": "1.0",
///      "severity": "critical",
///      "threat_category": "credential_detection",
///      "verdict": "deny"
///    }
///  ],
///  "type": "object",
///  "required": [
///    "event_id",
///    "latency_ms",
///    "schema_version",
///    "verdict"
///  ],
///  "properties": {
///    "details_url": {
///      "description": "URL to the OpenLatch dashboard with detailed event analysis. Omitted when not available.",
///      "type": "string"
///    },
///    "event_id": {
///      "description": "Server-assigned or client-generated ID of the event this verdict responds to.",
///      "type": "string"
///    },
///    "latency_ms": {
///      "description": "Total end-to-end processing latency in milliseconds, including cloud round-trip when applicable.",
///      "type": "number",
///      "minimum": 0.0
///    },
///    "reason": {
///      "description": "Human-readable explanation for the verdict. Omitted for allow verdicts.",
///      "type": "string"
///    },
///    "rule_id": {
///      "description": "Identifier of the detection rule that triggered this verdict. Omitted when no rule matched.",
///      "type": "string"
///    },
///    "schema_version": {
///      "description": "Schema version for forward compatibility. Currently '1.0'.",
///      "type": "string"
///    },
///    "severity": {
///      "description": "Threat severity level (e.g., 'critical', 'high', 'medium', 'low'). Omitted when no threat was detected.",
///      "type": "string"
///    },
///    "threat_category": {
///      "description": "Category of detected threat (e.g., 'credential_exfiltration', 'command_injection'). Omitted when no threat detected.",
///      "type": "string"
///    },
///    "verdict": {
///      "description": "The verdict: allow = proceed, approve = user-confirmed allow, deny = blocked.",
///      "$ref": "#/$defs/Verdict"
///    }
///  },
///  "additionalProperties": false,
///  "x-postgresql-skip": true
///}
/// ```
/// </details>
#[derive(::serde::Deserialize, ::serde::Serialize, Clone, Debug, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct VerdictResponse {
    ///URL to the OpenLatch dashboard with detailed event analysis. Omitted when not available.
    #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
    pub details_url: ::std::option::Option<::std::string::String>,
    ///Server-assigned or client-generated ID of the event this verdict responds to.
    pub event_id: ::std::string::String,
    pub latency_ms: f64,
    ///Human-readable explanation for the verdict. Omitted for allow verdicts.
    #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
    pub reason: ::std::option::Option<::std::string::String>,
    ///Identifier of the detection rule that triggered this verdict. Omitted when no rule matched.
    #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
    pub rule_id: ::std::option::Option<::std::string::String>,
    ///Schema version for forward compatibility. Currently '1.0'.
    pub schema_version: ::std::string::String,
    ///Threat severity level (e.g., 'critical', 'high', 'medium', 'low'). Omitted when no threat was detected.
    #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
    pub severity: ::std::option::Option<::std::string::String>,
    ///Category of detected threat (e.g., 'credential_exfiltration', 'command_injection'). Omitted when no threat detected.
    #[serde(default, skip_serializing_if = "::std::option::Option::is_none")]
    pub threat_category: ::std::option::Option<::std::string::String>,
    ///The verdict: allow = proceed, approve = user-confirmed allow, deny = blocked.
    pub verdict: Verdict,
}