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,168 @@
1
+ """Reviewer decisions on extraction items (design 9.2 step 12, 12.3 Review).
2
+
3
+ `decide_items` records accept, reject or reset decisions (with optional edits) on items of an
4
+ open extraction Proposal, re-materializes the extraction branch so it holds exactly the
5
+ accepted items, and refreshes the Proposal (summary, checks, merge preview), all in the
6
+ caller's transaction. When the branch rejects the accepted set (for example a new Record that
7
+ misses a required value), the commit raises `ValidationFailedError` and the caller's
8
+ transaction must roll back: decisions and branch stay as they were.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from collections.abc import Callable, Mapping, Sequence
14
+ from dataclasses import dataclass
15
+ from datetime import datetime
16
+ from typing import Any
17
+
18
+ from pydantic import ValidationError
19
+ from sqlalchemy.ext.asyncio import AsyncSession
20
+
21
+ from lorebase_core.merge import MergePolicy
22
+ from lorebase_store.commit.schema import load_compiled
23
+ from lorebase_store.extraction.items import ItemEditError, normalize_edits
24
+ from lorebase_store.extraction.materialize import (
25
+ MaterializeContext,
26
+ Materialized,
27
+ materialize_items,
28
+ )
29
+ from lorebase_store.extraction.proposal import refresh_proposal
30
+ from lorebase_store.merging import AttemptResult
31
+ from lorebase_store.models.enums import ItemDecision, ProposalOrigin, ProposalStatus
32
+ from lorebase_store.repositories.documents import DocumentRepository, ExtractionJobRepository
33
+ from lorebase_store.repositories.materialized import MaterializedRepository
34
+ from lorebase_store.repositories.proposal_items import ItemRow, ProposalItemRepository
35
+ from lorebase_store.repositories.proposals import ProposalRepository, ProposalRow
36
+ from lorebase_store.repositories.scope import scope_to_domain
37
+ from lorebase_store.runtime import StoreRuntime
38
+
39
+ MERGE_POLICY_KEY = "merge_policy"
40
+ """Key of the merge policy in a Proposal's `policies`."""
41
+ UNKNOWN_PROMPT = "unknown"
42
+
43
+
44
+ class ReviewError(Exception):
45
+ """A decision request that cannot be applied; `reason` names the case."""
46
+
47
+ def __init__(self, reason: str, message: str, item_ids: Sequence[str] = ()) -> None:
48
+ super().__init__(message)
49
+ self.reason = reason
50
+ self.item_ids = list(item_ids)
51
+
52
+
53
+ @dataclass(frozen=True, slots=True)
54
+ class Decision:
55
+ """One decision; `edits` of `None` keeps the item's stored edits, `{}` clears them."""
56
+
57
+ item_id: str
58
+ decision: ItemDecision
59
+ edits: Mapping[str, Any] | None = None
60
+
61
+
62
+ @dataclass(frozen=True)
63
+ class Reviewed:
64
+ proposal: ProposalRow
65
+ items: list[ItemRow]
66
+ outcome: Materialized
67
+ attempt: AttemptResult | None
68
+
69
+
70
+ async def extraction_proposal(
71
+ session: AsyncSession, domain_id: str, proposal_id: str, *, for_update: bool = False
72
+ ) -> ProposalRow:
73
+ """The extraction Proposal, or `ReviewError` (`not_found`, `not_extraction`)."""
74
+ proposal = await ProposalRepository(session, domain_id).get(proposal_id, for_update=for_update)
75
+ if proposal is None:
76
+ raise ReviewError("not_found", f"Proposal {proposal_id!r} not found.")
77
+ if proposal.origin is not ProposalOrigin.EXTRACTION:
78
+ raise ReviewError("not_extraction", f"Proposal {proposal_id!r} has no extraction items.")
79
+ return proposal
80
+
81
+
82
+ async def decide_items(
83
+ session: AsyncSession,
84
+ domain_id: str,
85
+ proposal_id: str,
86
+ decisions: Sequence[Decision],
87
+ *,
88
+ actor: str,
89
+ runtime: StoreRuntime,
90
+ clock: Callable[[], datetime],
91
+ ) -> Reviewed:
92
+ """Apply `decisions`, re-materialize the extraction branch and refresh the Proposal.
93
+
94
+ Raises `ReviewError` (`not_found`, `not_extraction`, `not_open`, `unknown_items`,
95
+ `invalid_edits`), `ValidationFailedError` and the commit pipeline's other errors.
96
+ """
97
+ await scope_to_domain(session, domain_id)
98
+ proposal = await extraction_proposal(session, domain_id, proposal_id, for_update=True)
99
+ if proposal.status is not ProposalStatus.OPEN:
100
+ raise ReviewError("not_open", f"Proposal {proposal_id!r} is {proposal.status.value}.")
101
+ writes = runtime.settings.writes
102
+ compiled = await MaterializedRepository(
103
+ session, domain_id, proposal.target_ref, writes=writes
104
+ ).schema()
105
+ if compiled is None:
106
+ raise ReviewError("not_found", f"branch {proposal.target_ref!r} is not materialized")
107
+ schema = load_compiled(compiled.compiled)
108
+ repository = ProposalItemRepository(session, domain_id)
109
+ stored = {item.item_id: item for item in await repository.all(proposal_id, for_update=True)}
110
+ unknown = sorted({d.item_id for d in decisions} - stored.keys())
111
+ if unknown:
112
+ raise ReviewError("unknown_items", "Items not in the Proposal.", unknown)
113
+ now = clock()
114
+ changed: list[str] = []
115
+ for decision in decisions:
116
+ item = stored[decision.item_id]
117
+ try:
118
+ edits = (
119
+ item.edits
120
+ if decision.edits is None
121
+ else normalize_edits(item, decision.edits, schema) or None
122
+ )
123
+ except ItemEditError as error:
124
+ raise ReviewError("invalid_edits", str(error), [error.item_id]) from None
125
+ pending = decision.decision is ItemDecision.PENDING
126
+ await repository.decide(
127
+ proposal_id,
128
+ item.item_id,
129
+ decision=decision.decision,
130
+ decided_by=None if pending else actor,
131
+ decided_at=None if pending else now,
132
+ edits=edits,
133
+ )
134
+ changed.append(item.item_id)
135
+ items = await repository.all(proposal_id)
136
+ job = await ExtractionJobRepository(session, domain_id).by_proposal(proposal_id)
137
+ context = MaterializeContext(
138
+ domain_id=domain_id,
139
+ branch=proposal.source_ref,
140
+ target=proposal.target_ref,
141
+ job_id=job.id if job is not None else proposal.source_ref,
142
+ prompt_version=job.prompt_version if job is not None else UNKNOWN_PROMPT,
143
+ actor=actor,
144
+ runtime=runtime,
145
+ clock=clock,
146
+ )
147
+ outcome = await materialize_items(
148
+ session,
149
+ context,
150
+ items,
151
+ schema,
152
+ message=f"Review of {proposal_id}: {len(changed)} decision(s)",
153
+ )
154
+ documents: list[str] = []
155
+ if job is not None:
156
+ found = DocumentRepository(session, domain_id)
157
+ for document_id in job.documents:
158
+ row = await found.get(document_id)
159
+ documents.append(row.filename if row is not None else document_id)
160
+ try:
161
+ policy = MergePolicy.model_validate((proposal.policies or {}).get(MERGE_POLICY_KEY) or {})
162
+ except ValidationError:
163
+ policy = MergePolicy()
164
+ final, attempt = await refresh_proposal(
165
+ session, context, proposal, items, schema, outcome, documents=documents, policy=policy
166
+ )
167
+ by_id = {item.item_id: item for item in items}
168
+ return Reviewed(final, [by_id[i] for i in changed], outcome, attempt)