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.
Files changed (163) hide show
  1. lorebase_store/__init__.py +1 -0
  2. lorebase_store/_migrations/env.py +106 -0
  3. lorebase_store/_migrations/script.py.mako +27 -0
  4. lorebase_store/_migrations/versions/0001_initial_schema.py +1071 -0
  5. lorebase_store/_migrations/versions/0002_merge_attempt_details.py +74 -0
  6. lorebase_store/_migrations/versions/0003_webhooks.py +127 -0
  7. lorebase_store/_migrations/versions/0004_defer_revision_references.py +38 -0
  8. lorebase_store/_migrations/versions/0005_security_definer_reads.py +396 -0
  9. lorebase_store/_migrations/versions/0006_api_key_usage.py +48 -0
  10. lorebase_store/_migrations/versions/0007_document_extraction.py +154 -0
  11. lorebase_store/_migrations/versions/0008_definer_argument_checks.py +178 -0
  12. lorebase_store/_migrations/versions/0009_api_key_networks.py +33 -0
  13. lorebase_store/_migrations/versions/0010_overlay_branches.py +582 -0
  14. lorebase_store/_migrations/versions/0011_branch_scoped_search_index.py +229 -0
  15. lorebase_store/_migrations/versions/0012_overlay_neighborhood_lookups.py +185 -0
  16. lorebase_store/_migrations/versions/0013_web_sessions.py +72 -0
  17. lorebase_store/_migrations/versions/0014_embedding_jobs.py +70 -0
  18. lorebase_store/_migrations/versions/0015_append_only_audit_log.py +83 -0
  19. lorebase_store/_migrations/versions/0016_sealed_webhook_keys.py +55 -0
  20. lorebase_store/_migrations/versions/0017_background_branch_copies.py +77 -0
  21. lorebase_store/_migrations/versions/0018_search_lexicon.py +477 -0
  22. lorebase_store/_migrations/versions/0019_vault_bindings.py +56 -0
  23. lorebase_store/_migrations/versions/0020_outbox_consumers.py +150 -0
  24. lorebase_store/_migrations/versions/0021_schema_suggestions.py +114 -0
  25. lorebase_store/_migrations/versions/0022_read_audit_and_shared_rate_limits.py +133 -0
  26. lorebase_store/_migrations/versions/0023_agent_trace.py +96 -0
  27. lorebase_store/_migrations/versions/0024_embedding_template_keys.py +205 -0
  28. lorebase_store/_migrations/versions/0025_snapshot_refs.py +35 -0
  29. lorebase_store/_migrations/versions/0026_plain_reverse_link_index.py +112 -0
  30. lorebase_store/_migrations/versions/0027_vault_rotation_and_tombstones.py +145 -0
  31. lorebase_store/_migrations/versions/0028_operation_deferred_events.py +71 -0
  32. lorebase_store/_migrations/versions/0029_agent_trace_sessions.py +108 -0
  33. lorebase_store/_migrations/versions/0030_path_fanout.py +31 -0
  34. lorebase_store/_migrations/versions/0031_branch_builds.py +91 -0
  35. lorebase_store/_migrations/versions/0032_lineage_revisions.py +35 -0
  36. lorebase_store/_migrations/versions/0033_vault_moved_tokens.py +30 -0
  37. lorebase_store/_migrations/versions/0034_inference_endpoint_modes.py +73 -0
  38. lorebase_store/_migrations/versions/0035_search_truncation.py +104 -0
  39. lorebase_store/_migrations/versions/0036_lineage_maps.py +325 -0
  40. lorebase_store/_migrations/versions/0037_bm25_search.py +346 -0
  41. lorebase_store/auth/__init__.py +52 -0
  42. lorebase_store/auth/api_keys.py +161 -0
  43. lorebase_store/auth/audit.py +382 -0
  44. lorebase_store/auth/checkpoints.py +185 -0
  45. lorebase_store/auth/grants.py +161 -0
  46. lorebase_store/auth/principals.py +158 -0
  47. lorebase_store/auth/scope.py +70 -0
  48. lorebase_store/auth/sessions.py +153 -0
  49. lorebase_store/auth/unit.py +53 -0
  50. lorebase_store/auth/usage.py +70 -0
  51. lorebase_store/blobs.py +73 -0
  52. lorebase_store/bm25.py +346 -0
  53. lorebase_store/bulk.py +147 -0
  54. lorebase_store/bundle.py +1830 -0
  55. lorebase_store/changefeed.py +261 -0
  56. lorebase_store/commit/__init__.py +42 -0
  57. lorebase_store/commit/changes.py +67 -0
  58. lorebase_store/commit/errors.py +60 -0
  59. lorebase_store/commit/migrations.py +335 -0
  60. lorebase_store/commit/pipeline.py +754 -0
  61. lorebase_store/commit/schema.py +123 -0
  62. lorebase_store/db.py +58 -0
  63. lorebase_store/definer.py +50 -0
  64. lorebase_store/digests.py +99 -0
  65. lorebase_store/embeddings.py +444 -0
  66. lorebase_store/endpoint_modes.py +59 -0
  67. lorebase_store/extraction/__init__.py +61 -0
  68. lorebase_store/extraction/items.py +187 -0
  69. lorebase_store/extraction/materialize.py +364 -0
  70. lorebase_store/extraction/proposal.py +65 -0
  71. lorebase_store/extraction/review.py +168 -0
  72. lorebase_store/gc.py +970 -0
  73. lorebase_store/indexes.py +1033 -0
  74. lorebase_store/jobs.py +651 -0
  75. lorebase_store/keyring.py +72 -0
  76. lorebase_store/layout.py +335 -0
  77. lorebase_store/materialize/__init__.py +103 -0
  78. lorebase_store/materialize/apply.py +436 -0
  79. lorebase_store/materialize/background.py +1184 -0
  80. lorebase_store/materialize/branch.py +268 -0
  81. lorebase_store/materialize/build.py +536 -0
  82. lorebase_store/materialize/derived.py +540 -0
  83. lorebase_store/materialize/lineage_backfill.py +502 -0
  84. lorebase_store/materialize/links.py +266 -0
  85. lorebase_store/materialize/overlay.py +292 -0
  86. lorebase_store/materialize/paths.py +315 -0
  87. lorebase_store/materialize/rebuild.py +326 -0
  88. lorebase_store/materialize/rows.py +153 -0
  89. lorebase_store/materialize/sensitivity.py +66 -0
  90. lorebase_store/materialize/snapshot.py +205 -0
  91. lorebase_store/materialize/stale.py +444 -0
  92. lorebase_store/materialize/verify.py +321 -0
  93. lorebase_store/merging/__init__.py +39 -0
  94. lorebase_store/merging/attempts.py +334 -0
  95. lorebase_store/merging/lineage.py +123 -0
  96. lorebase_store/merging/service.py +652 -0
  97. lorebase_store/migrations.py +133 -0
  98. lorebase_store/models/__init__.py +136 -0
  99. lorebase_store/models/base.py +64 -0
  100. lorebase_store/models/enums.py +132 -0
  101. lorebase_store/models/governance.py +268 -0
  102. lorebase_store/models/ingestion.py +288 -0
  103. lorebase_store/models/materialized.py +277 -0
  104. lorebase_store/models/proposals.py +195 -0
  105. lorebase_store/models/queues.py +87 -0
  106. lorebase_store/models/tracing.py +116 -0
  107. lorebase_store/models/versioning.py +222 -0
  108. lorebase_store/models/webhooks.py +96 -0
  109. lorebase_store/nodestore.py +384 -0
  110. lorebase_store/objectstore.py +162 -0
  111. lorebase_store/outbox_events.py +166 -0
  112. lorebase_store/profiling/__init__.py +58 -0
  113. lorebase_store/profiling/delimited.py +109 -0
  114. lorebase_store/profiling/postgres.py +211 -0
  115. lorebase_store/profiling/record_files.py +127 -0
  116. lorebase_store/profiling/records.py +92 -0
  117. lorebase_store/profiling/sql_databases.py +298 -0
  118. lorebase_store/ratelimit.py +129 -0
  119. lorebase_store/reads.py +1491 -0
  120. lorebase_store/replicas.py +434 -0
  121. lorebase_store/repositories/__init__.py +152 -0
  122. lorebase_store/repositories/changesets.py +59 -0
  123. lorebase_store/repositories/deferred_events.py +142 -0
  124. lorebase_store/repositories/documents.py +305 -0
  125. lorebase_store/repositories/domains.py +97 -0
  126. lorebase_store/repositories/events.py +205 -0
  127. lorebase_store/repositories/index_view.py +489 -0
  128. lorebase_store/repositories/invocations.py +68 -0
  129. lorebase_store/repositories/materializations.py +276 -0
  130. lorebase_store/repositories/materialized.py +1129 -0
  131. lorebase_store/repositories/merges.py +417 -0
  132. lorebase_store/repositories/proposal_items.py +199 -0
  133. lorebase_store/repositories/proposals.py +184 -0
  134. lorebase_store/repositories/provenance.py +77 -0
  135. lorebase_store/repositories/refs.py +452 -0
  136. lorebase_store/repositories/revisions.py +273 -0
  137. lorebase_store/repositories/scope.py +33 -0
  138. lorebase_store/repositories/source_runs.py +123 -0
  139. lorebase_store/repositories/suggestions.py +638 -0
  140. lorebase_store/runtime.py +87 -0
  141. lorebase_store/schema_cache.py +50 -0
  142. lorebase_store/settings.py +879 -0
  143. lorebase_store/storage.py +1478 -0
  144. lorebase_store/telemetry.py +142 -0
  145. lorebase_store/testing.py +108 -0
  146. lorebase_store/tracing.py +505 -0
  147. lorebase_store/traverse.py +578 -0
  148. lorebase_store/vault/__init__.py +50 -0
  149. lorebase_store/vault/keys.py +216 -0
  150. lorebase_store/vault/quotes.py +133 -0
  151. lorebase_store/vault/rotation.py +209 -0
  152. lorebase_store/vault/settings.py +152 -0
  153. lorebase_store/vault/store.py +987 -0
  154. lorebase_store/vault/transfer.py +558 -0
  155. lorebase_store/webhooks/__init__.py +24 -0
  156. lorebase_store/webhooks/delivery.py +637 -0
  157. lorebase_store/webhooks/keys.py +126 -0
  158. lorebase_store/webhooks/repository.py +142 -0
  159. lorebase_store/webhooks/settings.py +138 -0
  160. lorebase_store/webhooks/signing.py +50 -0
  161. lorebase_store-0.2.3.dist-info/METADATA +13 -0
  162. lorebase_store-0.2.3.dist-info/RECORD +163 -0
  163. 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