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,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,,
|