lorebase-store 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_store/__init__.py +1 -0
- lorebase_store/_migrations/env.py +106 -0
- lorebase_store/_migrations/script.py.mako +27 -0
- lorebase_store/_migrations/versions/0001_initial_schema.py +1071 -0
- lorebase_store/_migrations/versions/0002_merge_attempt_details.py +74 -0
- lorebase_store/_migrations/versions/0003_webhooks.py +127 -0
- lorebase_store/_migrations/versions/0004_defer_revision_references.py +38 -0
- lorebase_store/_migrations/versions/0005_security_definer_reads.py +396 -0
- lorebase_store/_migrations/versions/0006_api_key_usage.py +48 -0
- lorebase_store/_migrations/versions/0007_document_extraction.py +154 -0
- lorebase_store/_migrations/versions/0008_definer_argument_checks.py +178 -0
- lorebase_store/_migrations/versions/0009_api_key_networks.py +33 -0
- lorebase_store/_migrations/versions/0010_overlay_branches.py +582 -0
- lorebase_store/_migrations/versions/0011_branch_scoped_search_index.py +229 -0
- lorebase_store/_migrations/versions/0012_overlay_neighborhood_lookups.py +185 -0
- lorebase_store/_migrations/versions/0013_web_sessions.py +72 -0
- lorebase_store/_migrations/versions/0014_embedding_jobs.py +70 -0
- lorebase_store/_migrations/versions/0015_append_only_audit_log.py +83 -0
- lorebase_store/_migrations/versions/0016_sealed_webhook_keys.py +55 -0
- lorebase_store/_migrations/versions/0017_background_branch_copies.py +77 -0
- lorebase_store/_migrations/versions/0018_search_lexicon.py +477 -0
- lorebase_store/_migrations/versions/0019_vault_bindings.py +56 -0
- lorebase_store/_migrations/versions/0020_outbox_consumers.py +150 -0
- lorebase_store/_migrations/versions/0021_schema_suggestions.py +114 -0
- lorebase_store/_migrations/versions/0022_read_audit_and_shared_rate_limits.py +133 -0
- lorebase_store/_migrations/versions/0023_agent_trace.py +96 -0
- lorebase_store/_migrations/versions/0024_embedding_template_keys.py +205 -0
- lorebase_store/_migrations/versions/0025_snapshot_refs.py +35 -0
- lorebase_store/_migrations/versions/0026_plain_reverse_link_index.py +112 -0
- lorebase_store/_migrations/versions/0027_vault_rotation_and_tombstones.py +145 -0
- lorebase_store/_migrations/versions/0028_operation_deferred_events.py +71 -0
- lorebase_store/_migrations/versions/0029_agent_trace_sessions.py +108 -0
- lorebase_store/_migrations/versions/0030_path_fanout.py +31 -0
- lorebase_store/_migrations/versions/0031_branch_builds.py +91 -0
- lorebase_store/_migrations/versions/0032_lineage_revisions.py +35 -0
- lorebase_store/_migrations/versions/0033_vault_moved_tokens.py +30 -0
- lorebase_store/_migrations/versions/0034_inference_endpoint_modes.py +73 -0
- lorebase_store/_migrations/versions/0035_search_truncation.py +104 -0
- lorebase_store/_migrations/versions/0036_lineage_maps.py +325 -0
- lorebase_store/_migrations/versions/0037_bm25_search.py +346 -0
- lorebase_store/auth/__init__.py +52 -0
- lorebase_store/auth/api_keys.py +161 -0
- lorebase_store/auth/audit.py +382 -0
- lorebase_store/auth/checkpoints.py +185 -0
- lorebase_store/auth/grants.py +161 -0
- lorebase_store/auth/principals.py +158 -0
- lorebase_store/auth/scope.py +70 -0
- lorebase_store/auth/sessions.py +153 -0
- lorebase_store/auth/unit.py +53 -0
- lorebase_store/auth/usage.py +70 -0
- lorebase_store/blobs.py +73 -0
- lorebase_store/bm25.py +346 -0
- lorebase_store/bulk.py +147 -0
- lorebase_store/bundle.py +1830 -0
- lorebase_store/changefeed.py +261 -0
- lorebase_store/commit/__init__.py +42 -0
- lorebase_store/commit/changes.py +67 -0
- lorebase_store/commit/errors.py +60 -0
- lorebase_store/commit/migrations.py +335 -0
- lorebase_store/commit/pipeline.py +754 -0
- lorebase_store/commit/schema.py +123 -0
- lorebase_store/db.py +58 -0
- lorebase_store/definer.py +50 -0
- lorebase_store/digests.py +99 -0
- lorebase_store/embeddings.py +444 -0
- lorebase_store/endpoint_modes.py +59 -0
- lorebase_store/extraction/__init__.py +61 -0
- lorebase_store/extraction/items.py +187 -0
- lorebase_store/extraction/materialize.py +364 -0
- lorebase_store/extraction/proposal.py +65 -0
- lorebase_store/extraction/review.py +168 -0
- lorebase_store/gc.py +970 -0
- lorebase_store/indexes.py +1033 -0
- lorebase_store/jobs.py +651 -0
- lorebase_store/keyring.py +72 -0
- lorebase_store/layout.py +335 -0
- lorebase_store/materialize/__init__.py +103 -0
- lorebase_store/materialize/apply.py +436 -0
- lorebase_store/materialize/background.py +1184 -0
- lorebase_store/materialize/branch.py +268 -0
- lorebase_store/materialize/build.py +536 -0
- lorebase_store/materialize/derived.py +540 -0
- lorebase_store/materialize/lineage_backfill.py +502 -0
- lorebase_store/materialize/links.py +266 -0
- lorebase_store/materialize/overlay.py +292 -0
- lorebase_store/materialize/paths.py +315 -0
- lorebase_store/materialize/rebuild.py +326 -0
- lorebase_store/materialize/rows.py +153 -0
- lorebase_store/materialize/sensitivity.py +66 -0
- lorebase_store/materialize/snapshot.py +205 -0
- lorebase_store/materialize/stale.py +444 -0
- lorebase_store/materialize/verify.py +321 -0
- lorebase_store/merging/__init__.py +39 -0
- lorebase_store/merging/attempts.py +334 -0
- lorebase_store/merging/lineage.py +123 -0
- lorebase_store/merging/service.py +652 -0
- lorebase_store/migrations.py +133 -0
- lorebase_store/models/__init__.py +136 -0
- lorebase_store/models/base.py +64 -0
- lorebase_store/models/enums.py +132 -0
- lorebase_store/models/governance.py +268 -0
- lorebase_store/models/ingestion.py +288 -0
- lorebase_store/models/materialized.py +277 -0
- lorebase_store/models/proposals.py +195 -0
- lorebase_store/models/queues.py +87 -0
- lorebase_store/models/tracing.py +116 -0
- lorebase_store/models/versioning.py +222 -0
- lorebase_store/models/webhooks.py +96 -0
- lorebase_store/nodestore.py +384 -0
- lorebase_store/objectstore.py +162 -0
- lorebase_store/outbox_events.py +166 -0
- lorebase_store/profiling/__init__.py +58 -0
- lorebase_store/profiling/delimited.py +109 -0
- lorebase_store/profiling/postgres.py +211 -0
- lorebase_store/profiling/record_files.py +127 -0
- lorebase_store/profiling/records.py +92 -0
- lorebase_store/profiling/sql_databases.py +298 -0
- lorebase_store/ratelimit.py +129 -0
- lorebase_store/reads.py +1491 -0
- lorebase_store/replicas.py +434 -0
- lorebase_store/repositories/__init__.py +152 -0
- lorebase_store/repositories/changesets.py +59 -0
- lorebase_store/repositories/deferred_events.py +142 -0
- lorebase_store/repositories/documents.py +305 -0
- lorebase_store/repositories/domains.py +97 -0
- lorebase_store/repositories/events.py +205 -0
- lorebase_store/repositories/index_view.py +489 -0
- lorebase_store/repositories/invocations.py +68 -0
- lorebase_store/repositories/materializations.py +276 -0
- lorebase_store/repositories/materialized.py +1129 -0
- lorebase_store/repositories/merges.py +417 -0
- lorebase_store/repositories/proposal_items.py +199 -0
- lorebase_store/repositories/proposals.py +184 -0
- lorebase_store/repositories/provenance.py +77 -0
- lorebase_store/repositories/refs.py +452 -0
- lorebase_store/repositories/revisions.py +273 -0
- lorebase_store/repositories/scope.py +33 -0
- lorebase_store/repositories/source_runs.py +123 -0
- lorebase_store/repositories/suggestions.py +638 -0
- lorebase_store/runtime.py +87 -0
- lorebase_store/schema_cache.py +50 -0
- lorebase_store/settings.py +879 -0
- lorebase_store/storage.py +1478 -0
- lorebase_store/telemetry.py +142 -0
- lorebase_store/testing.py +108 -0
- lorebase_store/tracing.py +505 -0
- lorebase_store/traverse.py +578 -0
- lorebase_store/vault/__init__.py +50 -0
- lorebase_store/vault/keys.py +216 -0
- lorebase_store/vault/quotes.py +133 -0
- lorebase_store/vault/rotation.py +209 -0
- lorebase_store/vault/settings.py +152 -0
- lorebase_store/vault/store.py +987 -0
- lorebase_store/vault/transfer.py +558 -0
- lorebase_store/webhooks/__init__.py +24 -0
- lorebase_store/webhooks/delivery.py +637 -0
- lorebase_store/webhooks/keys.py +126 -0
- lorebase_store/webhooks/repository.py +142 -0
- lorebase_store/webhooks/settings.py +138 -0
- lorebase_store/webhooks/signing.py +50 -0
- lorebase_store-0.2.3.dist-info/METADATA +13 -0
- lorebase_store-0.2.3.dist-info/RECORD +163 -0
- lorebase_store-0.2.3.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
"""Principals, groups, grants, API keys, web sessions, Operation invocations and audit
|
|
2
|
+
(design 7.4, 10, 13)."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from datetime import date, datetime
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from sqlalchemy import (
|
|
10
|
+
BigInteger,
|
|
11
|
+
Double,
|
|
12
|
+
ForeignKey,
|
|
13
|
+
Identity,
|
|
14
|
+
Index,
|
|
15
|
+
Integer,
|
|
16
|
+
text,
|
|
17
|
+
)
|
|
18
|
+
from sqlalchemy.dialects.postgresql import ARRAY, CIDR, JSONB
|
|
19
|
+
from sqlalchemy.orm import Mapped, mapped_column
|
|
20
|
+
|
|
21
|
+
from lorebase_store.models.base import H32, Base, DomainKey, domain_fk, h32_check
|
|
22
|
+
from lorebase_store.models.enums import (
|
|
23
|
+
GRANT_SCOPE,
|
|
24
|
+
INVOCATION_STATUS,
|
|
25
|
+
PRINCIPAL_TYPE,
|
|
26
|
+
GrantScope,
|
|
27
|
+
InvocationStatus,
|
|
28
|
+
PrincipalType,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class Principal(Base):
|
|
33
|
+
"""User, service or API key identity. Ids are prefixed (`usr_`, `svc_`)."""
|
|
34
|
+
|
|
35
|
+
__tablename__ = "principals"
|
|
36
|
+
__table_args__ = (
|
|
37
|
+
Index(
|
|
38
|
+
None,
|
|
39
|
+
"external_subject",
|
|
40
|
+
unique=True,
|
|
41
|
+
postgresql_where=text("external_subject IS NOT NULL"),
|
|
42
|
+
),
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
id: Mapped[str] = mapped_column(primary_key=True)
|
|
46
|
+
type: Mapped[PrincipalType] = mapped_column(PRINCIPAL_TYPE)
|
|
47
|
+
display_name: Mapped[str]
|
|
48
|
+
external_subject: Mapped[str | None]
|
|
49
|
+
created_at: Mapped[datetime] = mapped_column(server_default=text("now()"))
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class Group(Base):
|
|
53
|
+
"""Group of principals, optionally mapped from an identity provider claim."""
|
|
54
|
+
|
|
55
|
+
__tablename__ = "groups"
|
|
56
|
+
__table_args__ = (
|
|
57
|
+
Index(
|
|
58
|
+
None,
|
|
59
|
+
"external_group",
|
|
60
|
+
unique=True,
|
|
61
|
+
postgresql_where=text("external_group IS NOT NULL"),
|
|
62
|
+
),
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
id: Mapped[str] = mapped_column(primary_key=True)
|
|
66
|
+
display_name: Mapped[str]
|
|
67
|
+
external_group: Mapped[str | None]
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class GroupMember(Base):
|
|
71
|
+
"""Membership of a principal in a group."""
|
|
72
|
+
|
|
73
|
+
__tablename__ = "group_members"
|
|
74
|
+
__table_args__ = (Index(None, "principal_id"),)
|
|
75
|
+
|
|
76
|
+
group_id: Mapped[str] = mapped_column(
|
|
77
|
+
ForeignKey("groups.id", ondelete="CASCADE"), primary_key=True
|
|
78
|
+
)
|
|
79
|
+
principal_id: Mapped[str] = mapped_column(
|
|
80
|
+
ForeignKey("principals.id", ondelete="CASCADE"), primary_key=True
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class Grant(Base):
|
|
85
|
+
"""Role of a subject (principal or group id) in a Domain, optionally scoped."""
|
|
86
|
+
|
|
87
|
+
__tablename__ = "grants"
|
|
88
|
+
__table_args__ = (domain_fk(), Index(None, "domain_id", "subject"), Index(None, "subject"))
|
|
89
|
+
|
|
90
|
+
id: Mapped[str] = mapped_column(primary_key=True)
|
|
91
|
+
domain_id: Mapped[str]
|
|
92
|
+
subject: Mapped[str]
|
|
93
|
+
role: Mapped[str]
|
|
94
|
+
scope_type: Mapped[GrantScope] = mapped_column(GRANT_SCOPE)
|
|
95
|
+
scope_ref: Mapped[str | None]
|
|
96
|
+
created_by: Mapped[str]
|
|
97
|
+
created_at: Mapped[datetime] = mapped_column(server_default=text("now()"))
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class ApiKey(Base):
|
|
101
|
+
"""API key of a service principal; only an HMAC of the secret is stored."""
|
|
102
|
+
|
|
103
|
+
__tablename__ = "api_keys"
|
|
104
|
+
__table_args__ = (h32_check("hash"),)
|
|
105
|
+
|
|
106
|
+
id: Mapped[str] = mapped_column(primary_key=True)
|
|
107
|
+
principal_id: Mapped[str] = mapped_column(
|
|
108
|
+
ForeignKey("principals.id", ondelete="CASCADE"), index=True
|
|
109
|
+
)
|
|
110
|
+
prefix: Mapped[str] = mapped_column(unique=True)
|
|
111
|
+
hash: Mapped[H32]
|
|
112
|
+
scopes: Mapped[Any] = mapped_column(JSONB, server_default=text("'[]'::jsonb"))
|
|
113
|
+
expires_at: Mapped[datetime | None]
|
|
114
|
+
last_used_at: Mapped[datetime | None]
|
|
115
|
+
revoked_at: Mapped[datetime | None]
|
|
116
|
+
created_at: Mapped[datetime] = mapped_column(server_default=text("now()"))
|
|
117
|
+
# Client networks the key may be used from; null: any address.
|
|
118
|
+
allowed_networks: Mapped[list[Any] | None] = mapped_column(ARRAY(CIDR))
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class ApiKeyUsage(Base):
|
|
122
|
+
"""Requests and context-pack tokens a key used on one UTC day (daily quotas)."""
|
|
123
|
+
|
|
124
|
+
__tablename__ = "api_key_usage"
|
|
125
|
+
|
|
126
|
+
api_key_id: Mapped[str] = mapped_column(
|
|
127
|
+
ForeignKey("api_keys.id", ondelete="CASCADE"), primary_key=True
|
|
128
|
+
)
|
|
129
|
+
day: Mapped[date] = mapped_column(primary_key=True)
|
|
130
|
+
requests: Mapped[int] = mapped_column(BigInteger, server_default=text("0"))
|
|
131
|
+
context_tokens: Mapped[int] = mapped_column(BigInteger, server_default=text("0"))
|
|
132
|
+
updated_at: Mapped[datetime] = mapped_column(server_default=text("now()"))
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class WebSession(Base):
|
|
136
|
+
"""Signed-in browser session of a user (Keeper as backend-for-frontend, design 13.2).
|
|
137
|
+
|
|
138
|
+
Only the SHA-256 digest of the session id is stored; `tokens` holds the
|
|
139
|
+
provider's refresh and ID tokens encrypted by Keeper. Installation-wide like `api_keys`.
|
|
140
|
+
"""
|
|
141
|
+
|
|
142
|
+
__tablename__ = "web_sessions"
|
|
143
|
+
__table_args__ = (h32_check("id_hash"),)
|
|
144
|
+
|
|
145
|
+
id_hash: Mapped[H32] = mapped_column(primary_key=True)
|
|
146
|
+
principal_id: Mapped[str] = mapped_column(
|
|
147
|
+
ForeignKey("principals.id", ondelete="CASCADE"), index=True
|
|
148
|
+
)
|
|
149
|
+
subject: Mapped[str]
|
|
150
|
+
external_groups: Mapped[Any] = mapped_column(JSONB, server_default=text("'[]'::jsonb"))
|
|
151
|
+
tokens: Mapped[bytes | None]
|
|
152
|
+
created_at: Mapped[datetime] = mapped_column(server_default=text("now()"))
|
|
153
|
+
last_seen_at: Mapped[datetime] = mapped_column(index=True)
|
|
154
|
+
expires_at: Mapped[datetime] = mapped_column(index=True)
|
|
155
|
+
refresh_at: Mapped[datetime | None]
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class OperationInvocation(Base):
|
|
159
|
+
"""One Operation execution; idempotent per principal and key."""
|
|
160
|
+
|
|
161
|
+
__tablename__ = "operation_invocations"
|
|
162
|
+
__table_args__ = (
|
|
163
|
+
domain_fk(),
|
|
164
|
+
Index(
|
|
165
|
+
"uq_operation_invocations_idempotency",
|
|
166
|
+
"domain_id",
|
|
167
|
+
"principal",
|
|
168
|
+
"idempotency_key",
|
|
169
|
+
unique=True,
|
|
170
|
+
postgresql_where=text("idempotency_key IS NOT NULL"),
|
|
171
|
+
),
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
domain_id: Mapped[DomainKey]
|
|
175
|
+
id: Mapped[str] = mapped_column(primary_key=True)
|
|
176
|
+
operation_id: Mapped[str]
|
|
177
|
+
principal: Mapped[str]
|
|
178
|
+
params: Mapped[dict[str, Any]]
|
|
179
|
+
status: Mapped[InvocationStatus] = mapped_column(INVOCATION_STATUS)
|
|
180
|
+
revision_id: Mapped[H32 | None]
|
|
181
|
+
proposal_id: Mapped[str | None]
|
|
182
|
+
idempotency_key: Mapped[str | None]
|
|
183
|
+
created_at: Mapped[datetime] = mapped_column(server_default=text("now()"))
|
|
184
|
+
duration_ms: Mapped[int | None] = mapped_column(Integer)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class OperationDeferredEvent(Base):
|
|
188
|
+
"""An `operation.<side effect>` event of an execution committed on a branch whose
|
|
189
|
+
Proposal is its review (agent writes): written to the outbox, and deleted here, in the
|
|
190
|
+
transaction of the merge that brings `revision` (the head after the execution) into the
|
|
191
|
+
target. Deleted with its branch when the branch is deleted unmerged."""
|
|
192
|
+
|
|
193
|
+
__tablename__ = "operation_deferred_events"
|
|
194
|
+
__table_args__ = (domain_fk(), Index(None, "domain_id", "branch"))
|
|
195
|
+
|
|
196
|
+
id: Mapped[int] = mapped_column(BigInteger, Identity(always=True), primary_key=True)
|
|
197
|
+
domain_id: Mapped[str]
|
|
198
|
+
branch: Mapped[str]
|
|
199
|
+
revision_id: Mapped[H32]
|
|
200
|
+
invocation_id: Mapped[str]
|
|
201
|
+
event_type: Mapped[str]
|
|
202
|
+
payload: Mapped[dict[str, Any]]
|
|
203
|
+
created_at: Mapped[datetime] = mapped_column(server_default=text("now()"))
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
class _AuditColumns:
|
|
207
|
+
"""Columns of a hash-chained audit table."""
|
|
208
|
+
|
|
209
|
+
seq: Mapped[int] = mapped_column(BigInteger, Identity(always=True), primary_key=True)
|
|
210
|
+
domain_id: Mapped[str | None]
|
|
211
|
+
at: Mapped[datetime] = mapped_column(server_default=text("now()"))
|
|
212
|
+
actor: Mapped[str]
|
|
213
|
+
action: Mapped[str]
|
|
214
|
+
target: Mapped[str | None]
|
|
215
|
+
details: Mapped[dict[str, Any]] = mapped_column(server_default=text("'{}'::jsonb"))
|
|
216
|
+
prev_hash: Mapped[H32 | None]
|
|
217
|
+
entry_hash: Mapped[H32]
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
class AuditEntry(_AuditColumns, Base):
|
|
221
|
+
"""Hash-chained audit entry: `entry_hash = sha256(prev_hash || canonical entry)`.
|
|
222
|
+
|
|
223
|
+
`domain_id` is null for installation-wide actions and has no foreign key, so audit
|
|
224
|
+
survives Domain deletion.
|
|
225
|
+
"""
|
|
226
|
+
|
|
227
|
+
__tablename__ = "audit_log"
|
|
228
|
+
__table_args__ = (
|
|
229
|
+
h32_check("entry_hash"),
|
|
230
|
+
Index(None, "domain_id", "seq"),
|
|
231
|
+
Index(None, "entry_hash", unique=True),
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
class AuditReadEntry(_AuditColumns, Base):
|
|
236
|
+
"""Hash-chained record of a read (`keeper.auth.audit.reads`), chained like `audit_log` but
|
|
237
|
+
in chains of its own, so sampled, best-effort read records never touch the chains of
|
|
238
|
+
actions."""
|
|
239
|
+
|
|
240
|
+
__tablename__ = "audit_read_log"
|
|
241
|
+
__table_args__ = (
|
|
242
|
+
h32_check("entry_hash"),
|
|
243
|
+
Index(None, "domain_id", "seq"),
|
|
244
|
+
Index(None, "entry_hash", unique=True),
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
class RateLimitBucket(Base):
|
|
249
|
+
"""Shared token bucket of Keeper's rate limits (`keeper.limits.rate.backend: postgres`).
|
|
250
|
+
|
|
251
|
+
UNLOGGED: buckets are lost on a crash of the server and start full again. `key` is the
|
|
252
|
+
SHA-256 of the bucket name, so client addresses and principal ids are not stored;
|
|
253
|
+
`full_at` (epoch seconds) is when the bucket will be full, after which the row is
|
|
254
|
+
redundant and may be deleted.
|
|
255
|
+
"""
|
|
256
|
+
|
|
257
|
+
__tablename__ = "rate_limit_buckets"
|
|
258
|
+
__table_args__ = (
|
|
259
|
+
h32_check("key"),
|
|
260
|
+
Index(None, "full_at"),
|
|
261
|
+
{"prefixes": ["UNLOGGED"]},
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
key: Mapped[H32] = mapped_column(primary_key=True)
|
|
265
|
+
tokens: Mapped[float] = mapped_column(Double)
|
|
266
|
+
updated: Mapped[float] = mapped_column(Double)
|
|
267
|
+
full_at: Mapped[float] = mapped_column(Double)
|
|
268
|
+
granted: Mapped[float] = mapped_column(Double)
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"""Source runs, documents, extraction, schema suggestions and the personal data vault (design
|
|
2
|
+
7.4, 9, 13.5)."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from sqlalchemy import (
|
|
10
|
+
BigInteger,
|
|
11
|
+
CheckConstraint,
|
|
12
|
+
Float,
|
|
13
|
+
ForeignKeyConstraint,
|
|
14
|
+
Identity,
|
|
15
|
+
Index,
|
|
16
|
+
Integer,
|
|
17
|
+
LargeBinary,
|
|
18
|
+
text,
|
|
19
|
+
)
|
|
20
|
+
from sqlalchemy.dialects.postgresql import JSONB
|
|
21
|
+
from sqlalchemy.orm import Mapped, mapped_column
|
|
22
|
+
|
|
23
|
+
from lorebase_store.models.base import H32, Base, DomainKey, domain_fk, h32_check
|
|
24
|
+
from lorebase_store.models.enums import RUN_STATUS, RunStatus
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SourceRun(Base):
|
|
28
|
+
"""One synchronization run of a structured Source."""
|
|
29
|
+
|
|
30
|
+
__tablename__ = "source_runs"
|
|
31
|
+
__table_args__ = (domain_fk(), Index(None, "domain_id", "source_id", "started_at"))
|
|
32
|
+
|
|
33
|
+
domain_id: Mapped[DomainKey]
|
|
34
|
+
id: Mapped[str] = mapped_column(primary_key=True)
|
|
35
|
+
source_id: Mapped[str]
|
|
36
|
+
status: Mapped[RunStatus] = mapped_column(RUN_STATUS)
|
|
37
|
+
started_at: Mapped[datetime | None]
|
|
38
|
+
finished_at: Mapped[datetime | None]
|
|
39
|
+
stats: Mapped[dict[str, Any]] = mapped_column(server_default=text("'{}'::jsonb"))
|
|
40
|
+
revision_id: Mapped[H32 | None]
|
|
41
|
+
proposal_id: Mapped[str | None]
|
|
42
|
+
error: Mapped[dict[str, Any] | None]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Document(Base):
|
|
46
|
+
"""Uploaded document; the bytes live in the blob store at `blob_ref`. One row per Domain
|
|
47
|
+
and content hash."""
|
|
48
|
+
|
|
49
|
+
__tablename__ = "documents"
|
|
50
|
+
__table_args__ = (
|
|
51
|
+
h32_check("sha256"),
|
|
52
|
+
domain_fk(),
|
|
53
|
+
Index(None, "domain_id", "sha256", unique=True),
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
domain_id: Mapped[DomainKey]
|
|
57
|
+
id: Mapped[str] = mapped_column(primary_key=True)
|
|
58
|
+
filename: Mapped[str]
|
|
59
|
+
media_type: Mapped[str]
|
|
60
|
+
sha256: Mapped[H32]
|
|
61
|
+
size: Mapped[int] = mapped_column(BigInteger)
|
|
62
|
+
blob_ref: Mapped[str]
|
|
63
|
+
text_hash: Mapped[H32 | None]
|
|
64
|
+
metadata_: Mapped[dict[str, Any]] = mapped_column(
|
|
65
|
+
"metadata", server_default=text("'{}'::jsonb")
|
|
66
|
+
)
|
|
67
|
+
created_at: Mapped[datetime] = mapped_column(server_default=text("now()"))
|
|
68
|
+
created_by: Mapped[str]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class DocumentBlob(Base):
|
|
72
|
+
"""Document bytes of the Postgres blob store backend, addressed by SHA-256 per Domain."""
|
|
73
|
+
|
|
74
|
+
__tablename__ = "document_blobs"
|
|
75
|
+
__table_args__ = (h32_check("sha256"), domain_fk())
|
|
76
|
+
|
|
77
|
+
domain_id: Mapped[DomainKey]
|
|
78
|
+
sha256: Mapped[H32] = mapped_column(primary_key=True)
|
|
79
|
+
content: Mapped[bytes] = mapped_column(LargeBinary)
|
|
80
|
+
size: Mapped[int] = mapped_column(BigInteger)
|
|
81
|
+
created_at: Mapped[datetime] = mapped_column(server_default=text("now()"))
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class DocumentChunk(Base):
|
|
85
|
+
"""Text span of a document used for extraction and evidence offsets."""
|
|
86
|
+
|
|
87
|
+
__tablename__ = "document_chunks"
|
|
88
|
+
__table_args__ = (
|
|
89
|
+
ForeignKeyConstraint(
|
|
90
|
+
["domain_id", "document_id"],
|
|
91
|
+
["documents.domain_id", "documents.id"],
|
|
92
|
+
ondelete="CASCADE",
|
|
93
|
+
),
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
domain_id: Mapped[DomainKey]
|
|
97
|
+
document_id: Mapped[str] = mapped_column(primary_key=True)
|
|
98
|
+
seq: Mapped[int] = mapped_column(Integer, primary_key=True)
|
|
99
|
+
start_offset: Mapped[int] = mapped_column(Integer)
|
|
100
|
+
end_offset: Mapped[int] = mapped_column(Integer)
|
|
101
|
+
text: Mapped[str]
|
|
102
|
+
chunk_hash: Mapped[H32]
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class ExtractionJob(Base):
|
|
106
|
+
"""Extraction over documents producing a Proposal."""
|
|
107
|
+
|
|
108
|
+
__tablename__ = "extraction_jobs"
|
|
109
|
+
__table_args__ = (domain_fk(), Index(None, "documents", postgresql_using="gin"))
|
|
110
|
+
|
|
111
|
+
domain_id: Mapped[DomainKey]
|
|
112
|
+
id: Mapped[str] = mapped_column(primary_key=True)
|
|
113
|
+
documents: Mapped[Any] = mapped_column(JSONB)
|
|
114
|
+
schema_hash: Mapped[H32]
|
|
115
|
+
model: Mapped[str]
|
|
116
|
+
prompt_version: Mapped[str]
|
|
117
|
+
status: Mapped[RunStatus] = mapped_column(RUN_STATUS)
|
|
118
|
+
budget: Mapped[dict[str, Any]] = mapped_column(server_default=text("'{}'::jsonb"))
|
|
119
|
+
usage: Mapped[dict[str, Any]] = mapped_column(server_default=text("'{}'::jsonb"))
|
|
120
|
+
proposal_id: Mapped[str | None]
|
|
121
|
+
created_at: Mapped[datetime] = mapped_column(server_default=text("now()"))
|
|
122
|
+
created_by: Mapped[str | None]
|
|
123
|
+
#: The extraction branch (`extract/<job id>`).
|
|
124
|
+
branch: Mapped[str | None]
|
|
125
|
+
started_at: Mapped[datetime | None]
|
|
126
|
+
finished_at: Mapped[datetime | None]
|
|
127
|
+
stats: Mapped[dict[str, Any]] = mapped_column(server_default=text("'{}'::jsonb"))
|
|
128
|
+
error: Mapped[dict[str, Any] | None]
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class ExtractionCacheEntry(Base):
|
|
132
|
+
"""Model output cached by content key; holds no Domain data beyond the key."""
|
|
133
|
+
|
|
134
|
+
__tablename__ = "extraction_cache"
|
|
135
|
+
__table_args__ = (h32_check("cache_key"),)
|
|
136
|
+
|
|
137
|
+
cache_key: Mapped[H32] = mapped_column(primary_key=True)
|
|
138
|
+
model: Mapped[str]
|
|
139
|
+
output: Mapped[dict[str, Any]]
|
|
140
|
+
usage: Mapped[dict[str, Any]]
|
|
141
|
+
created_at: Mapped[datetime] = mapped_column(server_default=text("now()"))
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class InferenceEndpointMode(Base):
|
|
145
|
+
"""Structured output mode that a chat endpoint was found to need (`json_schema`: 0,
|
|
146
|
+
`json_mode`: 1, `prompt`: 2), so a restarted process does not retry the stronger ones.
|
|
147
|
+
|
|
148
|
+
`key` is the SHA-256 of the endpoint, model and output settings: no address is stored in
|
|
149
|
+
the clear (a keyed digest when `inference.mode_memory_key` is set). Holds no Domain data.
|
|
150
|
+
"""
|
|
151
|
+
|
|
152
|
+
__tablename__ = "inference_endpoint_modes"
|
|
153
|
+
__table_args__ = (
|
|
154
|
+
h32_check("key"),
|
|
155
|
+
CheckConstraint("mode_index BETWEEN 0 AND 2", name="mode_index"),
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
key: Mapped[H32] = mapped_column(primary_key=True)
|
|
159
|
+
mode_index: Mapped[int] = mapped_column(Integer)
|
|
160
|
+
updated_at: Mapped[datetime] = mapped_column(server_default=text("now()"))
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class SchemaSuggestion(Base):
|
|
164
|
+
"""A new Kind, Attribute or Relation that extraction found outside the schema, aggregated
|
|
165
|
+
over documents by `fingerprint` (`lorebase_core.infer.suggestions`).
|
|
166
|
+
|
|
167
|
+
`observations` maps document ids to `{"n": count, "c": summed confidence}`, so extracting a
|
|
168
|
+
document again replaces its share instead of adding to it; `support` is the sum of the
|
|
169
|
+
counts. `examples` holds evidence (`document_id`, `chunk`, `start`, `end`, `quote`) and
|
|
170
|
+
`samples` distinct sample values, both bounded. Accepting a suggestion opens a schema change
|
|
171
|
+
Proposal (`proposal_id`, `branch`); nothing applies it to a schema by itself.
|
|
172
|
+
"""
|
|
173
|
+
|
|
174
|
+
__tablename__ = "schema_suggestions"
|
|
175
|
+
__table_args__ = (
|
|
176
|
+
domain_fk(),
|
|
177
|
+
Index(None, "domain_id", "fingerprint", unique=True),
|
|
178
|
+
Index(None, "domain_id", "status", "support"),
|
|
179
|
+
CheckConstraint(
|
|
180
|
+
"suggestion_type IN ('kind', 'attribute', 'relation')", name="suggestion_type"
|
|
181
|
+
),
|
|
182
|
+
CheckConstraint("status IN ('pending', 'accepted', 'rejected')", name="status"),
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
domain_id: Mapped[DomainKey]
|
|
186
|
+
id: Mapped[str] = mapped_column(primary_key=True)
|
|
187
|
+
fingerprint: Mapped[str]
|
|
188
|
+
suggestion_type: Mapped[str]
|
|
189
|
+
name: Mapped[str]
|
|
190
|
+
#: Owner Kind of an Attribute, from Kind of a Relation: a Kind id or `+<KindName>`.
|
|
191
|
+
kind_ref: Mapped[str | None]
|
|
192
|
+
#: To Kind of a Relation.
|
|
193
|
+
target_ref: Mapped[str | None]
|
|
194
|
+
labels: Mapped[Any] = mapped_column(JSONB, server_default=text("'[]'::jsonb"))
|
|
195
|
+
support: Mapped[int] = mapped_column(Integer, server_default=text("0"))
|
|
196
|
+
confidence: Mapped[float] = mapped_column(Float, server_default=text("0"))
|
|
197
|
+
observations: Mapped[dict[str, Any]] = mapped_column(server_default=text("'{}'::jsonb"))
|
|
198
|
+
examples: Mapped[Any] = mapped_column(JSONB, server_default=text("'[]'::jsonb"))
|
|
199
|
+
samples: Mapped[Any] = mapped_column(JSONB, server_default=text("'[]'::jsonb"))
|
|
200
|
+
sources: Mapped[Any] = mapped_column(JSONB, server_default=text("'[]'::jsonb"))
|
|
201
|
+
status: Mapped[str] = mapped_column(server_default=text("'pending'"))
|
|
202
|
+
decided_by: Mapped[str | None]
|
|
203
|
+
decided_at: Mapped[datetime | None]
|
|
204
|
+
proposal_id: Mapped[str | None]
|
|
205
|
+
branch: Mapped[str | None]
|
|
206
|
+
last_job_id: Mapped[str | None]
|
|
207
|
+
created_at: Mapped[datetime] = mapped_column(server_default=text("now()"))
|
|
208
|
+
updated_at: Mapped[datetime] = mapped_column(server_default=text("now()"))
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
class VaultEntry(Base):
|
|
212
|
+
"""Encrypted personal value referenced from Record objects by its token (`id`); erasure
|
|
213
|
+
clears the ciphertext and the fingerprint and sets `erased_at` (`lorebase_store.vault`).
|
|
214
|
+
|
|
215
|
+
The Kind, Record key and Attribute bind a token to the value it stands for; `fingerprint`
|
|
216
|
+
(a keyed hash of the value without the Record key) finds the rows erasure by value erases.
|
|
217
|
+
`key_id` names the encryption key and `fingerprint_key` the token key and scheme of the
|
|
218
|
+
fingerprint (`<token_key_id>/<scheme>`, null for rows written before key rotation).
|
|
219
|
+
"""
|
|
220
|
+
|
|
221
|
+
__tablename__ = "vault"
|
|
222
|
+
__table_args__ = (
|
|
223
|
+
domain_fk(),
|
|
224
|
+
Index(None, "domain_id", "kind_id", "record_key"),
|
|
225
|
+
Index(None, "domain_id", "kind_id", "attribute_id", "fingerprint"),
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
domain_id: Mapped[DomainKey]
|
|
229
|
+
id: Mapped[str] = mapped_column(primary_key=True)
|
|
230
|
+
kind_id: Mapped[str]
|
|
231
|
+
attribute_id: Mapped[str]
|
|
232
|
+
record_key: Mapped[str]
|
|
233
|
+
fingerprint: Mapped[bytes | None]
|
|
234
|
+
value_ciphertext: Mapped[bytes | None]
|
|
235
|
+
key_id: Mapped[str | None]
|
|
236
|
+
fingerprint_key: Mapped[str | None]
|
|
237
|
+
moved_to: Mapped[str | None]
|
|
238
|
+
created_at: Mapped[datetime] = mapped_column(server_default=text("now()"))
|
|
239
|
+
erased_at: Mapped[datetime | None]
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
class VaultTombstone(Base):
|
|
243
|
+
"""A value erased from the vault: the fingerprint of every value an erasure erased, of one
|
|
244
|
+
Record (`record_key`) or of every Record of the Kind (null), so a later write of it is
|
|
245
|
+
stored erased or refused (`vault.erased_values`). Holds no value."""
|
|
246
|
+
|
|
247
|
+
__tablename__ = "vault_tombstones"
|
|
248
|
+
__table_args__ = (
|
|
249
|
+
domain_fk(),
|
|
250
|
+
Index(None, "domain_id", "fingerprint"),
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
domain_id: Mapped[DomainKey]
|
|
254
|
+
seq: Mapped[int] = mapped_column(BigInteger, Identity(always=True), primary_key=True)
|
|
255
|
+
kind_id: Mapped[str]
|
|
256
|
+
attribute_id: Mapped[str]
|
|
257
|
+
record_key: Mapped[str | None]
|
|
258
|
+
fingerprint: Mapped[bytes]
|
|
259
|
+
fingerprint_key: Mapped[str | None]
|
|
260
|
+
created_at: Mapped[datetime] = mapped_column(server_default=text("now()"))
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
class VaultLineage(Base):
|
|
264
|
+
"""A Record whose vaulted values were taken over from a Record of the same Kind under
|
|
265
|
+
another key (a key change): erasing either erases the values of both."""
|
|
266
|
+
|
|
267
|
+
__tablename__ = "vault_lineage"
|
|
268
|
+
__table_args__ = (
|
|
269
|
+
domain_fk(),
|
|
270
|
+
Index(None, "domain_id", "kind_id", "previous_key"),
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
domain_id: Mapped[DomainKey]
|
|
274
|
+
kind_id: Mapped[str] = mapped_column(primary_key=True)
|
|
275
|
+
record_key: Mapped[str] = mapped_column(primary_key=True)
|
|
276
|
+
previous_key: Mapped[str] = mapped_column(primary_key=True)
|
|
277
|
+
created_at: Mapped[datetime] = mapped_column(server_default=text("now()"))
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
class InstallationSecret(Base):
|
|
281
|
+
"""Random secrets of the installation made by the migrations, such as the key of the keyed
|
|
282
|
+
digests when no `vault.digest_key` is configured (`lorebase_store.digests`)."""
|
|
283
|
+
|
|
284
|
+
__tablename__ = "installation_secrets"
|
|
285
|
+
|
|
286
|
+
name: Mapped[str] = mapped_column(primary_key=True)
|
|
287
|
+
secret: Mapped[str]
|
|
288
|
+
created_at: Mapped[datetime] = mapped_column(server_default=text("now()"))
|