lorebase-client 0.2.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lorebase_client/__init__.py +86 -0
- lorebase_client/aclient.py +1585 -0
- lorebase_client/client.py +1587 -0
- lorebase_client/config.py +160 -0
- lorebase_client/core.py +330 -0
- lorebase_client/endpoints.py +1292 -0
- lorebase_client/errors.py +242 -0
- lorebase_client/models.py +1662 -0
- lorebase_client/py.typed +0 -0
- lorebase_client-0.2.3.dist-info/METADATA +7 -0
- lorebase_client-0.2.3.dist-info/RECORD +12 -0
- lorebase_client-0.2.3.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,1662 @@
|
|
|
1
|
+
"""Typed request and response models of the Keeper API.
|
|
2
|
+
|
|
3
|
+
They mirror the routes in `packages/keeper` and the OpenAPI document in `docs/openapi.json`
|
|
4
|
+
(a contract test compares the two). Response models ignore members they do not know, so a
|
|
5
|
+
newer server does not break an older client. Values of Records and Links are in canonical
|
|
6
|
+
form: integers and decimals are JSON strings, timestamps RFC 3339.
|
|
7
|
+
|
|
8
|
+
Request models are validated when built; the clients send them without unset members.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
from typing import Any, Literal, Protocol, Self
|
|
15
|
+
|
|
16
|
+
from pydantic import BaseModel, ConfigDict, Field, JsonValue, model_validator
|
|
17
|
+
|
|
18
|
+
DOMAIN_ID_PATTERN = r"^[a-z][a-z0-9_-]*$"
|
|
19
|
+
|
|
20
|
+
SchemaSection = Literal["kinds", "relations", "traits", "operations", "derivations", "sources"]
|
|
21
|
+
SearchMode = Literal["lexical", "semantic", "hybrid"]
|
|
22
|
+
BranchMode = Literal["copy", "overlay"]
|
|
23
|
+
MaterializationStatus = Literal["pending", "running", "done", "failed"]
|
|
24
|
+
MaterializationPhase = Literal[
|
|
25
|
+
"records",
|
|
26
|
+
"links",
|
|
27
|
+
"history",
|
|
28
|
+
"clear",
|
|
29
|
+
"tree_history",
|
|
30
|
+
"tree_rows",
|
|
31
|
+
"switch",
|
|
32
|
+
"prune",
|
|
33
|
+
"derived",
|
|
34
|
+
"paths",
|
|
35
|
+
"search",
|
|
36
|
+
"done",
|
|
37
|
+
]
|
|
38
|
+
ServedFrom = Literal["materialized", "tree"]
|
|
39
|
+
ChangeKind = Literal["created", "updated", "deleted"]
|
|
40
|
+
LinkDirection = Literal["forward", "inverse"]
|
|
41
|
+
ProposalStatus = Literal["open", "merged", "closed"]
|
|
42
|
+
ProposalOrigin = Literal["manual", "source_sync", "extraction", "operation"]
|
|
43
|
+
ReviewVerdict = Literal["approve", "request_changes", "comment"]
|
|
44
|
+
ConflictStatus = Literal["open", "resolved"]
|
|
45
|
+
CheckStatus = Literal["passed", "failed", "pending"]
|
|
46
|
+
ContextFormat = Literal["json", "markdown"]
|
|
47
|
+
ItemDecisionValue = Literal["pending", "accepted", "rejected"]
|
|
48
|
+
ItemType = Literal["record", "value", "link"]
|
|
49
|
+
SuggestionType = Literal["kind", "attribute", "relation"]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class Model(BaseModel):
|
|
53
|
+
"""Base of response models: immutable, unknown members ignored."""
|
|
54
|
+
|
|
55
|
+
model_config = ConfigDict(extra="ignore", populate_by_name=True, frozen=True)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class RequestModel(BaseModel):
|
|
59
|
+
"""Base of request models: unknown members are rejected before anything is sent."""
|
|
60
|
+
|
|
61
|
+
model_config = ConfigDict(extra="forbid", populate_by_name=True)
|
|
62
|
+
|
|
63
|
+
def payload(self) -> dict[str, Any]:
|
|
64
|
+
"""JSON body: set members only, by alias."""
|
|
65
|
+
return self.model_dump(mode="json", exclude_none=True, by_alias=True)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# Pages ------------------------------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class Paged[T](Protocol):
|
|
72
|
+
"""Anything with `items` and `next_cursor`: what the pagination helpers walk."""
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def items(self) -> list[T]: ...
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def next_cursor(self) -> str | None: ...
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class Page[T](Model):
|
|
82
|
+
"""One page of a list endpoint; `next_cursor` is null on the last page."""
|
|
83
|
+
|
|
84
|
+
items: list[T]
|
|
85
|
+
next_cursor: str | None = None
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# Identity ---------------------------------------------------------------------------------
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class QuotaUse(Model):
|
|
92
|
+
"""Use of one daily quota; without a limit, `limit` and `remaining` are None."""
|
|
93
|
+
|
|
94
|
+
limit: int | None = None
|
|
95
|
+
used: int
|
|
96
|
+
remaining: int | None = None
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class QuotaReport(Model):
|
|
100
|
+
"""Daily quotas of an API key and what it has used since the last reset."""
|
|
101
|
+
|
|
102
|
+
period: str = "day"
|
|
103
|
+
resets_at: datetime
|
|
104
|
+
requests: QuotaUse
|
|
105
|
+
context_tokens: QuotaUse
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class WhoAmI(Model):
|
|
109
|
+
"""The authenticated caller."""
|
|
110
|
+
|
|
111
|
+
id: str
|
|
112
|
+
type: str
|
|
113
|
+
display_name: str
|
|
114
|
+
method: str
|
|
115
|
+
subjects: list[str]
|
|
116
|
+
key_scopes: list[str]
|
|
117
|
+
installation_admin: bool
|
|
118
|
+
quota: QuotaReport | None = None
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# Domains ----------------------------------------------------------------------------------
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class DomainCreate(RequestModel):
|
|
125
|
+
"""A new Domain."""
|
|
126
|
+
|
|
127
|
+
id: str = Field(pattern=DOMAIN_ID_PATTERN, max_length=63)
|
|
128
|
+
display_name: str = Field(default="", max_length=200)
|
|
129
|
+
description: str = Field(default="", max_length=4000)
|
|
130
|
+
default_branch: str | None = Field(default=None, min_length=1, max_length=200)
|
|
131
|
+
settings: dict[str, JsonValue] = Field(default_factory=dict)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class DomainUpdate(RequestModel):
|
|
135
|
+
"""Display metadata; omitted members stay as they are."""
|
|
136
|
+
|
|
137
|
+
display_name: str | None = Field(default=None, min_length=1, max_length=200)
|
|
138
|
+
description: str | None = Field(default=None, max_length=4000)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class Domain(Model):
|
|
142
|
+
"""A Domain and the head Revision of its default branch."""
|
|
143
|
+
|
|
144
|
+
id: str
|
|
145
|
+
display_name: str
|
|
146
|
+
description: str | None = None
|
|
147
|
+
default_branch: str
|
|
148
|
+
settings: dict[str, Any] = Field(default_factory=dict)
|
|
149
|
+
created_at: datetime
|
|
150
|
+
created_by: str
|
|
151
|
+
head: str | None = Field(default=None, description="Head of the default branch (get only).")
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
# Branches and tags ------------------------------------------------------------------------
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class BranchCreate(RequestModel):
|
|
158
|
+
"""A new branch, started at a Revision or at the head of a branch or tag."""
|
|
159
|
+
|
|
160
|
+
name: str = Field(min_length=1)
|
|
161
|
+
source: str | None = None
|
|
162
|
+
revision: str | None = None
|
|
163
|
+
mode: BranchMode | None = None
|
|
164
|
+
"""`copy` (own rows) or `overlay` (only its changes); default: the server's setting."""
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class BranchUpdate(RequestModel):
|
|
168
|
+
"""Branch flags."""
|
|
169
|
+
|
|
170
|
+
protected: bool
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class MaterializationProgress(Model):
|
|
174
|
+
"""How far the background copy of a branch got."""
|
|
175
|
+
|
|
176
|
+
phase: MaterializationPhase
|
|
177
|
+
rows_done: int
|
|
178
|
+
rows_total: int | None
|
|
179
|
+
error: str | None
|
|
180
|
+
updated_at: datetime
|
|
181
|
+
finished_at: datetime | None
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class Branch(Model):
|
|
185
|
+
"""A branch."""
|
|
186
|
+
|
|
187
|
+
name: str
|
|
188
|
+
head: str
|
|
189
|
+
protected: bool
|
|
190
|
+
default: bool
|
|
191
|
+
materialization: str
|
|
192
|
+
version: int
|
|
193
|
+
created_at: datetime
|
|
194
|
+
created_by: str
|
|
195
|
+
materialization_status: MaterializationStatus | None = None
|
|
196
|
+
materialization_progress: MaterializationProgress | None = None
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class BranchDetail(Branch):
|
|
200
|
+
"""A branch with its distance to the default branch."""
|
|
201
|
+
|
|
202
|
+
ahead: int
|
|
203
|
+
behind: int
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
class TagCreate(RequestModel):
|
|
207
|
+
"""A new tag at a Revision or at the head of a branch."""
|
|
208
|
+
|
|
209
|
+
name: str = Field(min_length=1)
|
|
210
|
+
source: str | None = None
|
|
211
|
+
revision: str | None = None
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
class Tag(Model):
|
|
215
|
+
"""An immutable tag."""
|
|
216
|
+
|
|
217
|
+
name: str
|
|
218
|
+
revision: str
|
|
219
|
+
created_at: datetime
|
|
220
|
+
created_by: str
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
# Schema -----------------------------------------------------------------------------------
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class ElementRemoval(RequestModel):
|
|
227
|
+
"""A schema element to remove."""
|
|
228
|
+
|
|
229
|
+
section: SchemaSection
|
|
230
|
+
name: str = Field(min_length=1)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
class _ChangeSet(RequestModel):
|
|
234
|
+
yaml: str | None = Field(default=None, description="Full schema document.")
|
|
235
|
+
patch: dict[str, JsonValue] | None = Field(default=None, description="Partial document.")
|
|
236
|
+
remove: list[ElementRemoval] = Field(default_factory=list)
|
|
237
|
+
|
|
238
|
+
@model_validator(mode="after")
|
|
239
|
+
def _one_form(self) -> Self:
|
|
240
|
+
if (self.yaml is not None) == (self.patch is not None or bool(self.remove)):
|
|
241
|
+
raise ValueError("give either `yaml`, or `patch` and/or `remove`")
|
|
242
|
+
return self
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
class SchemaChangeRequest(_ChangeSet):
|
|
246
|
+
"""A schema change set for one branch."""
|
|
247
|
+
|
|
248
|
+
message: str | None = Field(default=None, max_length=2000)
|
|
249
|
+
expected_head: str | None = None
|
|
250
|
+
dry_run: bool = False
|
|
251
|
+
cascade: bool = False
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
class SchemaValidateRequest(_ChangeSet):
|
|
255
|
+
"""A change set to validate against a branch or Revision."""
|
|
256
|
+
|
|
257
|
+
branch: str | None = None
|
|
258
|
+
at: str | None = None
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
class EmbeddingModel(Model):
|
|
262
|
+
"""The model a `query_vector` must be made with."""
|
|
263
|
+
|
|
264
|
+
model: str
|
|
265
|
+
dims: int
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
class SchemaSearch(Model):
|
|
269
|
+
"""How the Domain can be searched; `embedding` is set when semantic search is available."""
|
|
270
|
+
|
|
271
|
+
modes: list[Literal["lexical", "semantic", "hybrid"]] = ["lexical"]
|
|
272
|
+
embedding: EmbeddingModel | None = None
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
class SchemaDocument(Model):
|
|
276
|
+
"""The schema of a Revision: elements keyed by id, with api names."""
|
|
277
|
+
|
|
278
|
+
domain: str
|
|
279
|
+
branch: str | None = None
|
|
280
|
+
revision: str
|
|
281
|
+
schema_: dict[str, Any] = Field(alias="schema")
|
|
282
|
+
search: SchemaSearch = Field(default_factory=SchemaSearch)
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
class ElementChange(Model):
|
|
286
|
+
"""One schema element written or removed."""
|
|
287
|
+
|
|
288
|
+
op: Literal["create", "update", "delete"]
|
|
289
|
+
section: str
|
|
290
|
+
id: str
|
|
291
|
+
api_name: str
|
|
292
|
+
|
|
293
|
+
|
|
294
|
+
class SchemaChangeResult(Model):
|
|
295
|
+
"""Outcome of a schema change set."""
|
|
296
|
+
|
|
297
|
+
status: Literal["committed", "dry_run", "no_op"]
|
|
298
|
+
branch: str
|
|
299
|
+
head: str
|
|
300
|
+
revision: str | None = None
|
|
301
|
+
changes: list[ElementChange]
|
|
302
|
+
change_count: int
|
|
303
|
+
data_changes: int
|
|
304
|
+
issues: list[dict[str, Any]]
|
|
305
|
+
issue_count: int
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
class RebaseTagsRequest(RequestModel):
|
|
309
|
+
"""A tag rebase of one branch: take the default branch's sensitivity tags."""
|
|
310
|
+
|
|
311
|
+
message: str | None = Field(default=None, max_length=2000)
|
|
312
|
+
expected_head: str | None = None
|
|
313
|
+
dry_run: bool = False
|
|
314
|
+
rows_only: bool = False
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
class TagChange(Model):
|
|
318
|
+
"""Sensitivity tags a tag rebase adds to one Attribute."""
|
|
319
|
+
|
|
320
|
+
section: Literal["kinds", "relations"]
|
|
321
|
+
owner: str
|
|
322
|
+
attribute: str
|
|
323
|
+
tags: list[str]
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
class RebaseTagsResult(Model):
|
|
327
|
+
"""Outcome of a tag rebase; `job` is the queued row rewrite of a `rows_only` rebase."""
|
|
328
|
+
|
|
329
|
+
status: Literal["committed", "dry_run", "no_op", "queued"]
|
|
330
|
+
branch: str
|
|
331
|
+
head: str
|
|
332
|
+
revision: str | None = None
|
|
333
|
+
tags: list[TagChange] = Field(default_factory=list)
|
|
334
|
+
changes: list[ElementChange] = Field(default_factory=list)
|
|
335
|
+
change_count: int = 0
|
|
336
|
+
issues: list[dict[str, Any]] = Field(default_factory=list)
|
|
337
|
+
issue_count: int = 0
|
|
338
|
+
job: int | None = None
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
class SchemaValidation(Model):
|
|
342
|
+
"""Static validation of a change set."""
|
|
343
|
+
|
|
344
|
+
valid: bool
|
|
345
|
+
issues: list[dict[str, Any]]
|
|
346
|
+
issue_count: int
|
|
347
|
+
changes: list[ElementChange]
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
# Operations -------------------------------------------------------------------------------
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
class ExecuteRequest(RequestModel):
|
|
354
|
+
"""An execution of an Operation."""
|
|
355
|
+
|
|
356
|
+
parameters: dict[str, JsonValue] = Field(default_factory=dict)
|
|
357
|
+
branch: str | None = None
|
|
358
|
+
dry_run: bool = False
|
|
359
|
+
idempotency_key: str | None = None
|
|
360
|
+
expected_head: str | None = None
|
|
361
|
+
message: str | None = Field(default=None, max_length=2000)
|
|
362
|
+
task: str | None = Field(default=None, min_length=1, max_length=200)
|
|
363
|
+
intent: str | None = Field(default=None, max_length=4000)
|
|
364
|
+
auto_propose: bool | None = None
|
|
365
|
+
auto_merge: bool | None = None
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
class Execution(Model):
|
|
369
|
+
"""Result of an Operation execution."""
|
|
370
|
+
|
|
371
|
+
status: Literal["committed", "dry_run"]
|
|
372
|
+
operation: str
|
|
373
|
+
branch: str
|
|
374
|
+
invocation_id: str
|
|
375
|
+
parameters_hash: str
|
|
376
|
+
head: str
|
|
377
|
+
revision: str | None = None
|
|
378
|
+
revisions: list[str]
|
|
379
|
+
edits: list[dict[str, Any]]
|
|
380
|
+
edit_count: int
|
|
381
|
+
edits_truncated: bool
|
|
382
|
+
violations: list[dict[str, Any]]
|
|
383
|
+
violation_count: int
|
|
384
|
+
warnings: list[dict[str, Any]]
|
|
385
|
+
review_required: bool
|
|
386
|
+
replayed: bool
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
class ProposalRef(Model):
|
|
390
|
+
"""The Proposal carrying the edits of an agent write."""
|
|
391
|
+
|
|
392
|
+
id: str
|
|
393
|
+
title: str
|
|
394
|
+
status: str
|
|
395
|
+
source_ref: str
|
|
396
|
+
target_ref: str
|
|
397
|
+
url: str
|
|
398
|
+
created: bool
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
class AgentApproval(Model):
|
|
402
|
+
required: int
|
|
403
|
+
approvals: int
|
|
404
|
+
satisfied: bool
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
class AgentAttempt(Model):
|
|
408
|
+
"""Conflicts between the task branch and the target."""
|
|
409
|
+
|
|
410
|
+
id: str
|
|
411
|
+
result: str | None = None
|
|
412
|
+
open_conflicts: int
|
|
413
|
+
resolved_conflicts: int
|
|
414
|
+
counts: dict[str, dict[str, int]]
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
class AgentMerge(Model):
|
|
418
|
+
requested: bool
|
|
419
|
+
allowed: bool
|
|
420
|
+
performed: bool
|
|
421
|
+
blockers: list[str]
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
class NextHint(Model):
|
|
425
|
+
"""A next step: what to do, and the call that does it when there is one."""
|
|
426
|
+
|
|
427
|
+
action: str
|
|
428
|
+
detail: str
|
|
429
|
+
method: str | None = None
|
|
430
|
+
href: str | None = None
|
|
431
|
+
permission: str | None = None
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
class AgentWrite(Model):
|
|
435
|
+
"""An Operation that needed review, run on a task branch and proposed (HTTP 202)."""
|
|
436
|
+
|
|
437
|
+
status: Literal["proposed", "merged", "conflicts"]
|
|
438
|
+
operation: str
|
|
439
|
+
branch: str
|
|
440
|
+
target: str
|
|
441
|
+
invocation_id: str
|
|
442
|
+
parameters_hash: str
|
|
443
|
+
revision: str | None = None
|
|
444
|
+
revisions: list[str]
|
|
445
|
+
edit_count: int
|
|
446
|
+
replayed: bool
|
|
447
|
+
proposal: ProposalRef
|
|
448
|
+
approval: AgentApproval
|
|
449
|
+
attempt: AgentAttempt | None = None
|
|
450
|
+
merge: AgentMerge
|
|
451
|
+
merged_revision: str | None = None
|
|
452
|
+
branch_created: bool
|
|
453
|
+
branch_deleted: bool
|
|
454
|
+
warnings: list[dict[str, Any]]
|
|
455
|
+
next: list[NextHint]
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
# Reads ------------------------------------------------------------------------------------
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
class LinkCount(Model):
|
|
462
|
+
relation: str
|
|
463
|
+
direction: LinkDirection
|
|
464
|
+
count: int
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
class Lineage(Model):
|
|
468
|
+
provenance_id: str
|
|
469
|
+
origin_type: str | None = None
|
|
470
|
+
actor: str | None = None
|
|
471
|
+
at: datetime | None = None
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
class RecordItem(Model):
|
|
475
|
+
"""A Record with values keyed by Attribute api name."""
|
|
476
|
+
|
|
477
|
+
kind: str
|
|
478
|
+
key: str
|
|
479
|
+
title: str | None = None
|
|
480
|
+
values: dict[str, JsonValue] = Field(default_factory=dict)
|
|
481
|
+
updated_revision: str | None = None
|
|
482
|
+
updated_at: datetime | None = None
|
|
483
|
+
links: list[LinkCount] | None = None
|
|
484
|
+
lineage: dict[str, Lineage] | None = None
|
|
485
|
+
included: dict[str, list[RecordItem]] | None = None
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
class RecordRef(Model):
|
|
489
|
+
kind: str
|
|
490
|
+
key: str
|
|
491
|
+
title: str | None = None
|
|
492
|
+
|
|
493
|
+
|
|
494
|
+
class Served(Model):
|
|
495
|
+
"""Where a response was read."""
|
|
496
|
+
|
|
497
|
+
revision: str
|
|
498
|
+
served_from: ServedFrom
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
class RecordResponse(Served):
|
|
502
|
+
record: RecordItem
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
class RecordPage(Served):
|
|
506
|
+
items: list[RecordItem]
|
|
507
|
+
next_cursor: str | None = None
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
class AggregateCount(Model):
|
|
511
|
+
kind: str
|
|
512
|
+
value: JsonValue = None
|
|
513
|
+
count: int
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
class Aggregate(Model):
|
|
517
|
+
counts: list[AggregateCount]
|
|
518
|
+
truncated: bool = False
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
class QueryResult(RecordPage):
|
|
522
|
+
aggregate: Aggregate | None = None
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
class LinkItem(Model):
|
|
526
|
+
"""A Link seen from one side."""
|
|
527
|
+
|
|
528
|
+
relation: str
|
|
529
|
+
direction: LinkDirection
|
|
530
|
+
from_key: str
|
|
531
|
+
to_key: str
|
|
532
|
+
values: dict[str, JsonValue] = Field(default_factory=dict)
|
|
533
|
+
target: RecordRef | None = None
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
class LinkPage(Served):
|
|
537
|
+
items: list[LinkItem]
|
|
538
|
+
next_cursor: str | None = None
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
class Edge(Model):
|
|
542
|
+
relation: str
|
|
543
|
+
source: RecordRef = Field(alias="from")
|
|
544
|
+
to: RecordRef
|
|
545
|
+
values: dict[str, JsonValue] = Field(default_factory=dict)
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
class TraverseHop(Model):
|
|
549
|
+
relation: str
|
|
550
|
+
records: list[RecordItem]
|
|
551
|
+
edges: list[Edge]
|
|
552
|
+
truncated: bool = False
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
class TraverseResult(Served):
|
|
556
|
+
start: RecordItem
|
|
557
|
+
hops: list[TraverseHop]
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
class NeighborhoodResult(Served):
|
|
561
|
+
nodes: list[RecordItem]
|
|
562
|
+
edges: list[Edge]
|
|
563
|
+
truncated: bool = False
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
class SearchHit(Model):
|
|
567
|
+
kind: str
|
|
568
|
+
key: str
|
|
569
|
+
title: str | None = None
|
|
570
|
+
snippet: str | None = None
|
|
571
|
+
score: float
|
|
572
|
+
match: Literal["all", "partial"] | None = None
|
|
573
|
+
"""`all` when the hit holds every term of q, `partial` when only some (ranked after the
|
|
574
|
+
others); None for hits found by vector only."""
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
class SearchCorrection(Model):
|
|
578
|
+
"""A query word the branch does not hold and the words searched in its place as well."""
|
|
579
|
+
|
|
580
|
+
word: str
|
|
581
|
+
replacements: list[str]
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
class SearchResult(Served):
|
|
585
|
+
mode: str
|
|
586
|
+
items: list[SearchHit]
|
|
587
|
+
corrections: list[SearchCorrection] = Field(default_factory=list)
|
|
588
|
+
truncated_candidates: bool = False
|
|
589
|
+
"""Only a sample of the matches was ranked (every term of q is very common): narrow q."""
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
class QueryRequest(RequestModel):
|
|
593
|
+
"""A query of the JSON DSL and where to run it. Predicates are plain JSON objects."""
|
|
594
|
+
|
|
595
|
+
kind: str | None = None
|
|
596
|
+
trait: str | None = None
|
|
597
|
+
where: dict[str, JsonValue] | None = None
|
|
598
|
+
select: list[str] | None = None
|
|
599
|
+
include: dict[str, dict[str, JsonValue]] | None = None
|
|
600
|
+
order_by: list[dict[str, str]] | None = None
|
|
601
|
+
limit: int | None = Field(default=None, ge=0)
|
|
602
|
+
cursor: str | None = None
|
|
603
|
+
aggregate: dict[str, JsonValue] | None = None
|
|
604
|
+
branch: str | None = None
|
|
605
|
+
at: str | None = None
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
class RecordAddress(RequestModel):
|
|
609
|
+
kind: str = Field(min_length=1)
|
|
610
|
+
key: str = Field(min_length=1)
|
|
611
|
+
|
|
612
|
+
|
|
613
|
+
class TraverseStep(RequestModel):
|
|
614
|
+
relation: str = Field(min_length=1)
|
|
615
|
+
where: dict[str, JsonValue] | None = None
|
|
616
|
+
limit: int | None = Field(default=None, ge=1)
|
|
617
|
+
|
|
618
|
+
|
|
619
|
+
class TraverseRequest(RequestModel):
|
|
620
|
+
"""Follow a path of Relations from one Record."""
|
|
621
|
+
|
|
622
|
+
branch: str | None = None
|
|
623
|
+
at: str | None = None
|
|
624
|
+
start: RecordAddress
|
|
625
|
+
path: list[TraverseStep] = Field(min_length=1)
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
class NeighborhoodRequest(RequestModel):
|
|
629
|
+
"""Breadth-first expansion from seed Records."""
|
|
630
|
+
|
|
631
|
+
branch: str | None = None
|
|
632
|
+
at: str | None = None
|
|
633
|
+
seeds: list[RecordAddress] = Field(min_length=1)
|
|
634
|
+
depth: int = Field(default=1, ge=1)
|
|
635
|
+
relations: list[str] | None = None
|
|
636
|
+
max_nodes: int | None = Field(default=None, ge=1)
|
|
637
|
+
max_edges: int | None = Field(default=None, ge=1)
|
|
638
|
+
values: bool | None = None
|
|
639
|
+
"""False: nodes come back as headers (kind, key, title, empty `values`) and edges as
|
|
640
|
+
endpoints (empty `values`); None keeps the server default (with values)."""
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
class SearchTuning(RequestModel):
|
|
644
|
+
"""Ranking knobs of one search in place of the configured ones; unset knobs keep theirs.
|
|
645
|
+
|
|
646
|
+
`candidates` and `typo_candidates` may only be lowered.
|
|
647
|
+
"""
|
|
648
|
+
|
|
649
|
+
lexical_weight: float | None = Field(default=None, ge=0, le=100)
|
|
650
|
+
semantic_weight: float | None = Field(default=None, ge=0, le=100)
|
|
651
|
+
rrf_k: int | None = Field(default=None, ge=1, le=10000)
|
|
652
|
+
trigram_threshold: float | None = Field(default=None, ge=0.1, le=1)
|
|
653
|
+
trigram_weight: float | None = Field(default=None, ge=0, le=100)
|
|
654
|
+
typo_candidates: int | None = Field(default=None, ge=0)
|
|
655
|
+
candidates: int | None = Field(default=None, ge=1)
|
|
656
|
+
|
|
657
|
+
|
|
658
|
+
class SearchRequest(RequestModel):
|
|
659
|
+
"""Lexical, semantic (caller-supplied vector) or hybrid search."""
|
|
660
|
+
|
|
661
|
+
branch: str | None = None
|
|
662
|
+
at: str | None = None
|
|
663
|
+
q: str | None = Field(default=None, min_length=1)
|
|
664
|
+
kinds: list[str] | None = None
|
|
665
|
+
where: dict[str, JsonValue] | None = None
|
|
666
|
+
mode: SearchMode | None = None
|
|
667
|
+
query_vector: list[float] | None = None
|
|
668
|
+
limit: int | None = Field(default=None, ge=1)
|
|
669
|
+
tuning: SearchTuning | None = None
|
|
670
|
+
|
|
671
|
+
|
|
672
|
+
# Revisions, log and diff ------------------------------------------------------------------
|
|
673
|
+
|
|
674
|
+
|
|
675
|
+
class Revision(Model):
|
|
676
|
+
id: str
|
|
677
|
+
parents: list[str]
|
|
678
|
+
root: str
|
|
679
|
+
generation: int
|
|
680
|
+
author: str
|
|
681
|
+
on_behalf_of: str | None = None
|
|
682
|
+
message: str
|
|
683
|
+
origin: dict[str, Any]
|
|
684
|
+
stats: dict[str, Any]
|
|
685
|
+
created_at: datetime
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
class LogPage(Model):
|
|
689
|
+
branch: str
|
|
690
|
+
head: str
|
|
691
|
+
items: list[Revision]
|
|
692
|
+
next_cursor: str | None = None
|
|
693
|
+
|
|
694
|
+
|
|
695
|
+
class PartitionCount(Model):
|
|
696
|
+
partition: str
|
|
697
|
+
element: str
|
|
698
|
+
name: str
|
|
699
|
+
count: int
|
|
700
|
+
|
|
701
|
+
|
|
702
|
+
class RevisionCounts(Model):
|
|
703
|
+
revision: str
|
|
704
|
+
partitions: list[PartitionCount]
|
|
705
|
+
|
|
706
|
+
|
|
707
|
+
class ValueChange(Model):
|
|
708
|
+
name: str
|
|
709
|
+
old: JsonValue = None
|
|
710
|
+
new: JsonValue = None
|
|
711
|
+
|
|
712
|
+
|
|
713
|
+
class Change(Model):
|
|
714
|
+
"""One changed element between two Revisions."""
|
|
715
|
+
|
|
716
|
+
element: str
|
|
717
|
+
change: Literal["added", "removed", "modified"]
|
|
718
|
+
partition: str
|
|
719
|
+
kind: str | None = None
|
|
720
|
+
relation: str | None = None
|
|
721
|
+
key: str | None = None
|
|
722
|
+
from_key: str | None = None
|
|
723
|
+
to_key: str | None = None
|
|
724
|
+
element_type: str | None = None
|
|
725
|
+
api_name: str | None = None
|
|
726
|
+
old_hash: str | None = None
|
|
727
|
+
new_hash: str | None = None
|
|
728
|
+
changes: list[ValueChange] | None = None
|
|
729
|
+
provenance: dict[str, str] | None = None
|
|
730
|
+
|
|
731
|
+
|
|
732
|
+
class PartitionSummary(Model):
|
|
733
|
+
partition: str
|
|
734
|
+
element: str
|
|
735
|
+
name: str
|
|
736
|
+
added: int = 0
|
|
737
|
+
removed: int = 0
|
|
738
|
+
modified: int = 0
|
|
739
|
+
|
|
740
|
+
|
|
741
|
+
class ChangesPage(Model):
|
|
742
|
+
"""A page of element changes with the summary of all of them."""
|
|
743
|
+
|
|
744
|
+
from_revision: str | None = None
|
|
745
|
+
to_revision: str
|
|
746
|
+
total: int
|
|
747
|
+
summary: list[PartitionSummary]
|
|
748
|
+
items: list[Change]
|
|
749
|
+
next_cursor: str | None = None
|
|
750
|
+
|
|
751
|
+
|
|
752
|
+
# Administration ---------------------------------------------------------------------------
|
|
753
|
+
|
|
754
|
+
|
|
755
|
+
class EraseRequest(RequestModel):
|
|
756
|
+
"""Erase the vaulted values of a Record (`key`, optional `attributes`) or a value of a
|
|
757
|
+
vault Attribute in every Record of a Kind (`attribute`, `value`)."""
|
|
758
|
+
|
|
759
|
+
kind: str
|
|
760
|
+
key: str | None = None
|
|
761
|
+
attributes: list[str] | None = None
|
|
762
|
+
attribute: str | None = None
|
|
763
|
+
value: JsonValue = None
|
|
764
|
+
branch: str | None = None
|
|
765
|
+
|
|
766
|
+
|
|
767
|
+
class EraseResult(Model):
|
|
768
|
+
"""Outcome of an erasure: `erased` vault values were erased by this request."""
|
|
769
|
+
|
|
770
|
+
domain: str
|
|
771
|
+
kind: str
|
|
772
|
+
mode: str
|
|
773
|
+
erased: int
|
|
774
|
+
unopenable: int = 0
|
|
775
|
+
|
|
776
|
+
|
|
777
|
+
class VaultKeys(Model):
|
|
778
|
+
"""Which keys the vault rows use (`vault.manage`)."""
|
|
779
|
+
|
|
780
|
+
key_id: str
|
|
781
|
+
token_key_id: str
|
|
782
|
+
fingerprint_key: str
|
|
783
|
+
encryption_keys: dict[str, int]
|
|
784
|
+
fingerprint_keys: dict[str, int]
|
|
785
|
+
tombstone_keys: dict[str, int]
|
|
786
|
+
outdated: int
|
|
787
|
+
removable_keys: list[str]
|
|
788
|
+
removable_token_keys: list[str]
|
|
789
|
+
digests: str
|
|
790
|
+
|
|
791
|
+
|
|
792
|
+
class VaultRotation(Model):
|
|
793
|
+
"""A queued vault key rotation."""
|
|
794
|
+
|
|
795
|
+
job_id: int
|
|
796
|
+
|
|
797
|
+
|
|
798
|
+
class CreateApiKey(RequestModel):
|
|
799
|
+
"""Request to create an API key for a Domain."""
|
|
800
|
+
|
|
801
|
+
name: str = Field(min_length=1, max_length=120)
|
|
802
|
+
role: str | None = None
|
|
803
|
+
ttl_days: int | None = Field(default=None, ge=1)
|
|
804
|
+
principal_id: str | None = None
|
|
805
|
+
allowed_networks: list[str] | None = None
|
|
806
|
+
|
|
807
|
+
|
|
808
|
+
class ApiKey(Model):
|
|
809
|
+
"""An API key; the secret is shown only when it is created."""
|
|
810
|
+
|
|
811
|
+
id: str
|
|
812
|
+
principal_id: str
|
|
813
|
+
prefix: str
|
|
814
|
+
scopes: list[str]
|
|
815
|
+
expires_at: datetime | None = None
|
|
816
|
+
last_used_at: datetime | None = None
|
|
817
|
+
revoked_at: datetime | None = None
|
|
818
|
+
created_at: datetime
|
|
819
|
+
allowed_networks: list[str] = Field(default_factory=list)
|
|
820
|
+
|
|
821
|
+
|
|
822
|
+
class ApiKeyCreated(ApiKey):
|
|
823
|
+
"""A new API key: `key` is the secret, returned once."""
|
|
824
|
+
|
|
825
|
+
key: str
|
|
826
|
+
role: str | None = None
|
|
827
|
+
|
|
828
|
+
|
|
829
|
+
class Principal(Model):
|
|
830
|
+
id: str
|
|
831
|
+
type: str
|
|
832
|
+
display_name: str
|
|
833
|
+
external_subject: str | None = None
|
|
834
|
+
created_at: datetime
|
|
835
|
+
|
|
836
|
+
|
|
837
|
+
class CreateGrant(RequestModel):
|
|
838
|
+
"""Request to grant a role."""
|
|
839
|
+
|
|
840
|
+
subject: str = Field(min_length=1)
|
|
841
|
+
role: str
|
|
842
|
+
scope_type: Literal["domain", "kind", "operation", "branch"] = "domain"
|
|
843
|
+
scope_ref: str | None = Field(default=None, min_length=1)
|
|
844
|
+
|
|
845
|
+
|
|
846
|
+
class Grant(Model):
|
|
847
|
+
id: str
|
|
848
|
+
domain_id: str
|
|
849
|
+
subject: str
|
|
850
|
+
role: str
|
|
851
|
+
scope_type: str
|
|
852
|
+
scope_ref: str | None = None
|
|
853
|
+
created_by: str
|
|
854
|
+
created_at: datetime
|
|
855
|
+
|
|
856
|
+
|
|
857
|
+
class AuditEntry(Model):
|
|
858
|
+
seq: int
|
|
859
|
+
at: datetime
|
|
860
|
+
domain_id: str | None = None
|
|
861
|
+
actor: str
|
|
862
|
+
action: str
|
|
863
|
+
target: str | None = None
|
|
864
|
+
details: dict[str, Any] = Field(default_factory=dict)
|
|
865
|
+
entry_hash: str
|
|
866
|
+
|
|
867
|
+
|
|
868
|
+
class AuditBreak(Model):
|
|
869
|
+
"""The first audit entry of a chain that does not verify."""
|
|
870
|
+
|
|
871
|
+
seq: int
|
|
872
|
+
reason: str
|
|
873
|
+
|
|
874
|
+
|
|
875
|
+
class AuditVerification(Model):
|
|
876
|
+
"""Result of recomputing a Domain's audit hash chain."""
|
|
877
|
+
|
|
878
|
+
domain_id: str
|
|
879
|
+
intact: bool
|
|
880
|
+
checked: int
|
|
881
|
+
after_seq: int
|
|
882
|
+
last_seq: int | None = None
|
|
883
|
+
first_broken: AuditBreak | None = None
|
|
884
|
+
|
|
885
|
+
|
|
886
|
+
# Proposals --------------------------------------------------------------------------------
|
|
887
|
+
|
|
888
|
+
|
|
889
|
+
class ProposalCreate(RequestModel):
|
|
890
|
+
"""A new Proposal: merge `source_ref` into `target_ref`."""
|
|
891
|
+
|
|
892
|
+
source_ref: str = Field(min_length=1, max_length=200)
|
|
893
|
+
target_ref: str = Field(min_length=1, max_length=200)
|
|
894
|
+
title: str = Field(min_length=1, max_length=300)
|
|
895
|
+
description: str | None = Field(default=None, max_length=20000)
|
|
896
|
+
|
|
897
|
+
|
|
898
|
+
class ProposalUpdate(RequestModel):
|
|
899
|
+
"""Title and description; omitted members are left unchanged."""
|
|
900
|
+
|
|
901
|
+
title: str | None = Field(default=None, min_length=1, max_length=300)
|
|
902
|
+
description: str | None = Field(default=None, max_length=20000)
|
|
903
|
+
|
|
904
|
+
|
|
905
|
+
class ReviewIn(RequestModel):
|
|
906
|
+
"""A review of a Proposal."""
|
|
907
|
+
|
|
908
|
+
verdict: ReviewVerdict
|
|
909
|
+
comment: str | None = Field(default=None, max_length=20000)
|
|
910
|
+
|
|
911
|
+
|
|
912
|
+
class MergeIn(RequestModel):
|
|
913
|
+
"""Options of a merge."""
|
|
914
|
+
|
|
915
|
+
message: str | None = Field(default=None, max_length=2000)
|
|
916
|
+
|
|
917
|
+
|
|
918
|
+
class ResolutionIn(RequestModel):
|
|
919
|
+
"""A decision for one Conflict (`value` or per-member `values` for `custom` only)."""
|
|
920
|
+
|
|
921
|
+
choice: str
|
|
922
|
+
value: JsonValue = None
|
|
923
|
+
values: dict[str, JsonValue] | None = None
|
|
924
|
+
|
|
925
|
+
|
|
926
|
+
class FingerprintedResolutionIn(ResolutionIn):
|
|
927
|
+
"""A decision for the Conflict with this fingerprint."""
|
|
928
|
+
|
|
929
|
+
fingerprint: str = Field(pattern=r"^[0-9a-f]{64}$")
|
|
930
|
+
|
|
931
|
+
|
|
932
|
+
class ResolveManyIn(RequestModel):
|
|
933
|
+
"""Decisions for several Conflicts at once."""
|
|
934
|
+
|
|
935
|
+
resolutions: list[FingerprintedResolutionIn]
|
|
936
|
+
|
|
937
|
+
|
|
938
|
+
class Check(Model):
|
|
939
|
+
"""One Proposal check."""
|
|
940
|
+
|
|
941
|
+
name: str
|
|
942
|
+
status: CheckStatus
|
|
943
|
+
message: str | None = None
|
|
944
|
+
|
|
945
|
+
|
|
946
|
+
class Review(Model):
|
|
947
|
+
id: str
|
|
948
|
+
reviewer: str
|
|
949
|
+
verdict: ReviewVerdict
|
|
950
|
+
comment: str | None
|
|
951
|
+
at: datetime
|
|
952
|
+
|
|
953
|
+
|
|
954
|
+
class Approval(Model):
|
|
955
|
+
"""Review state against the protection of the target branch."""
|
|
956
|
+
|
|
957
|
+
protected: bool
|
|
958
|
+
required: int
|
|
959
|
+
approvals: int
|
|
960
|
+
approvers: list[str]
|
|
961
|
+
changes_requested: list[str]
|
|
962
|
+
satisfied: bool
|
|
963
|
+
|
|
964
|
+
|
|
965
|
+
class Resolution(Model):
|
|
966
|
+
"""A stored resolution; `matched` is false when its Conflict is gone from the attempt."""
|
|
967
|
+
|
|
968
|
+
fingerprint: str
|
|
969
|
+
choice: str
|
|
970
|
+
value: JsonValue = None
|
|
971
|
+
values: dict[str, JsonValue] | None = None
|
|
972
|
+
resolver: str
|
|
973
|
+
at: datetime
|
|
974
|
+
matched: bool = True
|
|
975
|
+
|
|
976
|
+
|
|
977
|
+
class Attempt(Model):
|
|
978
|
+
"""A merge attempt and the resolutions of its Proposal."""
|
|
979
|
+
|
|
980
|
+
id: str
|
|
981
|
+
proposal: str
|
|
982
|
+
status: str
|
|
983
|
+
result: str | None
|
|
984
|
+
base: str | None
|
|
985
|
+
ours: str
|
|
986
|
+
theirs: str
|
|
987
|
+
virtual_base: bool
|
|
988
|
+
created_at: datetime
|
|
989
|
+
created_by: str | None
|
|
990
|
+
revision: str | None
|
|
991
|
+
policy: dict[str, Any]
|
|
992
|
+
open_conflicts: int
|
|
993
|
+
resolved_conflicts: int
|
|
994
|
+
counts: dict[str, dict[str, int]]
|
|
995
|
+
recomputed: bool
|
|
996
|
+
stale: bool
|
|
997
|
+
resolutions: list[Resolution]
|
|
998
|
+
unmatched_resolutions: list[str]
|
|
999
|
+
auto_resolutions: list[dict[str, Any]]
|
|
1000
|
+
|
|
1001
|
+
|
|
1002
|
+
class ConflictItem(Model):
|
|
1003
|
+
"""A Conflict: `id`, `status` and the members the merge engine reports."""
|
|
1004
|
+
|
|
1005
|
+
model_config = ConfigDict(extra="allow")
|
|
1006
|
+
|
|
1007
|
+
id: str
|
|
1008
|
+
status: ConflictStatus
|
|
1009
|
+
resolution: dict[str, Any] | None = None
|
|
1010
|
+
|
|
1011
|
+
|
|
1012
|
+
class Conflicts(Attempt):
|
|
1013
|
+
"""An attempt with one page of its Conflicts; walkable with the pagination helpers."""
|
|
1014
|
+
|
|
1015
|
+
conflicts: list[ConflictItem]
|
|
1016
|
+
next_cursor: str | None = None
|
|
1017
|
+
|
|
1018
|
+
@property
|
|
1019
|
+
def items(self) -> list[ConflictItem]:
|
|
1020
|
+
return self.conflicts
|
|
1021
|
+
|
|
1022
|
+
|
|
1023
|
+
class Proposal(Model):
|
|
1024
|
+
"""A Proposal as listed."""
|
|
1025
|
+
|
|
1026
|
+
id: str
|
|
1027
|
+
title: str
|
|
1028
|
+
description: str | None
|
|
1029
|
+
source_ref: str
|
|
1030
|
+
target_ref: str
|
|
1031
|
+
origin: ProposalOrigin
|
|
1032
|
+
status: ProposalStatus
|
|
1033
|
+
author: str
|
|
1034
|
+
created_at: datetime
|
|
1035
|
+
updated_at: datetime | None
|
|
1036
|
+
merged_revision: str | None
|
|
1037
|
+
policy: dict[str, Any]
|
|
1038
|
+
checks: list[Check]
|
|
1039
|
+
|
|
1040
|
+
|
|
1041
|
+
class ProposalDetail(Proposal):
|
|
1042
|
+
"""A Proposal with its reviews, approval state and last merge attempt."""
|
|
1043
|
+
|
|
1044
|
+
reviews: list[Review]
|
|
1045
|
+
approval: Approval
|
|
1046
|
+
attempt: Attempt | None
|
|
1047
|
+
|
|
1048
|
+
|
|
1049
|
+
class ReviewResult(Model):
|
|
1050
|
+
review: Review
|
|
1051
|
+
approval: Approval
|
|
1052
|
+
|
|
1053
|
+
|
|
1054
|
+
class MergeResult(Model):
|
|
1055
|
+
"""A merged Proposal."""
|
|
1056
|
+
|
|
1057
|
+
status: Literal["merged"] = "merged"
|
|
1058
|
+
result: str
|
|
1059
|
+
revision: str
|
|
1060
|
+
proposal: Proposal
|
|
1061
|
+
attempt: Conflicts
|
|
1062
|
+
source_deleted: bool
|
|
1063
|
+
replayed: bool
|
|
1064
|
+
|
|
1065
|
+
|
|
1066
|
+
# Agents -----------------------------------------------------------------------------------
|
|
1067
|
+
|
|
1068
|
+
|
|
1069
|
+
class ContextRequest(RequestModel):
|
|
1070
|
+
"""What an agent needs to reason about: seed Records and how much around them."""
|
|
1071
|
+
|
|
1072
|
+
branch: str | None = None
|
|
1073
|
+
at: str | None = None
|
|
1074
|
+
refs: list[str] | None = None
|
|
1075
|
+
query: dict[str, JsonValue] | None = None
|
|
1076
|
+
search: str | None = Field(default=None, min_length=1)
|
|
1077
|
+
budget_tokens: int | None = Field(default=None, ge=1)
|
|
1078
|
+
depth: int | None = Field(default=None, ge=0)
|
|
1079
|
+
relations: list[str] | None = None
|
|
1080
|
+
include_schema: bool = True
|
|
1081
|
+
include_recent_changes: bool = True
|
|
1082
|
+
include_lineage: bool = True
|
|
1083
|
+
since: str | None = None
|
|
1084
|
+
format: ContextFormat = "json"
|
|
1085
|
+
|
|
1086
|
+
|
|
1087
|
+
class ContextPack(Model):
|
|
1088
|
+
"""A context pack in JSON; sections are kept as plain data."""
|
|
1089
|
+
|
|
1090
|
+
domain: str
|
|
1091
|
+
branch: str
|
|
1092
|
+
revision: str
|
|
1093
|
+
etag: str
|
|
1094
|
+
seeds: list[dict[str, Any]]
|
|
1095
|
+
neighbors: list[dict[str, Any]]
|
|
1096
|
+
links: dict[str, list[dict[str, Any]]]
|
|
1097
|
+
schema_: dict[str, Any] | None = Field(default=None, alias="schema")
|
|
1098
|
+
recent_changes: list[dict[str, Any]] | None = None
|
|
1099
|
+
lineage: list[dict[str, Any]] | None = None
|
|
1100
|
+
since: str | None = None
|
|
1101
|
+
budget: dict[str, Any]
|
|
1102
|
+
|
|
1103
|
+
|
|
1104
|
+
class Explain(Model):
|
|
1105
|
+
"""An explained Record: where its values and Links came from; sections are plain data."""
|
|
1106
|
+
|
|
1107
|
+
domain: str
|
|
1108
|
+
branch: str
|
|
1109
|
+
revision: str
|
|
1110
|
+
record: dict[str, Any]
|
|
1111
|
+
created: dict[str, Any] | None = None
|
|
1112
|
+
attributes: list[dict[str, Any]]
|
|
1113
|
+
links: list[dict[str, Any]]
|
|
1114
|
+
links_truncated: bool
|
|
1115
|
+
history: dict[str, Any]
|
|
1116
|
+
proposals: list[dict[str, Any]]
|
|
1117
|
+
|
|
1118
|
+
|
|
1119
|
+
class Provenance(Model):
|
|
1120
|
+
"""Where a change came from."""
|
|
1121
|
+
|
|
1122
|
+
id: str
|
|
1123
|
+
origin_type: str
|
|
1124
|
+
origin: dict[str, JsonValue]
|
|
1125
|
+
actor: str
|
|
1126
|
+
at: datetime
|
|
1127
|
+
|
|
1128
|
+
|
|
1129
|
+
class AttributeChange(Model):
|
|
1130
|
+
name: str
|
|
1131
|
+
before: JsonValue = None
|
|
1132
|
+
after: JsonValue = None
|
|
1133
|
+
provenance: Provenance | None = None
|
|
1134
|
+
|
|
1135
|
+
|
|
1136
|
+
class RecordChange(Model):
|
|
1137
|
+
"""One change of a Record in one Revision."""
|
|
1138
|
+
|
|
1139
|
+
revision: str
|
|
1140
|
+
generation: int
|
|
1141
|
+
at: datetime
|
|
1142
|
+
author: str
|
|
1143
|
+
on_behalf_of: str | None = None
|
|
1144
|
+
message: str
|
|
1145
|
+
origin: dict[str, JsonValue]
|
|
1146
|
+
change: ChangeKind
|
|
1147
|
+
attributes: list[AttributeChange]
|
|
1148
|
+
provenance: Provenance | None = None
|
|
1149
|
+
|
|
1150
|
+
|
|
1151
|
+
class RecordHistory(Served):
|
|
1152
|
+
branch: str
|
|
1153
|
+
kind: str
|
|
1154
|
+
key: str
|
|
1155
|
+
items: list[RecordChange]
|
|
1156
|
+
next_cursor: str | None = None
|
|
1157
|
+
|
|
1158
|
+
|
|
1159
|
+
class LineageProposal(Model):
|
|
1160
|
+
id: str
|
|
1161
|
+
title: str
|
|
1162
|
+
status: str
|
|
1163
|
+
merged_revision: str | None = None
|
|
1164
|
+
|
|
1165
|
+
|
|
1166
|
+
class ExtractionEvidence(Model):
|
|
1167
|
+
job_id: str
|
|
1168
|
+
document_id: str
|
|
1169
|
+
span_start: int
|
|
1170
|
+
span_end: int
|
|
1171
|
+
model_id: str
|
|
1172
|
+
prompt_version: str
|
|
1173
|
+
|
|
1174
|
+
|
|
1175
|
+
class ValueLineage(Model):
|
|
1176
|
+
"""The current value of an Attribute and the change behind it."""
|
|
1177
|
+
|
|
1178
|
+
name: str
|
|
1179
|
+
value: JsonValue = None
|
|
1180
|
+
provenance: Provenance | None = None
|
|
1181
|
+
revision: str | None = None
|
|
1182
|
+
revision_at: datetime | None = None
|
|
1183
|
+
author: str | None = None
|
|
1184
|
+
message: str | None = None
|
|
1185
|
+
source: str | None = None
|
|
1186
|
+
operation: str | None = None
|
|
1187
|
+
extraction: ExtractionEvidence | None = None
|
|
1188
|
+
proposal: LineageProposal | None = None
|
|
1189
|
+
|
|
1190
|
+
|
|
1191
|
+
class RecordLineage(Served):
|
|
1192
|
+
branch: str
|
|
1193
|
+
kind: str
|
|
1194
|
+
key: str
|
|
1195
|
+
created: RecordChange | None = None
|
|
1196
|
+
changes: int
|
|
1197
|
+
changes_truncated: bool = False
|
|
1198
|
+
attributes: list[ValueLineage]
|
|
1199
|
+
sources: list[str]
|
|
1200
|
+
proposals: list[LineageProposal]
|
|
1201
|
+
|
|
1202
|
+
|
|
1203
|
+
class HealthSummary(Model):
|
|
1204
|
+
"""Totals of a health report; `ok` is false when there are violations, orphan Links,
|
|
1205
|
+
required-relation gaps or duplicates of unique Attributes."""
|
|
1206
|
+
|
|
1207
|
+
ok: bool
|
|
1208
|
+
kinds: int
|
|
1209
|
+
records: int
|
|
1210
|
+
links: int
|
|
1211
|
+
invalid_records: int
|
|
1212
|
+
orphan_links: int
|
|
1213
|
+
required_relation_gaps: int
|
|
1214
|
+
duplicate_groups: int
|
|
1215
|
+
stale_kinds: int
|
|
1216
|
+
sampled_kinds: int
|
|
1217
|
+
|
|
1218
|
+
|
|
1219
|
+
class DomainHealth(Model):
|
|
1220
|
+
"""Health of a Domain at the head of a branch; Kind and Relation sections are plain data."""
|
|
1221
|
+
|
|
1222
|
+
revision: str
|
|
1223
|
+
served_from: ServedFrom
|
|
1224
|
+
domain: str
|
|
1225
|
+
branch: str
|
|
1226
|
+
summary: HealthSummary
|
|
1227
|
+
kinds: list[dict[str, Any]]
|
|
1228
|
+
relations: list[dict[str, Any]]
|
|
1229
|
+
|
|
1230
|
+
|
|
1231
|
+
class KindHealthReport(Model):
|
|
1232
|
+
"""Health of one Kind with the Relations it takes part in."""
|
|
1233
|
+
|
|
1234
|
+
revision: str
|
|
1235
|
+
served_from: ServedFrom
|
|
1236
|
+
domain: str
|
|
1237
|
+
branch: str
|
|
1238
|
+
summary: HealthSummary
|
|
1239
|
+
health: dict[str, Any]
|
|
1240
|
+
relations: list[dict[str, Any]]
|
|
1241
|
+
|
|
1242
|
+
|
|
1243
|
+
class RevisionEvent(Model):
|
|
1244
|
+
"""A Revision committed on a branch."""
|
|
1245
|
+
|
|
1246
|
+
sequence: int | None = None
|
|
1247
|
+
type: str
|
|
1248
|
+
domain: str
|
|
1249
|
+
branch: str
|
|
1250
|
+
revision: str
|
|
1251
|
+
parents: list[str]
|
|
1252
|
+
generation: int
|
|
1253
|
+
author: str
|
|
1254
|
+
on_behalf_of: str | None = None
|
|
1255
|
+
message: str
|
|
1256
|
+
origin: dict[str, JsonValue]
|
|
1257
|
+
stats: dict[str, JsonValue]
|
|
1258
|
+
created_at: datetime
|
|
1259
|
+
kinds: dict[str, int]
|
|
1260
|
+
relations: dict[str, int]
|
|
1261
|
+
schema_changed: bool
|
|
1262
|
+
keys: dict[str, list[str]]
|
|
1263
|
+
keys_truncated: bool
|
|
1264
|
+
|
|
1265
|
+
|
|
1266
|
+
class ChangesFeed(Model):
|
|
1267
|
+
"""Revision events after a point; `next_since` continues the feed while `more` is true."""
|
|
1268
|
+
|
|
1269
|
+
branch: str
|
|
1270
|
+
head: str
|
|
1271
|
+
since: str | None = None
|
|
1272
|
+
items: list[RevisionEvent]
|
|
1273
|
+
next_since: str
|
|
1274
|
+
more: bool
|
|
1275
|
+
|
|
1276
|
+
|
|
1277
|
+
# Webhooks ---------------------------------------------------------------------------------
|
|
1278
|
+
|
|
1279
|
+
|
|
1280
|
+
class CreateWebhook(RequestModel):
|
|
1281
|
+
"""A webhook for events of a Domain, optionally narrowed to some branches."""
|
|
1282
|
+
|
|
1283
|
+
url: str = Field(min_length=1, max_length=2048)
|
|
1284
|
+
events: list[str] | None = None
|
|
1285
|
+
branches: list[str] | None = None
|
|
1286
|
+
description: str | None = Field(default=None, max_length=500)
|
|
1287
|
+
clearances: list[str] | None = None
|
|
1288
|
+
"""Sensitivity tags (`pii`, `confidential`, `restricted`) whose values the webhook receives
|
|
1289
|
+
in clear; others are masked."""
|
|
1290
|
+
|
|
1291
|
+
|
|
1292
|
+
class UpdateWebhook(RequestModel):
|
|
1293
|
+
"""The new clearances of a webhook, replacing the current ones."""
|
|
1294
|
+
|
|
1295
|
+
clearances: list[str]
|
|
1296
|
+
|
|
1297
|
+
|
|
1298
|
+
class Webhook(Model):
|
|
1299
|
+
id: str
|
|
1300
|
+
url: str
|
|
1301
|
+
description: str | None = None
|
|
1302
|
+
events: list[str]
|
|
1303
|
+
branches: list[str] | None = None
|
|
1304
|
+
clearances: list[str] = Field(default_factory=list)
|
|
1305
|
+
created_by: str
|
|
1306
|
+
created_at: datetime
|
|
1307
|
+
|
|
1308
|
+
|
|
1309
|
+
class WebhookCreated(Webhook):
|
|
1310
|
+
"""A new webhook: `secret` signs deliveries and is returned once."""
|
|
1311
|
+
|
|
1312
|
+
secret: str
|
|
1313
|
+
|
|
1314
|
+
|
|
1315
|
+
class WebhookList(Model):
|
|
1316
|
+
items: list[Webhook]
|
|
1317
|
+
|
|
1318
|
+
|
|
1319
|
+
# Sources ----------------------------------------------------------------------------------
|
|
1320
|
+
|
|
1321
|
+
|
|
1322
|
+
class SyncRequest(RequestModel):
|
|
1323
|
+
"""A synchronization of a Source; a dry run records the delta without writing."""
|
|
1324
|
+
|
|
1325
|
+
dry_run: bool = False
|
|
1326
|
+
|
|
1327
|
+
|
|
1328
|
+
class SyncQueued(Model):
|
|
1329
|
+
"""The queued (or already pending) synchronization job."""
|
|
1330
|
+
|
|
1331
|
+
job_id: int
|
|
1332
|
+
source: str
|
|
1333
|
+
api_name: str
|
|
1334
|
+
dry_run: bool
|
|
1335
|
+
|
|
1336
|
+
|
|
1337
|
+
class SourceRun(Model):
|
|
1338
|
+
id: str
|
|
1339
|
+
source: str
|
|
1340
|
+
status: str
|
|
1341
|
+
started_at: datetime | None = None
|
|
1342
|
+
finished_at: datetime | None = None
|
|
1343
|
+
stats: dict[str, JsonValue]
|
|
1344
|
+
revision: str | None = None
|
|
1345
|
+
proposal_id: str | None = None
|
|
1346
|
+
error: dict[str, JsonValue] | None = None
|
|
1347
|
+
|
|
1348
|
+
|
|
1349
|
+
# Documents --------------------------------------------------------------------------------
|
|
1350
|
+
|
|
1351
|
+
|
|
1352
|
+
class DocumentFromUrl(RequestModel):
|
|
1353
|
+
"""A document fetched by the server from an allowlisted URL."""
|
|
1354
|
+
|
|
1355
|
+
url: str = Field(min_length=1, max_length=2000)
|
|
1356
|
+
filename: str | None = Field(default=None, max_length=255)
|
|
1357
|
+
extract: bool | None = None
|
|
1358
|
+
tiers: list[int] | None = None
|
|
1359
|
+
|
|
1360
|
+
|
|
1361
|
+
class Document(Model):
|
|
1362
|
+
"""A stored document; `text_sha256` is set once Scribe parsed its text."""
|
|
1363
|
+
|
|
1364
|
+
id: str
|
|
1365
|
+
filename: str
|
|
1366
|
+
media_type: str
|
|
1367
|
+
sha256: str
|
|
1368
|
+
size: int
|
|
1369
|
+
text_sha256: str | None = None
|
|
1370
|
+
metadata: dict[str, JsonValue]
|
|
1371
|
+
created_at: datetime | None = None
|
|
1372
|
+
created_by: str
|
|
1373
|
+
|
|
1374
|
+
|
|
1375
|
+
class ExtractionJob(Model):
|
|
1376
|
+
id: str
|
|
1377
|
+
status: str
|
|
1378
|
+
model: str
|
|
1379
|
+
prompt_version: str
|
|
1380
|
+
branch: str | None = None
|
|
1381
|
+
proposal_id: str | None = None
|
|
1382
|
+
created_at: datetime | None = None
|
|
1383
|
+
finished_at: datetime | None = None
|
|
1384
|
+
usage: dict[str, JsonValue]
|
|
1385
|
+
stats: dict[str, JsonValue]
|
|
1386
|
+
error: dict[str, JsonValue] | None = None
|
|
1387
|
+
|
|
1388
|
+
|
|
1389
|
+
class DocumentDetail(Document):
|
|
1390
|
+
"""A document with its extraction jobs, newest first."""
|
|
1391
|
+
|
|
1392
|
+
extraction_jobs: list[ExtractionJob]
|
|
1393
|
+
|
|
1394
|
+
|
|
1395
|
+
class DocumentText(Model):
|
|
1396
|
+
"""The parsed text of a document; extraction evidence offsets index it."""
|
|
1397
|
+
|
|
1398
|
+
document_id: str
|
|
1399
|
+
text_sha256: str
|
|
1400
|
+
text: str
|
|
1401
|
+
length: int
|
|
1402
|
+
truncated: bool
|
|
1403
|
+
|
|
1404
|
+
|
|
1405
|
+
class DocumentUploaded(Model):
|
|
1406
|
+
"""The stored document; `duplicate` when the same bytes were stored before."""
|
|
1407
|
+
|
|
1408
|
+
document: Document
|
|
1409
|
+
duplicate: bool
|
|
1410
|
+
job_id: int | None = None
|
|
1411
|
+
|
|
1412
|
+
|
|
1413
|
+
# Extraction items -------------------------------------------------------------------------
|
|
1414
|
+
|
|
1415
|
+
|
|
1416
|
+
class ProposalItem(Model):
|
|
1417
|
+
"""One extracted Record, value or Link of an extraction Proposal."""
|
|
1418
|
+
|
|
1419
|
+
id: str
|
|
1420
|
+
type: ItemType
|
|
1421
|
+
seq: int
|
|
1422
|
+
decision: ItemDecisionValue
|
|
1423
|
+
confidence: float | None
|
|
1424
|
+
tier: int | None
|
|
1425
|
+
model: str | None
|
|
1426
|
+
kind: str | None = None
|
|
1427
|
+
relation: str | None = None
|
|
1428
|
+
key: str | None = None
|
|
1429
|
+
attribute: str | None = None
|
|
1430
|
+
value: JsonValue = None
|
|
1431
|
+
current: JsonValue = None
|
|
1432
|
+
from_key: str | None = Field(default=None, alias="from")
|
|
1433
|
+
to_key: str | None = Field(default=None, alias="to")
|
|
1434
|
+
values: dict[str, JsonValue] = Field(default_factory=dict)
|
|
1435
|
+
subject: dict[str, JsonValue]
|
|
1436
|
+
evidence: dict[str, JsonValue] | None
|
|
1437
|
+
match: dict[str, JsonValue] | None
|
|
1438
|
+
edits: dict[str, JsonValue] | None
|
|
1439
|
+
decided_by: str | None
|
|
1440
|
+
decided_at: datetime | None
|
|
1441
|
+
not_applied: str | None = None
|
|
1442
|
+
|
|
1443
|
+
|
|
1444
|
+
class ItemSelection(RequestModel):
|
|
1445
|
+
"""Items matching every given filter."""
|
|
1446
|
+
|
|
1447
|
+
status: list[ItemDecisionValue] | None = None
|
|
1448
|
+
kind: list[str] | None = None
|
|
1449
|
+
type: list[ItemType] | None = None
|
|
1450
|
+
min_confidence: float | None = Field(default=None, ge=0, le=1)
|
|
1451
|
+
max_confidence: float | None = Field(default=None, ge=0, le=1.01)
|
|
1452
|
+
|
|
1453
|
+
|
|
1454
|
+
class ItemDecision(RequestModel):
|
|
1455
|
+
"""A decision on one item; `edits` omitted keeps the stored ones, `{}` clears them."""
|
|
1456
|
+
|
|
1457
|
+
item: str = Field(min_length=1)
|
|
1458
|
+
decision: ItemDecisionValue
|
|
1459
|
+
edits: dict[str, JsonValue] | None = None
|
|
1460
|
+
|
|
1461
|
+
|
|
1462
|
+
class DecideItems(RequestModel):
|
|
1463
|
+
"""Decisions by item, and or one decision for every item matching `where`."""
|
|
1464
|
+
|
|
1465
|
+
decisions: list[ItemDecision] = Field(default_factory=list)
|
|
1466
|
+
where: ItemSelection | None = None
|
|
1467
|
+
decision: ItemDecisionValue | None = None
|
|
1468
|
+
|
|
1469
|
+
|
|
1470
|
+
class MaterializedItems(Model):
|
|
1471
|
+
"""What the extraction branch holds after the decisions."""
|
|
1472
|
+
|
|
1473
|
+
revision: str | None
|
|
1474
|
+
changes: int
|
|
1475
|
+
applied: int
|
|
1476
|
+
skipped: dict[str, str]
|
|
1477
|
+
|
|
1478
|
+
|
|
1479
|
+
class ItemsDecided(Model):
|
|
1480
|
+
proposal_id: str
|
|
1481
|
+
title: str
|
|
1482
|
+
checks: dict[str, JsonValue]
|
|
1483
|
+
items: list[ProposalItem]
|
|
1484
|
+
materialized: MaterializedItems
|
|
1485
|
+
|
|
1486
|
+
|
|
1487
|
+
class TypeGuess(Model):
|
|
1488
|
+
"""The type the samples of a suggested Attribute suggest."""
|
|
1489
|
+
|
|
1490
|
+
type: str
|
|
1491
|
+
constraints: dict[str, JsonValue]
|
|
1492
|
+
confidence: float
|
|
1493
|
+
reasons: list[str]
|
|
1494
|
+
|
|
1495
|
+
|
|
1496
|
+
class SuggestionDuplicate(Model):
|
|
1497
|
+
"""The element of the current schema a suggestion duplicates."""
|
|
1498
|
+
|
|
1499
|
+
section: Literal["kinds", "attributes", "relations"]
|
|
1500
|
+
api_name: str
|
|
1501
|
+
|
|
1502
|
+
|
|
1503
|
+
class SchemaSuggestion(Model):
|
|
1504
|
+
"""A Kind, Attribute or Relation extraction found outside the schema."""
|
|
1505
|
+
|
|
1506
|
+
id: str
|
|
1507
|
+
type: SuggestionType
|
|
1508
|
+
name: str
|
|
1509
|
+
kind: str | None = None
|
|
1510
|
+
target: str | None = None
|
|
1511
|
+
labels: list[str]
|
|
1512
|
+
support: int
|
|
1513
|
+
documents: int
|
|
1514
|
+
confidence: float
|
|
1515
|
+
status: ItemDecisionValue
|
|
1516
|
+
type_guess: TypeGuess | None = None
|
|
1517
|
+
samples: list[str]
|
|
1518
|
+
examples: list[dict[str, JsonValue]]
|
|
1519
|
+
sources: list[str]
|
|
1520
|
+
duplicate_of: SuggestionDuplicate | None = None
|
|
1521
|
+
withheld: bool = False
|
|
1522
|
+
proposal_id: str | None
|
|
1523
|
+
branch: str | None
|
|
1524
|
+
decided_by: str | None
|
|
1525
|
+
decided_at: datetime | None
|
|
1526
|
+
created_at: datetime | None
|
|
1527
|
+
updated_at: datetime | None
|
|
1528
|
+
|
|
1529
|
+
|
|
1530
|
+
class SuggestionDecision(RequestModel):
|
|
1531
|
+
"""A decision on one suggestion; `name` renames an accepted one."""
|
|
1532
|
+
|
|
1533
|
+
suggestion: str = Field(min_length=1)
|
|
1534
|
+
decision: ItemDecisionValue
|
|
1535
|
+
name: str | None = Field(default=None, max_length=100)
|
|
1536
|
+
|
|
1537
|
+
|
|
1538
|
+
class DecideSuggestions(RequestModel):
|
|
1539
|
+
decisions: list[SuggestionDecision] = Field(min_length=1)
|
|
1540
|
+
|
|
1541
|
+
|
|
1542
|
+
class SuggestionsMerged(Model):
|
|
1543
|
+
"""What merging stored variants of suggestions did."""
|
|
1544
|
+
|
|
1545
|
+
merged: int
|
|
1546
|
+
kept: int
|
|
1547
|
+
|
|
1548
|
+
|
|
1549
|
+
class SuggestionProposal(Model):
|
|
1550
|
+
"""The Proposal opened for the accepted suggestions."""
|
|
1551
|
+
|
|
1552
|
+
id: str
|
|
1553
|
+
title: str
|
|
1554
|
+
branch: str
|
|
1555
|
+
changes: list[ElementChange]
|
|
1556
|
+
|
|
1557
|
+
|
|
1558
|
+
class SuggestionsDecided(Model):
|
|
1559
|
+
suggestions: list[SchemaSuggestion]
|
|
1560
|
+
proposal: SuggestionProposal | None = None
|
|
1561
|
+
|
|
1562
|
+
|
|
1563
|
+
RecordItem.model_rebuild()
|
|
1564
|
+
|
|
1565
|
+
|
|
1566
|
+
# Agent trace ------------------------------------------------------------------------------
|
|
1567
|
+
|
|
1568
|
+
|
|
1569
|
+
class TraceRecordRef(Model):
|
|
1570
|
+
kind: str
|
|
1571
|
+
key: str
|
|
1572
|
+
|
|
1573
|
+
|
|
1574
|
+
class TraceLinkRef(Model):
|
|
1575
|
+
relation: str
|
|
1576
|
+
source: TraceRecordRef = Field(alias="from")
|
|
1577
|
+
to: TraceRecordRef
|
|
1578
|
+
|
|
1579
|
+
|
|
1580
|
+
class TraceRefs(Model):
|
|
1581
|
+
"""Records and Links a call touched; `truncated` when more were touched than kept."""
|
|
1582
|
+
|
|
1583
|
+
records: list[TraceRecordRef]
|
|
1584
|
+
links: list[TraceLinkRef]
|
|
1585
|
+
truncated: bool = False
|
|
1586
|
+
|
|
1587
|
+
|
|
1588
|
+
class TraceSession(Model):
|
|
1589
|
+
"""Summary of one agent session."""
|
|
1590
|
+
|
|
1591
|
+
session_id: str
|
|
1592
|
+
actor: str
|
|
1593
|
+
started_at: datetime
|
|
1594
|
+
last_at: datetime
|
|
1595
|
+
calls: int
|
|
1596
|
+
notes: int
|
|
1597
|
+
domains: list[str]
|
|
1598
|
+
tools: list[str]
|
|
1599
|
+
proposals: list[str]
|
|
1600
|
+
inferred: bool = False
|
|
1601
|
+
"""Grouped heuristically: the calls carried no session id."""
|
|
1602
|
+
|
|
1603
|
+
|
|
1604
|
+
class TraceSessionPage(Model):
|
|
1605
|
+
"""One page of sessions; `enabled` tells whether Keeper traces new requests."""
|
|
1606
|
+
|
|
1607
|
+
enabled: bool
|
|
1608
|
+
items: list[TraceSession]
|
|
1609
|
+
next_cursor: str | None = None
|
|
1610
|
+
|
|
1611
|
+
|
|
1612
|
+
class TraceStep(Model):
|
|
1613
|
+
"""One call or note of a session. `note` is opaque user text, not markup."""
|
|
1614
|
+
|
|
1615
|
+
step: int
|
|
1616
|
+
seq: int
|
|
1617
|
+
type: Literal["call", "note"]
|
|
1618
|
+
at: datetime
|
|
1619
|
+
actor: str
|
|
1620
|
+
principal_type: str | None = None
|
|
1621
|
+
domain_id: str | None = None
|
|
1622
|
+
tool: str | None = None
|
|
1623
|
+
method: str | None = None
|
|
1624
|
+
route: str | None = None
|
|
1625
|
+
status: int | None = None
|
|
1626
|
+
latency_ms: float | None = None
|
|
1627
|
+
cache: str | None = None
|
|
1628
|
+
branch: str | None = None
|
|
1629
|
+
query: dict[str, Any] | None = None
|
|
1630
|
+
result: dict[str, Any] | None = None
|
|
1631
|
+
refs: TraceRefs | None = None
|
|
1632
|
+
explain: dict[str, Any] | None = None
|
|
1633
|
+
note: str | None = None
|
|
1634
|
+
proposal_id: str | None = None
|
|
1635
|
+
request_id: str | None = None
|
|
1636
|
+
trace_id: str | None = None
|
|
1637
|
+
inferred: bool = False
|
|
1638
|
+
withheld: bool = False
|
|
1639
|
+
"""Another principal's step whose query, corrections and note text are not shown."""
|
|
1640
|
+
|
|
1641
|
+
|
|
1642
|
+
class TraceTimeline(Model):
|
|
1643
|
+
"""Steps of a session in order; `truncated` when it has more visible steps."""
|
|
1644
|
+
|
|
1645
|
+
session_id: str
|
|
1646
|
+
steps: list[TraceStep]
|
|
1647
|
+
truncated: bool
|
|
1648
|
+
|
|
1649
|
+
|
|
1650
|
+
class NoteRequest(RequestModel):
|
|
1651
|
+
"""A note attached to a session."""
|
|
1652
|
+
|
|
1653
|
+
text: str = Field(min_length=1)
|
|
1654
|
+
domain_id: str | None = Field(default=None, max_length=256)
|
|
1655
|
+
step: int | None = Field(default=None, ge=0)
|
|
1656
|
+
"""The agent's own step counter."""
|
|
1657
|
+
|
|
1658
|
+
|
|
1659
|
+
class NoteAccepted(Model):
|
|
1660
|
+
"""`recorded` is false when tracing is off or the note was dropped."""
|
|
1661
|
+
|
|
1662
|
+
recorded: bool
|