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,184 @@
|
|
|
1
|
+
"""Proposals and their reviews (design 7.3, 10.3)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from sqlalchemy import Table, and_, insert, or_, select, update
|
|
10
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
11
|
+
|
|
12
|
+
from lorebase_store.models import metadata
|
|
13
|
+
from lorebase_store.models.enums import ProposalOrigin, ProposalStatus, ReviewVerdict
|
|
14
|
+
|
|
15
|
+
PROPOSALS: Table = metadata.tables["proposals"]
|
|
16
|
+
REVIEWS: Table = metadata.tables["proposal_reviews"]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(frozen=True, slots=True)
|
|
20
|
+
class ProposalRow:
|
|
21
|
+
id: str
|
|
22
|
+
title: str
|
|
23
|
+
description: str | None
|
|
24
|
+
source_ref: str
|
|
25
|
+
target_ref: str
|
|
26
|
+
origin: ProposalOrigin
|
|
27
|
+
status: ProposalStatus
|
|
28
|
+
author: str
|
|
29
|
+
created_at: datetime
|
|
30
|
+
merged_revision: bytes | None = None
|
|
31
|
+
checks: dict[str, Any] | None = None
|
|
32
|
+
policies: dict[str, Any] | None = None
|
|
33
|
+
idempotency_key: str | None = None
|
|
34
|
+
updated_at: datetime | None = None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True, slots=True)
|
|
38
|
+
class ProposalFilter:
|
|
39
|
+
"""Optional filters of a Proposal listing."""
|
|
40
|
+
|
|
41
|
+
status: ProposalStatus | None = None
|
|
42
|
+
origin: ProposalOrigin | None = None
|
|
43
|
+
source_ref: str | None = None
|
|
44
|
+
target_ref: str | None = None
|
|
45
|
+
author: str | None = None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass(frozen=True, slots=True)
|
|
49
|
+
class ReviewRow:
|
|
50
|
+
id: str
|
|
51
|
+
proposal_id: str
|
|
52
|
+
reviewer: str
|
|
53
|
+
verdict: ReviewVerdict
|
|
54
|
+
comment: str | None
|
|
55
|
+
at: datetime
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _proposal(found: Any) -> ProposalRow:
|
|
59
|
+
return ProposalRow(**{k: v for k, v in found.items() if k != "domain_id"})
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class ProposalRepository:
|
|
63
|
+
"""Proposals of one Domain."""
|
|
64
|
+
|
|
65
|
+
def __init__(self, session: AsyncSession, domain_id: str) -> None:
|
|
66
|
+
self._session = session
|
|
67
|
+
self._domain = domain_id
|
|
68
|
+
|
|
69
|
+
async def insert(self, row: ProposalRow) -> None:
|
|
70
|
+
await self._session.execute(
|
|
71
|
+
insert(PROPOSALS).values(
|
|
72
|
+
domain_id=self._domain,
|
|
73
|
+
id=row.id,
|
|
74
|
+
title=row.title,
|
|
75
|
+
description=row.description,
|
|
76
|
+
source_ref=row.source_ref,
|
|
77
|
+
target_ref=row.target_ref,
|
|
78
|
+
origin=row.origin,
|
|
79
|
+
status=row.status,
|
|
80
|
+
author=row.author,
|
|
81
|
+
created_at=row.created_at,
|
|
82
|
+
merged_revision=row.merged_revision,
|
|
83
|
+
checks=row.checks,
|
|
84
|
+
policies=row.policies,
|
|
85
|
+
idempotency_key=row.idempotency_key,
|
|
86
|
+
updated_at=row.updated_at,
|
|
87
|
+
)
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
async def get(self, proposal_id: str, *, for_update: bool = False) -> ProposalRow | None:
|
|
91
|
+
query = select(PROPOSALS).where(
|
|
92
|
+
PROPOSALS.c.domain_id == self._domain, PROPOSALS.c.id == proposal_id
|
|
93
|
+
)
|
|
94
|
+
if for_update:
|
|
95
|
+
query = query.with_for_update()
|
|
96
|
+
found = (await self._session.execute(query)).mappings().one_or_none()
|
|
97
|
+
return None if found is None else _proposal(found)
|
|
98
|
+
|
|
99
|
+
async def by_key(self, author: str, idempotency_key: str) -> ProposalRow | None:
|
|
100
|
+
"""The Proposal an author created under an idempotency key."""
|
|
101
|
+
result = await self._session.execute(
|
|
102
|
+
select(PROPOSALS).where(
|
|
103
|
+
PROPOSALS.c.domain_id == self._domain,
|
|
104
|
+
PROPOSALS.c.author == author,
|
|
105
|
+
PROPOSALS.c.idempotency_key == idempotency_key,
|
|
106
|
+
)
|
|
107
|
+
)
|
|
108
|
+
found = result.mappings().one_or_none()
|
|
109
|
+
return None if found is None else _proposal(found)
|
|
110
|
+
|
|
111
|
+
async def page(
|
|
112
|
+
self,
|
|
113
|
+
filters: ProposalFilter,
|
|
114
|
+
*,
|
|
115
|
+
limit: int,
|
|
116
|
+
after: tuple[datetime, str] | None = None,
|
|
117
|
+
) -> list[ProposalRow]:
|
|
118
|
+
"""Proposals matching `filters`, newest first, after the keyset `(created_at, id)`."""
|
|
119
|
+
table = PROPOSALS
|
|
120
|
+
query = select(table).where(table.c.domain_id == self._domain)
|
|
121
|
+
for column, value in (
|
|
122
|
+
(table.c.status, filters.status),
|
|
123
|
+
(table.c.origin, filters.origin),
|
|
124
|
+
(table.c.source_ref, filters.source_ref),
|
|
125
|
+
(table.c.target_ref, filters.target_ref),
|
|
126
|
+
(table.c.author, filters.author),
|
|
127
|
+
):
|
|
128
|
+
if value is not None:
|
|
129
|
+
query = query.where(column == value)
|
|
130
|
+
if after is not None:
|
|
131
|
+
at, last_id = after
|
|
132
|
+
query = query.where(
|
|
133
|
+
or_(table.c.created_at < at, and_(table.c.created_at == at, table.c.id < last_id))
|
|
134
|
+
)
|
|
135
|
+
query = query.order_by(table.c.created_at.desc(), table.c.id.desc()).limit(limit)
|
|
136
|
+
result = await self._session.execute(query)
|
|
137
|
+
return [_proposal(row) for row in result.mappings()]
|
|
138
|
+
|
|
139
|
+
async def update(self, proposal_id: str, *, at: datetime, **values: Any) -> ProposalRow | None:
|
|
140
|
+
"""Set columns (`title`, `description`, `status`, `merged_revision`, `checks`,
|
|
141
|
+
`policies`) and `updated_at`; returns the row, `None` when it does not exist."""
|
|
142
|
+
allowed = {"title", "description", "status", "merged_revision", "checks", "policies"}
|
|
143
|
+
unknown = values.keys() - allowed
|
|
144
|
+
if unknown:
|
|
145
|
+
raise ValueError(f"cannot update {sorted(unknown)}")
|
|
146
|
+
await self._session.execute(
|
|
147
|
+
update(PROPOSALS)
|
|
148
|
+
.where(PROPOSALS.c.domain_id == self._domain, PROPOSALS.c.id == proposal_id)
|
|
149
|
+
.values(updated_at=at, **values)
|
|
150
|
+
)
|
|
151
|
+
return await self.get(proposal_id)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class ReviewRepository:
|
|
155
|
+
"""Reviews of the Proposals of one Domain."""
|
|
156
|
+
|
|
157
|
+
def __init__(self, session: AsyncSession, domain_id: str) -> None:
|
|
158
|
+
self._session = session
|
|
159
|
+
self._domain = domain_id
|
|
160
|
+
|
|
161
|
+
async def insert(self, row: ReviewRow) -> None:
|
|
162
|
+
await self._session.execute(
|
|
163
|
+
insert(REVIEWS).values(
|
|
164
|
+
domain_id=self._domain,
|
|
165
|
+
id=row.id,
|
|
166
|
+
proposal_id=row.proposal_id,
|
|
167
|
+
reviewer=row.reviewer,
|
|
168
|
+
verdict=row.verdict,
|
|
169
|
+
comment=row.comment,
|
|
170
|
+
at=row.at,
|
|
171
|
+
)
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
async def of(self, proposal_id: str) -> list[ReviewRow]:
|
|
175
|
+
"""Reviews of a Proposal, oldest first."""
|
|
176
|
+
result = await self._session.execute(
|
|
177
|
+
select(REVIEWS)
|
|
178
|
+
.where(REVIEWS.c.domain_id == self._domain, REVIEWS.c.proposal_id == proposal_id)
|
|
179
|
+
.order_by(REVIEWS.c.at, REVIEWS.c.id)
|
|
180
|
+
)
|
|
181
|
+
return [
|
|
182
|
+
ReviewRow(**{k: v for k, v in row.items() if k != "domain_id"})
|
|
183
|
+
for row in result.mappings()
|
|
184
|
+
]
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"""Provenance entries: where values came from (design 5.11, AC-08)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Iterable
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from sqlalchemy import Table, select
|
|
11
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
12
|
+
|
|
13
|
+
from lorebase_core.model import ProvenanceEntry
|
|
14
|
+
from lorebase_store.bulk import bulk_write, chunks
|
|
15
|
+
from lorebase_store.models import metadata
|
|
16
|
+
from lorebase_store.settings import WriteSettings
|
|
17
|
+
|
|
18
|
+
PROVENANCE: Table = metadata.tables["provenance"]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True, slots=True)
|
|
22
|
+
class ProvenanceRow:
|
|
23
|
+
id: str
|
|
24
|
+
origin_type: str
|
|
25
|
+
origin_ref: dict[str, Any]
|
|
26
|
+
actor: str
|
|
27
|
+
at: datetime
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def from_entry(cls, entry: ProvenanceEntry) -> ProvenanceRow:
|
|
31
|
+
"""Row of a core provenance entry; `origin_ref` holds the whole origin document."""
|
|
32
|
+
return cls(
|
|
33
|
+
id=entry.id,
|
|
34
|
+
origin_type=entry.origin.type,
|
|
35
|
+
origin_ref=entry.origin.content(),
|
|
36
|
+
actor=entry.actor,
|
|
37
|
+
at=datetime.fromisoformat(entry.at),
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class ProvenanceRepository:
|
|
42
|
+
"""Provenance rows of one Domain. Entries are immutable: re-inserting one is a no-op."""
|
|
43
|
+
|
|
44
|
+
def __init__(self, session: AsyncSession, domain_id: str, *, writes: WriteSettings) -> None:
|
|
45
|
+
self._session = session
|
|
46
|
+
self._domain = domain_id
|
|
47
|
+
self._writes = writes
|
|
48
|
+
|
|
49
|
+
async def insert_many(self, rows: Iterable[ProvenanceRow]) -> None:
|
|
50
|
+
values = [
|
|
51
|
+
{
|
|
52
|
+
"domain_id": self._domain,
|
|
53
|
+
"id": row.id,
|
|
54
|
+
"origin_type": row.origin_type,
|
|
55
|
+
"origin_ref": row.origin_ref,
|
|
56
|
+
"actor": row.actor,
|
|
57
|
+
"at": row.at,
|
|
58
|
+
}
|
|
59
|
+
for row in {row.id: row for row in rows}.values()
|
|
60
|
+
]
|
|
61
|
+
await bulk_write(self._session, PROVENANCE, values, writes=self._writes)
|
|
62
|
+
|
|
63
|
+
async def get_many(self, ids: Iterable[str]) -> dict[str, ProvenanceRow]:
|
|
64
|
+
found: dict[str, ProvenanceRow] = {}
|
|
65
|
+
for chunk in chunks(list(dict.fromkeys(ids)), self._writes.insert_batch_rows):
|
|
66
|
+
result = await self._session.execute(
|
|
67
|
+
select(
|
|
68
|
+
PROVENANCE.c.id,
|
|
69
|
+
PROVENANCE.c.origin_type,
|
|
70
|
+
PROVENANCE.c.origin_ref,
|
|
71
|
+
PROVENANCE.c.actor,
|
|
72
|
+
PROVENANCE.c.at,
|
|
73
|
+
).where(PROVENANCE.c.domain_id == self._domain, PROVENANCE.c.id.in_(chunk))
|
|
74
|
+
)
|
|
75
|
+
for row in result.mappings():
|
|
76
|
+
found[row["id"]] = ProvenanceRow(**row)
|
|
77
|
+
return found
|