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,126 @@
1
+ """Webhook signing keys at rest.
2
+
3
+ The signing key of a webhook is `sha256(secret)` (`webhooks.signing`): whoever holds it can
4
+ sign deliveries. With `webhooks.encryption_key` set, it is stored sealed (AES-256-GCM under a
5
+ key derived from that secret, `lorebase_store.keyring`) in `webhooks.sealed_key`, and
6
+ `secret_hash` stays null. Without it, the key is stored usable in `secret_hash`, as before
7
+ revision 0016 (Keeper does so only with `webhooks.allow_unsealed_keys`); the delivery worker
8
+ seals such rows once a key is configured (`seal_stored_keys`), so existing webhooks keep
9
+ working through the change. Keys sealed under one of `previous_encryption_keys` still open and
10
+ are sealed again under the current key.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from collections.abc import Sequence
16
+ from dataclasses import dataclass
17
+
18
+ from sqlalchemy import select, update
19
+ from sqlalchemy.ext.asyncio import AsyncSession
20
+
21
+ from lorebase_store.keyring import Keyring
22
+ from lorebase_store.models import metadata
23
+ from lorebase_store.webhooks.settings import WebhookSettings
24
+
25
+ WEBHOOKS = metadata.tables["webhooks"]
26
+ NAMESPACE = "webhooks"
27
+ PURPOSE = "signing-key"
28
+
29
+
30
+ @dataclass(frozen=True, slots=True)
31
+ class StoredKey:
32
+ """The columns that hold a signing key: exactly one is set."""
33
+
34
+ secret_hash: bytes | None
35
+ sealed_key: bytes | None
36
+
37
+
38
+ @dataclass(frozen=True, slots=True)
39
+ class OpenedKey:
40
+ """A usable signing key; `stale` when it is stored usable or under a previous key."""
41
+
42
+ key: bytes
43
+ stale: bool
44
+
45
+
46
+ class SigningKeys:
47
+ """Seals and opens webhook signing keys under the configured encryption keys."""
48
+
49
+ def __init__(self, current: str | None, previous: Sequence[str] = ()) -> None:
50
+ self._current = None if current is None else Keyring(current, namespace=NAMESPACE)
51
+ self._previous = [Keyring(secret, namespace=NAMESPACE) for secret in previous]
52
+
53
+ @classmethod
54
+ def from_settings(cls, settings: WebhookSettings) -> SigningKeys:
55
+ current = settings.encryption_key
56
+ return cls(
57
+ None if current is None else current.get_secret_value(),
58
+ [key.get_secret_value() for key in settings.previous_encryption_keys],
59
+ )
60
+
61
+ @property
62
+ def sealing(self) -> bool:
63
+ """Whether new keys are stored sealed."""
64
+ return self._current is not None
65
+
66
+ @property
67
+ def rotating(self) -> bool:
68
+ """Whether keys sealed under a previous encryption key may exist."""
69
+ return bool(self._previous)
70
+
71
+ def store(self, key: bytes) -> StoredKey:
72
+ """The columns to store `key` in."""
73
+ if self._current is None:
74
+ return StoredKey(secret_hash=key, sealed_key=None)
75
+ return StoredKey(secret_hash=None, sealed_key=self._current.seal(PURPOSE, key))
76
+
77
+ def open(self, stored: StoredKey) -> OpenedKey | None:
78
+ """The usable key; None when it is sealed under a key that is not configured."""
79
+ if stored.secret_hash is not None:
80
+ return OpenedKey(bytes(stored.secret_hash), stale=self._current is not None)
81
+ if stored.sealed_key is None:
82
+ return None
83
+ sealed = bytes(stored.sealed_key)
84
+ if self._current is not None:
85
+ key = self._current.open(PURPOSE, sealed)
86
+ if key is not None:
87
+ return OpenedKey(key, stale=False)
88
+ for keyring in self._previous:
89
+ key = keyring.open(PURPOSE, sealed)
90
+ if key is not None:
91
+ return OpenedKey(key, stale=True)
92
+ return None
93
+
94
+
95
+ async def seal_stored_keys(session: AsyncSession, keys: SigningKeys, *, limit: int) -> int:
96
+ """Seal up to `limit` keys stored usable or under a previous key; the number sealed.
97
+
98
+ Runs in the caller's transaction, which must see every Domain (RLS bypass). Rows are locked
99
+ with `SKIP LOCKED`, so concurrent workers seal different rows.
100
+ """
101
+ if not keys.sealing:
102
+ return 0
103
+ w = WEBHOOKS
104
+ query = (
105
+ select(w.c.domain_id, w.c.id, w.c.secret_hash, w.c.sealed_key)
106
+ .order_by(w.c.domain_id, w.c.id)
107
+ .with_for_update(skip_locked=True)
108
+ )
109
+ if not keys.rotating: # only keys stored usable are stale: found without opening any
110
+ query = query.where(w.c.secret_hash.is_not(None)).limit(limit)
111
+ rows = list(await session.execute(query))
112
+ sealed = 0
113
+ for row in rows:
114
+ if sealed >= limit:
115
+ break
116
+ opened = keys.open(StoredKey(row.secret_hash, row.sealed_key))
117
+ if opened is None or not opened.stale:
118
+ continue
119
+ stored = keys.store(opened.key)
120
+ await session.execute(
121
+ update(w)
122
+ .where(w.c.domain_id == row.domain_id, w.c.id == row.id)
123
+ .values(secret_hash=stored.secret_hash, sealed_key=stored.sealed_key)
124
+ )
125
+ sealed += 1
126
+ return sealed
@@ -0,0 +1,142 @@
1
+ """Webhooks of one Domain (management); deliveries are handled by `webhooks.delivery`."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Sequence
6
+ from dataclasses import dataclass
7
+ from datetime import datetime
8
+
9
+ from sqlalchemy import Table, delete, func, insert, select, update
10
+ from sqlalchemy.ext.asyncio import AsyncSession
11
+
12
+ from lorebase_store.auth.scope import new_id
13
+ from lorebase_store.models import metadata
14
+ from lorebase_store.webhooks.keys import StoredKey
15
+
16
+ WEBHOOKS: Table = metadata.tables["webhooks"]
17
+ WEBHOOK_DELIVERIES: Table = metadata.tables["webhook_deliveries"]
18
+ ID_PREFIX = "whk"
19
+
20
+
21
+ @dataclass(frozen=True, slots=True)
22
+ class WebhookRow:
23
+ id: str
24
+ domain_id: str
25
+ url: str
26
+ description: str | None
27
+ event_types: tuple[str, ...]
28
+ branches: tuple[str, ...] | None
29
+ created_by: str
30
+ created_at: datetime
31
+ clearances: tuple[str, ...] = ()
32
+ """Sensitivity tags whose values the webhook receives in clear."""
33
+
34
+
35
+ _COLUMNS = (
36
+ WEBHOOKS.c.id,
37
+ WEBHOOKS.c.domain_id,
38
+ WEBHOOKS.c.url,
39
+ WEBHOOKS.c.description,
40
+ WEBHOOKS.c.event_types,
41
+ WEBHOOKS.c.branches,
42
+ WEBHOOKS.c.created_by,
43
+ WEBHOOKS.c.created_at,
44
+ WEBHOOKS.c.clearances,
45
+ )
46
+
47
+
48
+ def webhook_row(row: object) -> WebhookRow:
49
+ values = row._mapping # type: ignore[attr-defined]
50
+ branches = values["branches"]
51
+ return WebhookRow(
52
+ id=values["id"],
53
+ domain_id=values["domain_id"],
54
+ url=values["url"],
55
+ description=values["description"],
56
+ event_types=tuple(values["event_types"]),
57
+ branches=None if branches is None else tuple(branches),
58
+ created_by=values["created_by"],
59
+ created_at=values["created_at"],
60
+ clearances=tuple(values.get("clearances") or ()),
61
+ )
62
+
63
+
64
+ class WebhookRepository:
65
+ """Webhooks of one Domain, in the caller's transaction (scoped to the Domain)."""
66
+
67
+ def __init__(self, session: AsyncSession, domain_id: str) -> None:
68
+ self._session = session
69
+ self._domain = domain_id
70
+
71
+ async def add(
72
+ self,
73
+ *,
74
+ url: str,
75
+ description: str | None,
76
+ event_types: Sequence[str],
77
+ branches: Sequence[str] | None,
78
+ signing_key: StoredKey,
79
+ created_by: str,
80
+ clearances: Sequence[str] = (),
81
+ ) -> WebhookRow:
82
+ result = await self._session.execute(
83
+ insert(WEBHOOKS)
84
+ .values(
85
+ domain_id=self._domain,
86
+ id=new_id(ID_PREFIX),
87
+ url=url,
88
+ description=description,
89
+ event_types=list(event_types),
90
+ branches=None if branches is None else list(branches),
91
+ secret_hash=signing_key.secret_hash,
92
+ sealed_key=signing_key.sealed_key,
93
+ created_by=created_by,
94
+ clearances=sorted(set(clearances)),
95
+ )
96
+ .returning(*_COLUMNS)
97
+ )
98
+ return webhook_row(result.one())
99
+
100
+ async def all(self) -> list[WebhookRow]:
101
+ result = await self._session.execute(
102
+ select(*_COLUMNS)
103
+ .where(WEBHOOKS.c.domain_id == self._domain)
104
+ .order_by(WEBHOOKS.c.created_at, WEBHOOKS.c.id)
105
+ )
106
+ return [webhook_row(row) for row in result]
107
+
108
+ async def count(self) -> int:
109
+ found = await self._session.scalar(
110
+ select(func.count()).select_from(WEBHOOKS).where(WEBHOOKS.c.domain_id == self._domain)
111
+ )
112
+ return int(found or 0)
113
+
114
+ async def get(self, webhook_id: str, *, for_update: bool = False) -> WebhookRow | None:
115
+ """One webhook of the Domain, row-locked when `for_update`; None when absent."""
116
+ query = select(*_COLUMNS).where(
117
+ WEBHOOKS.c.domain_id == self._domain, WEBHOOKS.c.id == webhook_id
118
+ )
119
+ found = (
120
+ await self._session.execute(query.with_for_update() if for_update else query)
121
+ ).first()
122
+ return None if found is None else webhook_row(found)
123
+
124
+ async def set_clearances(self, webhook_id: str, clearances: Sequence[str]) -> WebhookRow | None:
125
+ """Replace the clearances of a webhook; None when it does not exist."""
126
+ result = await self._session.execute(
127
+ update(WEBHOOKS)
128
+ .where(WEBHOOKS.c.domain_id == self._domain, WEBHOOKS.c.id == webhook_id)
129
+ .values(clearances=sorted(set(clearances)))
130
+ .returning(*_COLUMNS)
131
+ )
132
+ found = result.first()
133
+ return None if found is None else webhook_row(found)
134
+
135
+ async def delete(self, webhook_id: str) -> bool:
136
+ """Delete a webhook and its deliveries; false when it does not exist."""
137
+ result = await self._session.execute(
138
+ delete(WEBHOOKS)
139
+ .where(WEBHOOKS.c.domain_id == self._domain, WEBHOOKS.c.id == webhook_id)
140
+ .returning(WEBHOOKS.c.id)
141
+ )
142
+ return result.first() is not None
@@ -0,0 +1,138 @@
1
+ """The `webhooks` settings section: targets Keeper accepts and how the worker delivers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Self
6
+
7
+ from pydantic import Field, model_validator
8
+
9
+ from lorebase_platform.secrets import SecretValue
10
+ from lorebase_platform.settings import SectionModel, Settings, register_section
11
+
12
+ SECTION = "webhooks"
13
+ MIN_ENCRYPTION_KEY_LENGTH = 32
14
+
15
+
16
+ class WebhookSettings(SectionModel):
17
+ """Webhook management (Keeper) and delivery (`deliver_pending`)."""
18
+
19
+ allowed_schemes: tuple[str, ...] = Field(
20
+ default=("https",), min_length=1, description="URL schemes a webhook may use."
21
+ )
22
+ allow_private_targets: bool = Field(
23
+ default=False,
24
+ description="Allow loopback, private and link-local target addresses. Off by default so "
25
+ "Domain administrators cannot reach internal services.",
26
+ )
27
+ max_per_domain: int = Field(default=20, ge=1, description="Webhooks of one Domain.")
28
+ secret_bytes: int = Field(default=32, ge=16, description="Random bytes of a new secret.")
29
+ encryption_key: SecretValue | None = Field(
30
+ default=None,
31
+ description="Key (at least 32 characters) that encrypts webhook signing keys in the "
32
+ "database, e.g. secret://env/LOREBASE_WEBHOOK_KEY; Keeper and Scribe need the same "
33
+ "value. Unset: Keeper refuses to create webhooks unless allow_unsealed_keys is true. "
34
+ "Once set, the delivery worker encrypts the keys stored usable before.",
35
+ )
36
+ allow_unsealed_keys: bool = Field(
37
+ default=False,
38
+ description="For local development only: without encryption_key, store the signing "
39
+ "keys of new webhooks usable instead of refusing to create them.",
40
+ )
41
+ previous_encryption_keys: tuple[SecretValue, ...] = Field(
42
+ default=(),
43
+ description="Earlier encryption keys, tried when the current one does not open a "
44
+ "signing key; the worker encrypts such keys again under encryption_key. Remove one "
45
+ "once no webhook uses it.",
46
+ )
47
+ signature_header: str = Field(default="Lorebase-Signature", min_length=1)
48
+ event_header: str = Field(default="Lorebase-Event", min_length=1)
49
+ delivery_header: str = Field(default="Lorebase-Delivery", min_length=1)
50
+ idempotency_header: str = Field(
51
+ default="Idempotency-Key",
52
+ min_length=1,
53
+ description="Header carrying `evt_<sequence>`, the same on every attempt of an event: "
54
+ "deliveries are at least once, receivers drop the repeats by it.",
55
+ )
56
+ masked_value: str = Field(
57
+ default="***",
58
+ description="What a sensitive value is replaced by for a webhook not cleared for it.",
59
+ )
60
+ erased_value: str = Field(
61
+ default="[erased]",
62
+ description="What a cleared webhook receives for a vault value erased before sending.",
63
+ )
64
+ timeout_s: float = Field(
65
+ default=10.0,
66
+ gt=0,
67
+ description="Time budget of one attempt as a whole: host lookup, connection to each "
68
+ "checked address, sending and the answer's status. Past it the attempt fails and is "
69
+ "retried like any other failure.",
70
+ )
71
+ connect_timeout_s: float = Field(
72
+ default=5.0,
73
+ gt=0,
74
+ description="Time to open a connection to one address; the next checked address is "
75
+ "tried after it, within timeout_s.",
76
+ )
77
+ max_attempts: int = Field(
78
+ default=8, ge=1, description="Attempts before a delivery is marked dead."
79
+ )
80
+ backoff_base_s: float = Field(default=30.0, gt=0, description="Delay after the first failure.")
81
+ backoff_factor: float = Field(default=2.0, ge=1, description="Growth of each next delay.")
82
+ backoff_max_s: float = Field(default=3600.0, gt=0, description="Longest delay.")
83
+ lease_s: float = Field(
84
+ default=120.0,
85
+ gt=0,
86
+ description="A claimed delivery is retried by another worker after this long if its "
87
+ "result was never recorded; a pass running longer than this is abandoned.",
88
+ )
89
+ batch: int = Field(default=100, ge=1, description="Events and deliveries per worker pass.")
90
+ concurrency: int = Field(
91
+ default=8,
92
+ ge=1,
93
+ description="Attempts of one pass in flight at the same time, so a target that does "
94
+ "not answer holds one of them, not the whole pass.",
95
+ )
96
+ pass_timeout_s: float = Field(
97
+ default=60.0,
98
+ gt=0,
99
+ description="A pass starts no attempt after this long; the deliveries it claimed and did "
100
+ "not attempt are released to the next pass without counting an attempt.",
101
+ )
102
+ changed_keys_max: int = Field(
103
+ default=100, ge=0, description="Changed Record keys listed per event payload."
104
+ )
105
+ retention_s: float = Field(
106
+ default=7 * 24 * 3600.0,
107
+ gt=0,
108
+ description="Finished deliveries older than this are deleted by the worker (outbox "
109
+ "events follow store.outbox.retention_s).",
110
+ )
111
+
112
+ @model_validator(mode="after")
113
+ def _consistent(self) -> Self:
114
+ if self.backoff_base_s > self.backoff_max_s:
115
+ raise ValueError("backoff_base_s cannot exceed backoff_max_s")
116
+ if self.connect_timeout_s > self.timeout_s:
117
+ raise ValueError("connect_timeout_s cannot exceed timeout_s")
118
+ if self.lease_s <= self.pass_timeout_s + self.timeout_s:
119
+ raise ValueError("lease_s must exceed pass_timeout_s + timeout_s")
120
+ keys = [self.encryption_key, *self.previous_encryption_keys]
121
+ if any(
122
+ key is not None and len(key.get_secret_value()) < MIN_ENCRYPTION_KEY_LENGTH
123
+ for key in keys
124
+ ):
125
+ raise ValueError(
126
+ f"webhook encryption keys need at least {MIN_ENCRYPTION_KEY_LENGTH} characters"
127
+ )
128
+ if self.previous_encryption_keys and self.encryption_key is None:
129
+ raise ValueError("previous_encryption_keys need an encryption_key")
130
+ return self
131
+
132
+
133
+ register_section(SECTION, WebhookSettings)
134
+
135
+
136
+ def webhook_settings(settings: Settings) -> WebhookSettings:
137
+ """The validated `webhooks` section."""
138
+ return settings.section(SECTION, WebhookSettings)
@@ -0,0 +1,50 @@
1
+ """Webhook secrets and signatures.
2
+
3
+ A webhook secret is shown once, when the webhook is created. Lorebase keeps only its SHA-256
4
+ digest, which is also the HMAC key: receivers compute `sha256(secret)` once and verify
5
+
6
+ signature header = "t=<unix seconds>,v1=<hex HMAC-SHA256(key, "<t>." + body)>"
7
+
8
+ The timestamp lets receivers refuse replays older than their tolerance. Storing the digest
9
+ keeps the secret itself out of the database; a leaked digest still allows forging signatures,
10
+ so the digest is stored sealed when `webhooks.encryption_key` is set (`webhooks.keys`). Rotate
11
+ a webhook (delete and create) when the database and that key may have been exposed.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import hashlib
17
+ import hmac
18
+ import secrets
19
+
20
+ SECRET_PREFIX = "whsec_"
21
+ SIGNATURE_VERSION = "v1"
22
+
23
+
24
+ def new_secret(nbytes: int) -> str:
25
+ """A new random webhook secret."""
26
+ return SECRET_PREFIX + secrets.token_urlsafe(nbytes)
27
+
28
+
29
+ def signing_key(secret: str) -> bytes:
30
+ """The stored digest of a secret, used as the HMAC key."""
31
+ return hashlib.sha256(secret.encode()).digest()
32
+
33
+
34
+ def signature(key: bytes, timestamp: int, body: bytes) -> str:
35
+ """Value of the signature header for `body` sent at `timestamp`."""
36
+ digest = hmac.new(key, f"{timestamp}.".encode() + body, hashlib.sha256).hexdigest()
37
+ return f"t={timestamp},{SIGNATURE_VERSION}={digest}"
38
+
39
+
40
+ def verify(secret: str, header: str, body: bytes, *, now: int, tolerance_s: int) -> bool:
41
+ """Receiver side: whether `header` signs `body` with `secret` within the tolerance."""
42
+ parts = dict(part.split("=", 1) for part in header.split(",") if "=" in part)
43
+ try:
44
+ timestamp = int(parts["t"])
45
+ except (KeyError, ValueError):
46
+ return False
47
+ if abs(now - timestamp) > tolerance_s:
48
+ return False
49
+ expected = signature(signing_key(secret), timestamp, body)
50
+ return hmac.compare_digest(expected, header)
@@ -0,0 +1,13 @@
1
+ Metadata-Version: 2.5
2
+ Name: lorebase-store
3
+ Version: 0.2.3
4
+ Summary: Lorebase store: persistence, repositories, migrations and materializer
5
+ Requires-Python: >=3.12
6
+ Requires-Dist: alembic>=1.13
7
+ Requires-Dist: asyncpg>=0.29
8
+ Requires-Dist: cryptography>=42
9
+ Requires-Dist: lorebase-core<0.3,>=0.2
10
+ Requires-Dist: lorebase-platform<0.3,>=0.2
11
+ Requires-Dist: opentelemetry-api>=1.25
12
+ Requires-Dist: pgvector>=0.3
13
+ Requires-Dist: sqlalchemy[asyncio]>=2.0.30
@@ -0,0 +1,163 @@
1
+ lorebase_store/__init__.py,sha256=PNiDER4qM19h9zdsdfgKt2_dT4WgYK7EguJ8RU2qA_g,22
2
+ lorebase_store/blobs.py,sha256=Ovrb2wnHt2t0vYBBsgzMBGQMo0_ojElHGGc4sDtCu2I,2762
3
+ lorebase_store/bm25.py,sha256=3jLns00FdVhZi0_pTFhXY_TSwPqHL27Xa8uvK5B-lHE,13818
4
+ lorebase_store/bulk.py,sha256=06KKakOqqv7NuN1xbuk9VgzlWKA5f8uj0Ev92kC7Z1Q,4979
5
+ lorebase_store/bundle.py,sha256=7LRwxYj6cIHz-IZxJC8YyV4tvHVTlvvMNK51csxrQyQ,74328
6
+ lorebase_store/changefeed.py,sha256=ztgumUGGeUEkNwh_7-LcBd1iIrbSAyujzCOCEWNShsU,9673
7
+ lorebase_store/db.py,sha256=4mGzh8vnmBZWGuayIIZiqvwnJbXEzVZrCD3rOr_i2EU,2165
8
+ lorebase_store/definer.py,sha256=r9_hujWxd-DsWlENZk-SkvVaYgWtN7BYyWdWpt5dfsc,1943
9
+ lorebase_store/digests.py,sha256=lC5FgSiR7ZUDrNdBBzCYJYc9TdRZYMUZZL_hUvSZDtQ,4102
10
+ lorebase_store/embeddings.py,sha256=mchl7VtNJXm7Y4qi-RkSrT-JdYACbLgfSJW6COjSliw,15943
11
+ lorebase_store/endpoint_modes.py,sha256=Jch2pu-pBhX6_veR4Waf4aNtoQ3S-LK1LtswFUPcV98,2388
12
+ lorebase_store/gc.py,sha256=1NTQkeszB4970oBwNfdF6Y9Xqe1Ao-VLOcXWrweSY8Q,39953
13
+ lorebase_store/indexes.py,sha256=KSlNvpGO0qYW813g-DI86XkTyT7_9ofEQvJQzVXLNq8,42076
14
+ lorebase_store/jobs.py,sha256=fIyJsuWr6LiokoDu8yZZOmKCLpfnD13_Jy11oL8FZYs,25251
15
+ lorebase_store/keyring.py,sha256=bs-6gHA7_t-yn9L19wWDAke8T85eHReOEYXc-UGkmVs,3109
16
+ lorebase_store/layout.py,sha256=wekZqurX-9-KPFCY4S3QUaaksr4t9frK9XPAGyOuVXA,15545
17
+ lorebase_store/migrations.py,sha256=HJF-_1QmuUCkeHUQtp1foz3WQ85geEZsKE5eyOOIrM0,4987
18
+ lorebase_store/nodestore.py,sha256=vL1ukHW6yKQX_WseuCXVdelPkl604IfIIOCrae6v_eM,15170
19
+ lorebase_store/objectstore.py,sha256=4D3JM31H-3Dc583yMBZVxfPl1x0AUGouszuLkC_jWi8,5938
20
+ lorebase_store/outbox_events.py,sha256=7uAgwHAh7B8-sIsKhUz-1QcbtButeBPJlzfJ8uru8sU,6653
21
+ lorebase_store/ratelimit.py,sha256=B128PTSD70YiKT__pV-fwzUX79sS8RcHbQIbb1pHt_Y,4527
22
+ lorebase_store/reads.py,sha256=6NPtwldtd9YwGBtcTVhI9T2BfUmKj8nb1KVMQo_xkUI,59349
23
+ lorebase_store/replicas.py,sha256=w9JJlQHoyeeelEVYdvWBuknR8yhtalxNAjNFXdONLks,16376
24
+ lorebase_store/runtime.py,sha256=6O8DXC5wmUkYyzTIufj_sFRDFPddxjL9wg9SlOvFZuc,3179
25
+ lorebase_store/schema_cache.py,sha256=DC3Mh7Qf2UEMyc1Br_Too6OjwEgnMWBxMoChuglfUoM,1728
26
+ lorebase_store/settings.py,sha256=U0ARHtwXmzL6Sl4ekUzWYsg6YXf8EgXNI76DDxqNPec,37851
27
+ lorebase_store/storage.py,sha256=tFdGKqzxRn7PRrb1le8I90Vhs7XgQT-kMAD8rNZs1N8,60783
28
+ lorebase_store/telemetry.py,sha256=f_xeyo-csHRJM_xzymf5-7ZmxF4V8Pqkb9Xei96YK7A,5344
29
+ lorebase_store/testing.py,sha256=BAJcDEji6_hhlLViWCHjekKhr5fzy-KKyRG306zDOMI,4245
30
+ lorebase_store/tracing.py,sha256=sfTkJEXoMHOizUgBtXyWvqTdr-StaN3spEtPLlruUmg,20820
31
+ lorebase_store/traverse.py,sha256=ebrY6Cp2ZEn9GGCXAWgzYWF2-tSJPLM1tu4zGbwo37M,24716
32
+ lorebase_store/auth/__init__.py,sha256=B71QGpW1Pg8InKOevimx7MC90ToSXXVEqmp8qqHBtl8,1359
33
+ lorebase_store/auth/api_keys.py,sha256=MecexRi41Q4GSQXwNVkZ7OeN9qhgRLtdOnE6sqcvUQY,5809
34
+ lorebase_store/auth/audit.py,sha256=F3WAFNNsHNi9AVAqw3DaeYlR_qAEQyvKxbqXn50nCsg,14446
35
+ lorebase_store/auth/checkpoints.py,sha256=GhaaUedf8ua8zpUFWCPWEkVl_JI6ugzWeu0Ak27Qnwk,7729
36
+ lorebase_store/auth/grants.py,sha256=o_7-Zc8qRJ0a7W2zhoGdvRsDmNXURrZuP8GFEgjevQA,6048
37
+ lorebase_store/auth/principals.py,sha256=O9uQ7ia-R3EygnS3cmMsZDzNg4G-4w3GXFkhLrEHblc,5835
38
+ lorebase_store/auth/scope.py,sha256=DGvHdOsqmJSOs1AS2ourG-JGeMFo8dIvln_Sk_97FLo,2713
39
+ lorebase_store/auth/sessions.py,sha256=_UXTl0LDJ3U-eZk0ll5xBcERgJbC98rM5xNSw9OkO6M,5599
40
+ lorebase_store/auth/unit.py,sha256=-GF0C-zXvFD4Mjf_FTUCKsJNEEpK9N_znKez5X5yisc,1975
41
+ lorebase_store/auth/usage.py,sha256=FR1IgUUqBQ2s_NDW9-2zvPqR590wqD-kh07RyQ5Bqes,2554
42
+ lorebase_store/commit/__init__.py,sha256=1LeF-Div59pBygS9LhkxpoiKZj9XDitCWjL8xRBBSe0,1004
43
+ lorebase_store/commit/changes.py,sha256=jHo6yRQ6QeEwJmBmxuyhyNeDQ5jdnjUq8JAa6n4-WEQ,1849
44
+ lorebase_store/commit/errors.py,sha256=m3CQBzefn6t1leFQBq0o-LP7gcRJISyqGSsoNwvJ1RI,2147
45
+ lorebase_store/commit/migrations.py,sha256=rSaT9mM06B8nA1O8h4Bn2Ql2uZuQ4wDAbatVKZ0dd38,13155
46
+ lorebase_store/commit/pipeline.py,sha256=yosi-ldfePGvvgqBSlaciQ2JHdGKqWwz4LySGgMKkeY,27566
47
+ lorebase_store/commit/schema.py,sha256=tZiowPmiCVOs2YFO2zTCzfQoyMsbr76woTvG9TLiPJ8,4649
48
+ lorebase_store/extraction/__init__.py,sha256=8vexzkAxE9zLWGG83E1aJfEadG0N7PMyJZRiWIGLyvg,1477
49
+ lorebase_store/extraction/items.py,sha256=ZeoaJAt6nnNjSuOprnyTNZq4YUyV2qc-hUeHo5XplLY,7453
50
+ lorebase_store/extraction/materialize.py,sha256=uKVpbjvKe6i5gb0N3r8tfnH3GFcVrFcj4ZWt2pE7p0A,14776
51
+ lorebase_store/extraction/proposal.py,sha256=c_mL5W_84BQnGIG1OKt9Hk7_0hvn5cUHcTqjotk-2F8,2557
52
+ lorebase_store/extraction/review.py,sha256=MSfAzvQMZXWNojxfc4E9bJYi_B_BG4xjRN-E9kmJLwI,6826
53
+ lorebase_store/materialize/__init__.py,sha256=0-fhxvxwVryEV4Yay0WHYBpRu0OXmwyO8QVxhs-lpPA,2688
54
+ lorebase_store/materialize/apply.py,sha256=eqMsVD198ak-ad1BtfTwp2Rof842iZn3qdW6b7UzKAE,17796
55
+ lorebase_store/materialize/background.py,sha256=WljOHRgODjBGhwATA4la1rUkVIEAITLxFZ70QvotGRQ,48732
56
+ lorebase_store/materialize/branch.py,sha256=NupMtd_d_QeZqSy3bmD9WUrel5mX5NLDsyNlwZkia1A,11157
57
+ lorebase_store/materialize/build.py,sha256=H4IdURNnN40Mirh9luj7Ii2xCFy62NK_HLF0Zjx4LPU,20269
58
+ lorebase_store/materialize/derived.py,sha256=yDFRhGZy0NL0CyC80L5IB_096USF3hfZSosRoKRMibw,22812
59
+ lorebase_store/materialize/lineage_backfill.py,sha256=xTcov8xa3vyH5iW4L0WruTuHfMuRt5y85eYynBDhv5Q,20272
60
+ lorebase_store/materialize/links.py,sha256=uoAlsxfXosq-kjd0PVQaKfgJJD9lwgMfmAuCWVtR4nA,11114
61
+ lorebase_store/materialize/overlay.py,sha256=__CRKrCOvdiY6mZe7vukGjZstd0WC-fjLaTxLJKncyA,12725
62
+ lorebase_store/materialize/paths.py,sha256=eDWCwpreyzzDrvRTxUb8A9c6TUlGTae8CbWqjQU23Kw,14599
63
+ lorebase_store/materialize/rebuild.py,sha256=XH0ye2SuNEmuuucIze1KuyLeuBjapwe7G6F_cGQMQqA,12726
64
+ lorebase_store/materialize/rows.py,sha256=dWd9FMReeSTDl7NRaa4v1glnxvF0kp-O5iqegCP-TvY,6161
65
+ lorebase_store/materialize/sensitivity.py,sha256=ei4O8MroGOQdyZj_CrgytbqbVrYVCUTn8obTA6KA7i8,3115
66
+ lorebase_store/materialize/snapshot.py,sha256=mVPX53ouHA1L5UiS5u1r3_6c_WoEziIDOHNkfWXuhbU,8125
67
+ lorebase_store/materialize/stale.py,sha256=cQLqX8koJ9RyTv8bT5J43nuvFxAlDHU2_NVzJ7_pcH0,17706
68
+ lorebase_store/materialize/verify.py,sha256=bUH7ZAq2nBrN3jZ-CBP9QckufpHalTimzWacbwfdpOQ,12762
69
+ lorebase_store/merging/__init__.py,sha256=zQztCkfgAIyC2Z0jdSrRfflTaduKS8dkKmynFOpVc7U,888
70
+ lorebase_store/merging/attempts.py,sha256=-klgIFOOZ-6SdiwCLY3-se6JuEiFUdbUoh6ytqvNtKU,12691
71
+ lorebase_store/merging/lineage.py,sha256=VBifjGEsVgJ3ebK7vUFhzPX2uQ_5DAmBfui72NPUcfM,5066
72
+ lorebase_store/merging/service.py,sha256=GkN8c83MerojalTOeVGDILPpg-pXVXqTuOWxGSi0gtQ,26052
73
+ lorebase_store/models/__init__.py,sha256=4Lyzk8Jx9WIevkhVmdW22Zu2lfoFoPn0u1qhJwIzZLI,2791
74
+ lorebase_store/models/base.py,sha256=9wt0IZzBv-A211yGgb2EFcz0-dpA2sPms5MTfUpnmyc,2029
75
+ lorebase_store/models/enums.py,sha256=p_YtbNNFK8MGVb4ZXczaZ2Fb6c2cBqgopqRT4_uPRAs,3305
76
+ lorebase_store/models/governance.py,sha256=-w5o5g0dt4uDgw8P9rkzo3iGwgzGURRoCBORBpRaJAc,9112
77
+ lorebase_store/models/ingestion.py,sha256=LcXMnwvafk1TBR8xh6oE-7dm1hy9uXbXxlcOWvqq3t4,11124
78
+ lorebase_store/models/materialized.py,sha256=NZqf7nVRI0KbNyDkIQVmRVVx36pvTPfVffoWmWzE3hc,11047
79
+ lorebase_store/models/proposals.py,sha256=VOI96cdjDPs8hJxMLBOkWg_U21enZWZj252MRzAFhBc,6650
80
+ lorebase_store/models/queues.py,sha256=ZhI-CwepbLx9Lo2WfFJBAMUSvklT1L2yoo-M_CdtWgg,3357
81
+ lorebase_store/models/tracing.py,sha256=TPVwedRNAEtMj1nMFBj7a2IhL8n81Vv2W9PEWsI8tVQ,4361
82
+ lorebase_store/models/versioning.py,sha256=GFfWe9PbIXqc9QY1RK9ryRzbALYh48HpTyZezpNq4Dg,7106
83
+ lorebase_store/models/webhooks.py,sha256=r-9yjuOrRhvCDMWBh32Y-vrZuQhgeBcUxPoIthboMaU,3796
84
+ lorebase_store/profiling/__init__.py,sha256=5wUCtS7Q4aDZC2N3sO1eovb3lkDXx-7EzPvDiEYOFeA,1626
85
+ lorebase_store/profiling/delimited.py,sha256=vhKaD34icYjcG5g-B3Uqds5RKXhbCW5VQUK9-BViBcw,4211
86
+ lorebase_store/profiling/postgres.py,sha256=1eg0Uu5GsMX7x4eq-1zYpOWS2tKj7XMEpue_EnnKV-8,7890
87
+ lorebase_store/profiling/record_files.py,sha256=m8OKbLK2c6oYlY7ZCZApWusTg6RdFd3kpNsJ4uZousU,5226
88
+ lorebase_store/profiling/records.py,sha256=lMAFGLRrr53eoDXE6i7xXgUKHBRq9vwMzBXpvBjXqDw,3610
89
+ lorebase_store/profiling/sql_databases.py,sha256=6HDWYpkJWJvXrPZYfqKlan8wbzjU1n5h3uoMqSQOApw,11672
90
+ lorebase_store/repositories/__init__.py,sha256=Vrt3CUbbQCdCEc05whbjutCaw4Ib4SCRX0QyZFY-p1s,3978
91
+ lorebase_store/repositories/changesets.py,sha256=a4VhoueHECVQx3c51WME9JwAyHvgW4MAT4021U_1DNg,2166
92
+ lorebase_store/repositories/deferred_events.py,sha256=KIdknMeqZdwmmY4xPVq1_oTHgcbtc21A_J8G9IDYu_4,5442
93
+ lorebase_store/repositories/documents.py,sha256=mBooqei0W94QtJ6W_auS2BzgIxf5fuaD1nfi92P0OeY,10976
94
+ lorebase_store/repositories/domains.py,sha256=T8Gaui8JFQZ4AirpUa1LjtFgMM2Iivhk01Gk-rS3opk,3278
95
+ lorebase_store/repositories/events.py,sha256=_qj56nK3DiME8uyUR6_IcVtvqRytrWXDEezyW3DXWzE,7990
96
+ lorebase_store/repositories/index_view.py,sha256=BBuZXf9LQeWpkKo1QI8-EJErPlVPVgO592WjjjAY0H8,21277
97
+ lorebase_store/repositories/invocations.py,sha256=gWQNhowWn97piO6mdKfE1PrKd49-HqYBdUyZ8crf26Y,2247
98
+ lorebase_store/repositories/materializations.py,sha256=ZhNWvtDjkCzoaHqn9vJUKchQmU4llqSJ0f6z_QaI9hM,10304
99
+ lorebase_store/repositories/materialized.py,sha256=F2YA2H42uBtI6nZIfrLQKHch5RNqzmxE67-W848q5W8,44173
100
+ lorebase_store/repositories/merges.py,sha256=ktGg6uiDElEkATTRENEo6ZWPNvjV4NYDrmLXC80x8w8,15804
101
+ lorebase_store/repositories/proposal_items.py,sha256=XTe7lKHKmX8zB0Q0x6GMTeyyb2-dSNUVsSEJlVR4l2w,6921
102
+ lorebase_store/repositories/proposals.py,sha256=aF5vnzjLGxGZH_l61Wm4_Wjycqd90lGn5r6f99YidNo,6492
103
+ lorebase_store/repositories/provenance.py,sha256=nBK-8AC6phLpzvHA3tc62fWX8bKS0MrefjPt9FqABrQ,2655
104
+ lorebase_store/repositories/refs.py,sha256=pTV9S1xrblRPpS1Yn4uisZfscpInY1gsqGNDJn60ORM,16590
105
+ lorebase_store/repositories/revisions.py,sha256=gjPx5V9mz-KnNSUuenuB7d90SRPf1pzVBV4Bl-RN5do,9607
106
+ lorebase_store/repositories/scope.py,sha256=Sykom1CVJI8H5-Wq9omzchXdhMML98hA3_XeAV4Amrg,1063
107
+ lorebase_store/repositories/source_runs.py,sha256=cMRJuEN6uxdx_iuC1ko3ExmpDRJOuiN1jnVclVGkOmY,4277
108
+ lorebase_store/repositories/suggestions.py,sha256=c7whqpZq0Iawxjnb4bf1bRXoDy3bSYk0vjZoBUvhOfs,25325
109
+ lorebase_store/vault/__init__.py,sha256=sau_lwwm-sGjgFFsH1p3WAmJdLMP5TYJH3fLqKKY3A8,1169
110
+ lorebase_store/vault/keys.py,sha256=L22sv6TScdx7yz_Y9ciTDQBwJOVrNLlf8j0BEUB4xFw,8699
111
+ lorebase_store/vault/quotes.py,sha256=VUaVxvQsn1PBnSjimXKVnHOwvM4josJaIN4z2C_uoOM,5385
112
+ lorebase_store/vault/rotation.py,sha256=-0dQS32xzXGJMNZuCrfjhIsxOrARAo_kUS_9wdAB7TA,7705
113
+ lorebase_store/vault/settings.py,sha256=NacLjXVAbW0UqtpK_R-qlmrLhu9ciT2Nesuf9mwVo9I,6452
114
+ lorebase_store/vault/store.py,sha256=uc8_Zm8xfOorwstc7wfXBWElC_Gml-K0VWBqWeVb5Cs,37459
115
+ lorebase_store/vault/transfer.py,sha256=RqW6pUYCJ9QbSX_A0hP47bKca84hOxyQ6CD4gh-zAQ0,21820
116
+ lorebase_store/webhooks/__init__.py,sha256=gZtmp6q9AWHxHLpuS07qAOk8OjDmELHUO7DzeFG7ISw,780
117
+ lorebase_store/webhooks/delivery.py,sha256=JwPwnbbjN7xQgR94DcWbwBoGPqaa1bFgJU1Cn-Ylu2I,26017
118
+ lorebase_store/webhooks/keys.py,sha256=i4tmjSPvR1ZGcASxUPUKyU6Tvbmt6Nht3RbFQ2xsvlY,4819
119
+ lorebase_store/webhooks/repository.py,sha256=-SfzHk0Kv5eWJ4_ShaoWs2E_7pceIMYAniNLLh2Rm4Y,4894
120
+ lorebase_store/webhooks/settings.py,sha256=c0UQ-XTmARFj8FeSN6fyi5Efa_qd4o5H_8aOD1TC32U,6148
121
+ lorebase_store/webhooks/signing.py,sha256=6S2FNtF0-jhG0B9QV9aLdyyAI5aHrsZhdT7_UdMf8Ro,1913
122
+ lorebase_store/_migrations/env.py,sha256=PlYCxYsyZns9DD2Jgls6o6ptZNiQSoU94qdXhZSrcvM,3534
123
+ lorebase_store/_migrations/script.py.mako,sha256=rIDRZIc2ykfzBOs7iolUSB4ja9y2Wc3FFx5lxy7g09M,632
124
+ lorebase_store/_migrations/versions/0001_initial_schema.py,sha256=-2IuDifNsvztNRyrqe4wgh83AI00vB3Sdqh8kuV_fYo,43852
125
+ lorebase_store/_migrations/versions/0002_merge_attempt_details.py,sha256=8QsNbw9T8F8Geixps0Xfn1GiZS3k7Gjn0UFuEr4Qe74,2699
126
+ lorebase_store/_migrations/versions/0003_webhooks.py,sha256=Ga1OPS9P2B-FZe8nTNKIHdpmpaDRvT1TWzxhna1Hes8,4733
127
+ lorebase_store/_migrations/versions/0004_defer_revision_references.py,sha256=QkX6DPZcSS5l7lOVWQZkteWAaPwEnvGEg8AActwB_Hw,1325
128
+ lorebase_store/_migrations/versions/0005_security_definer_reads.py,sha256=ixcbJU9LnSqON6lnN2-H-z2WLVQukT86ep03VZBqPSE,18233
129
+ lorebase_store/_migrations/versions/0006_api_key_usage.py,sha256=o7_zEpHREKyaLGXTYD4mjfMf2YI6a88R4k5dYQMlwBQ,1491
130
+ lorebase_store/_migrations/versions/0007_document_extraction.py,sha256=VtUhBjxbH1mpSmvMs58Fvs1MpVnuYOdigRGRUgP9S7E,5860
131
+ lorebase_store/_migrations/versions/0008_definer_argument_checks.py,sha256=SxX37etFtPpNoR7ZY-YmtFpL67I_ZLz94mqTpRYBqoc,7034
132
+ lorebase_store/_migrations/versions/0009_api_key_networks.py,sha256=DKrKwHGVySsA5oNoFBjrj0u0L7r8RJpO69T1STF_tIQ,890
133
+ lorebase_store/_migrations/versions/0010_overlay_branches.py,sha256=b32yqZwCLY7i22s9_SLg4vmMhaqCRPjWgbErPWaK82M,25016
134
+ lorebase_store/_migrations/versions/0011_branch_scoped_search_index.py,sha256=p7D-YAFXl4XEaanb6PrVpy4v0cnWMO20wAAjINE-a1I,9019
135
+ lorebase_store/_migrations/versions/0012_overlay_neighborhood_lookups.py,sha256=ddX9gX5rxRdT7i40UWJFbZNyryNxhVcwbTBSP1SDa00,8617
136
+ lorebase_store/_migrations/versions/0013_web_sessions.py,sha256=2CoXk2sgL9ilmut-eBYBDHxFJzuJsBrAWns8oTN5IK8,2706
137
+ lorebase_store/_migrations/versions/0014_embedding_jobs.py,sha256=Vc0ikNxfc2zI6O07ihXHIy2Dd3ZS3kM0aKelUz2_3No,2772
138
+ lorebase_store/_migrations/versions/0015_append_only_audit_log.py,sha256=7IcdT6kBA5RuoReFJkgemykJduOSVz6r4ofKIYeAgWQ,3050
139
+ lorebase_store/_migrations/versions/0016_sealed_webhook_keys.py,sha256=ShAET0lduSQscOTa3OAhy6c05V35zBAvtQsKv03uiXE,2167
140
+ lorebase_store/_migrations/versions/0017_background_branch_copies.py,sha256=szv90WrkCM0mCmmNZmF7y4oFsW0m4Zs01_hjgmwPuqk,3369
141
+ lorebase_store/_migrations/versions/0018_search_lexicon.py,sha256=3KJcKrDx0usRAsZ4WkiVDmhxcMgQpb0hDWIlD7NueqQ,20829
142
+ lorebase_store/_migrations/versions/0019_vault_bindings.py,sha256=ciG8gO6i__RrVsBu32Pkc2Jy1C9smHrfOhL7Ejg67iw,2247
143
+ lorebase_store/_migrations/versions/0020_outbox_consumers.py,sha256=xeC083IeaNsjZ0AA-NqBcZbKMudgk1cOCEDgU6cvLIQ,5709
144
+ lorebase_store/_migrations/versions/0021_schema_suggestions.py,sha256=j3jJ2MtP8-vWNP-8Zvci_kQR7DEB7hScQjqW2sEI0Vw,4307
145
+ lorebase_store/_migrations/versions/0022_read_audit_and_shared_rate_limits.py,sha256=pXmIjcvIMBe9AD_oYJg9RZYlqZkZIstZtaJYYYrCPG4,5197
146
+ lorebase_store/_migrations/versions/0023_agent_trace.py,sha256=Z_BDxsAgX7R11fVIbTmsYxtK1fEHrqTBzFBO18GSUj0,3967
147
+ lorebase_store/_migrations/versions/0024_embedding_template_keys.py,sha256=qWChmPIhGJIuOVdU9jHsWcQZFSAdIHJNz5tXlgUDHk8,7898
148
+ lorebase_store/_migrations/versions/0025_snapshot_refs.py,sha256=oTYN3G_7pbERLFz72VcbykjMsOZwgML3HCwzakM47hc,1213
149
+ lorebase_store/_migrations/versions/0026_plain_reverse_link_index.py,sha256=wITdejx4XpzGKYoH_uT4LpolnkisJbiJQFppXb455q4,3900
150
+ lorebase_store/_migrations/versions/0027_vault_rotation_and_tombstones.py,sha256=jYxJK89VfrpEgVOLWmG7fCjHh8XSiKbPMvmqRXazZRk,5503
151
+ lorebase_store/_migrations/versions/0028_operation_deferred_events.py,sha256=-zCvkPLmXOMLAZ5spj8Zt2n9MHVK0_0rqG23b9ELNvg,2556
152
+ lorebase_store/_migrations/versions/0029_agent_trace_sessions.py,sha256=OrvTaERUoNl1dc5_HfgoWf6Mbl5teRNrCEVx7G10YdA,4689
153
+ lorebase_store/_migrations/versions/0030_path_fanout.py,sha256=kir14QfKXTgCnlX2I1UlmKbbU20n5MeX1PwDfaiJMVA,931
154
+ lorebase_store/_migrations/versions/0031_branch_builds.py,sha256=fjBre0w7ouJtiKeCoaB2o68MgXU4ksi9iyUQFEuAHNg,3291
155
+ lorebase_store/_migrations/versions/0032_lineage_revisions.py,sha256=LTJDAUHSDbwbTo0T_6-mgYTNsmfR7jSOh0MGdKFalGs,1067
156
+ lorebase_store/_migrations/versions/0033_vault_moved_tokens.py,sha256=yW_316lRs9qnwoZ-J7N9aZ78aVMsLnQ4fND7F_tCo0E,891
157
+ lorebase_store/_migrations/versions/0034_inference_endpoint_modes.py,sha256=z-8fKTFCQyXy-TcGmIPNiKxswhxK-4LFbASv7ERNzQI,2574
158
+ lorebase_store/_migrations/versions/0035_search_truncation.py,sha256=fCqOf7CCX1TYYYmalPj73Iddp_PHN06VWKsGnIhdwgM,4136
159
+ lorebase_store/_migrations/versions/0036_lineage_maps.py,sha256=9fSyUbI8cVkMHXPhjsu8_qJBlFEaPCISgP1ttgDv5bA,13494
160
+ lorebase_store/_migrations/versions/0037_bm25_search.py,sha256=__P7OlhcmK_nCztsXP-wt1ubkzEVkBx9mw6bDxlq8I0,17297
161
+ lorebase_store-0.2.3.dist-info/METADATA,sha256=egOn_416Kpbk3mo-1vRR6IPcx4Xr2SrR0by_Z7sYs4c,445
162
+ lorebase_store-0.2.3.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
163
+ lorebase_store-0.2.3.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any