rememberstack 0.1.0__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.
- rememberstack/__init__.py +9 -0
- rememberstack/adapters/__init__.py +42 -0
- rememberstack/adapters/codex_writer.py +221 -0
- rememberstack/adapters/markitdown_converter.py +42 -0
- rememberstack/adapters/openrouter.py +136 -0
- rememberstack/adapters/selfhost/__init__.py +54 -0
- rememberstack/adapters/selfhost/forget.py +66 -0
- rememberstack/adapters/selfhost/git.py +374 -0
- rememberstack/adapters/selfhost/lance.py +328 -0
- rememberstack/adapters/selfhost/minio.py +279 -0
- rememberstack/adapters/selfhost/mounts.py +249 -0
- rememberstack/adapters/selfhost/object_store.py +130 -0
- rememberstack/adapters/selfhost/projection.py +80 -0
- rememberstack/adapters/selfhost/queue.py +137 -0
- rememberstack/adapters/selfhost/telemetry.py +45 -0
- rememberstack/adapters/selfhost/watcher.py +70 -0
- rememberstack/adapters/testing/__init__.py +15 -0
- rememberstack/adapters/testing/cost_meter.py +13 -0
- rememberstack/adapters/testing/model_provider.py +83 -0
- rememberstack/adapters/testing/queue.py +43 -0
- rememberstack/adapters/testing/telemetry.py +22 -0
- rememberstack/client.py +19 -0
- rememberstack/core/__init__.py +127 -0
- rememberstack/core/blockizer.py +189 -0
- rememberstack/core/chunker.py +216 -0
- rememberstack/core/consumption_skill.py +275 -0
- rememberstack/core/conversion.py +76 -0
- rememberstack/core/core_manifest.py +598 -0
- rememberstack/core/extension_packs.py +124 -0
- rememberstack/core/forget.py +17 -0
- rememberstack/core/knowledge_authored.py +276 -0
- rememberstack/core/knowledge_compile.py +215 -0
- rememberstack/core/knowledge_fact_sheet.py +210 -0
- rememberstack/core/knowledge_hashing.py +68 -0
- rememberstack/core/knowledge_planner.py +64 -0
- rememberstack/core/knowledge_writer.py +175 -0
- rememberstack/core/ranking.py +200 -0
- rememberstack/core/recipe_linter.py +149 -0
- rememberstack/core/section_snap.py +209 -0
- rememberstack/core/storage_routing.py +27 -0
- rememberstack/eval/__init__.py +53 -0
- rememberstack/eval/consumption.py +141 -0
- rememberstack/eval/contradiction.py +184 -0
- rememberstack/eval/harness.py +136 -0
- rememberstack/eval/lifecycle.py +400 -0
- rememberstack/eval/operational_scale.py +49 -0
- rememberstack/eval/resolution.py +255 -0
- rememberstack/eval/retrieval_spikes.py +50 -0
- rememberstack/eval/skeleton.py +231 -0
- rememberstack/llm/__init__.py +1 -0
- rememberstack/model/__init__.py +589 -0
- rememberstack/model/adjudication.py +100 -0
- rememberstack/model/auth.py +27 -0
- rememberstack/model/blocks.py +30 -0
- rememberstack/model/chunks.py +190 -0
- rememberstack/model/claims.py +162 -0
- rememberstack/model/client.py +98 -0
- rememberstack/model/clustering.py +54 -0
- rememberstack/model/component_version.py +124 -0
- rememberstack/model/consumption.py +88 -0
- rememberstack/model/conversion.py +31 -0
- rememberstack/model/deployment.py +53 -0
- rememberstack/model/documents.py +168 -0
- rememberstack/model/envelope.py +513 -0
- rememberstack/model/evaluation.py +72 -0
- rememberstack/model/forget.py +143 -0
- rememberstack/model/git.py +13 -0
- rememberstack/model/knowledge.py +840 -0
- rememberstack/model/knowledge_authored.py +325 -0
- rememberstack/model/knowledge_planner.py +431 -0
- rememberstack/model/lifecycle.py +42 -0
- rememberstack/model/model_provider.py +78 -0
- rememberstack/model/mounts.py +24 -0
- rememberstack/model/object_store.py +21 -0
- rememberstack/model/operational_scale.py +59 -0
- rememberstack/model/operations.py +153 -0
- rememberstack/model/processing.py +228 -0
- rememberstack/model/queue.py +73 -0
- rememberstack/model/recipes.py +83 -0
- rememberstack/model/relations.py +79 -0
- rememberstack/model/resolution.py +83 -0
- rememberstack/model/retrieval_spikes.py +62 -0
- rememberstack/model/sections.py +120 -0
- rememberstack/model/telemetry.py +30 -0
- rememberstack/ports/__init__.py +29 -0
- rememberstack/ports/auth.py +16 -0
- rememberstack/ports/connector.py +23 -0
- rememberstack/ports/cost_meter.py +17 -0
- rememberstack/ports/forget.py +20 -0
- rememberstack/ports/git.py +20 -0
- rememberstack/ports/model_provider.py +28 -0
- rememberstack/ports/mounts.py +16 -0
- rememberstack/ports/object_store.py +27 -0
- rememberstack/ports/p1_index.py +92 -0
- rememberstack/ports/purge.py +93 -0
- rememberstack/ports/queue.py +23 -0
- rememberstack/ports/telemetry.py +21 -0
- rememberstack/profiles/__init__.py +22 -0
- rememberstack/profiles/selfhost.py +324 -0
- rememberstack/profiles/selfhost_forget.py +158 -0
- rememberstack/profiles/selfhost_operations.py +95 -0
- rememberstack/py.typed +1 -0
- rememberstack/spine/__init__.py +93 -0
- rememberstack/spine/admission.py +26 -0
- rememberstack/spine/backfill.py +168 -0
- rememberstack/spine/catalog_contract.py +742 -0
- rememberstack/spine/chunk_catalog.py +237 -0
- rememberstack/spine/claim_catalog.py +298 -0
- rememberstack/spine/clustering.py +740 -0
- rememberstack/spine/component_versions.py +208 -0
- rememberstack/spine/consumption.py +81 -0
- rememberstack/spine/deployment_bootstrap.py +445 -0
- rememberstack/spine/document_catalog.py +621 -0
- rememberstack/spine/entity_registry.py +205 -0
- rememberstack/spine/extension_packs.py +220 -0
- rememberstack/spine/fact_catalog.py +571 -0
- rememberstack/spine/forget.py +1753 -0
- rememberstack/spine/knowledge.py +5467 -0
- rememberstack/spine/lifecycle.py +1071 -0
- rememberstack/spine/migrations/__init__.py +1 -0
- rememberstack/spine/migrations/_helpers.py +153 -0
- rememberstack/spine/migrations/env.py +58 -0
- rememberstack/spine/migrations/script.py.mako +27 -0
- rememberstack/spine/migrations/versions/__init__.py +1 -0
- rememberstack/spine/migrations/versions/p0_02_0001_extensions_enums.py +189 -0
- rememberstack/spine/migrations/versions/p0_02_0002_infrastructure_registries.py +321 -0
- rememberstack/spine/migrations/versions/p0_02_0003_entities_evaluation_e0_e1.py +631 -0
- rememberstack/spine/migrations/versions/p0_02_0004_claims_facts_evidence.py +411 -0
- rememberstack/spine/migrations/versions/p0_02_0005_projection_knowledge_retrieval.py +391 -0
- rememberstack/spine/migrations/versions/p0_02_0006_partitions_views.py +158 -0
- rememberstack/spine/migrations/versions/p2_06_0007_invalidated_outcome.py +26 -0
- rememberstack/spine/migrations/versions/p3_01_0008_document_version_target.py +58 -0
- rememberstack/spine/migrations/versions/p3_05_0009_reconcile_stage.py +27 -0
- rememberstack/spine/migrations/versions/p3_07_0010_lifecycle_eval_suite.py +25 -0
- rememberstack/spine/migrations/versions/p4_01_0011_survivor_view_rewrite.py +57 -0
- rememberstack/spine/migrations/versions/p6_02_0012_knowledge_compile_recovery.py +58 -0
- rememberstack/spine/migrations/versions/p6_04_0013_knowledge_writer_ledger.py +46 -0
- rememberstack/spine/migrations/versions/p6_05_0014_knowledge_planner_runtime.py +217 -0
- rememberstack/spine/migrations/versions/p6_06_0015_authored_dispatch_runtime.py +38 -0
- rememberstack/spine/migrations/versions/p7_02_0016_operational_eval_suite.py +19 -0
- rememberstack/spine/migrations/versions/p7_05_0017_hard_forget.py +55 -0
- rememberstack/spine/observation_adjudication.py +778 -0
- rememberstack/spine/operations.py +298 -0
- rememberstack/spine/projection.py +662 -0
- rememberstack/spine/recipes.py +276 -0
- rememberstack/spine/resolver.py +763 -0
- rememberstack/spine/review.py +650 -0
- rememberstack/spine/settings.py +22 -0
- rememberstack/spine/supersession.py +510 -0
- rememberstack/spine/sync.py +128 -0
- rememberstack/spine/work_ledger.py +816 -0
- rememberstack/surfaces/__init__.py +110 -0
- rememberstack/surfaces/cli.py +447 -0
- rememberstack/surfaces/consumption_skill.py +87 -0
- rememberstack/surfaces/graph_queries.py +698 -0
- rememberstack/surfaces/http_api.py +377 -0
- rememberstack/surfaces/mcp.py +67 -0
- rememberstack/surfaces/query_engine.py +1591 -0
- rememberstack/surfaces/recipe_executor.py +185 -0
- rememberstack/surfaces/recipe_surface.py +219 -0
- rememberstack/surfaces/remote_mcp.py +133 -0
- rememberstack/surfaces/sdk.py +324 -0
- rememberstack/workers/__init__.py +155 -0
- rememberstack/workers/base.py +312 -0
- rememberstack/workers/e0.py +577 -0
- rememberstack/workers/e1.py +425 -0
- rememberstack/workers/e2.py +525 -0
- rememberstack/workers/e3.py +434 -0
- rememberstack/workers/forget.py +299 -0
- rememberstack/workers/knowledge_authored.py +146 -0
- rememberstack/workers/knowledge_driver.py +735 -0
- rememberstack/workers/knowledge_fact_sheet.py +123 -0
- rememberstack/workers/knowledge_planner.py +325 -0
- rememberstack/workers/knowledge_writer.py +393 -0
- rememberstack/workers/operations.py +42 -0
- rememberstack/workers/p1.py +234 -0
- rememberstack/workers/p2.py +513 -0
- rememberstack/workers/p2_analytics.py +276 -0
- rememberstack/workers/p3.py +673 -0
- rememberstack/workers/reconcile.py +485 -0
- rememberstack/workers/sync.py +168 -0
- rememberstack-0.1.0.dist-info/METADATA +213 -0
- rememberstack-0.1.0.dist-info/RECORD +186 -0
- rememberstack-0.1.0.dist-info/WHEEL +4 -0
- rememberstack-0.1.0.dist-info/entry_points.txt +2 -0
- rememberstack-0.1.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
"""The E3 fact catalog: relation/observation upserts, evidence, D54 counting.
|
|
2
|
+
|
|
3
|
+
Redundancy collapses here (D2): the same fact from many claims is one row plus
|
|
4
|
+
evidence links, and `evidence_count` is the number of DISTINCT DOCUMENT
|
|
5
|
+
LINEAGES with current-testimony support — re-extraction generations, document
|
|
6
|
+
versions, and within-document repetition never inflate it (D54).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from collections.abc import Iterator
|
|
10
|
+
from contextlib import contextmanager
|
|
11
|
+
import re
|
|
12
|
+
from typing import Final
|
|
13
|
+
from uuid import UUID
|
|
14
|
+
from uuid import uuid4
|
|
15
|
+
|
|
16
|
+
from sqlalchemy import bindparam
|
|
17
|
+
from sqlalchemy import JSON
|
|
18
|
+
from sqlalchemy import text
|
|
19
|
+
from sqlalchemy.engine import Engine
|
|
20
|
+
|
|
21
|
+
from rememberstack.model import FactForLabeling
|
|
22
|
+
from rememberstack.model import ObservationForEmbedding
|
|
23
|
+
from rememberstack.model import OtherPredicateGrammarError
|
|
24
|
+
from rememberstack.model import RelationUpsert
|
|
25
|
+
|
|
26
|
+
OTHER_PREDICATE_GRAMMAR: Final = re.compile(r"other:[a-z][a-z0-9_]{1,40}")
|
|
27
|
+
"""The D5 escape-value grammar: short snake_case behind the other: prefix."""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class FactCatalog:
|
|
31
|
+
"""Relation and observation writes over an explicitly composed engine."""
|
|
32
|
+
|
|
33
|
+
def __init__(self, *, engine: Engine) -> None:
|
|
34
|
+
"""Bind the catalog to the spine database."""
|
|
35
|
+
self._engine = engine
|
|
36
|
+
|
|
37
|
+
def upsert_relation(
|
|
38
|
+
self,
|
|
39
|
+
*,
|
|
40
|
+
deployment_id: UUID,
|
|
41
|
+
subject_entity_id: UUID,
|
|
42
|
+
predicate: str,
|
|
43
|
+
object_entity_id: UUID,
|
|
44
|
+
claim_id: UUID,
|
|
45
|
+
doc_id: UUID,
|
|
46
|
+
normalizer_version: str,
|
|
47
|
+
) -> RelationUpsert:
|
|
48
|
+
"""Land one asserted fact: one relation row, evidence-once, recount.
|
|
49
|
+
|
|
50
|
+
An existing believed relation for the (s, p, o) key is reused; the
|
|
51
|
+
evidence link is ON CONFLICT DO NOTHING (a retry can never inflate the
|
|
52
|
+
count); the D54 recount runs in the same transaction.
|
|
53
|
+
"""
|
|
54
|
+
with self._engine.begin() as connection:
|
|
55
|
+
key = {
|
|
56
|
+
"deployment_id": deployment_id,
|
|
57
|
+
"subject_entity_id": subject_entity_id,
|
|
58
|
+
"predicate": predicate,
|
|
59
|
+
"object_entity_id": object_entity_id,
|
|
60
|
+
}
|
|
61
|
+
# serialize concurrent upserts of one fact key (Codex review):
|
|
62
|
+
connection.execute(
|
|
63
|
+
_LOCK_FACT,
|
|
64
|
+
{
|
|
65
|
+
"key": f"{deployment_id}:rel:{subject_entity_id}"
|
|
66
|
+
f":{predicate}:{object_entity_id}"
|
|
67
|
+
},
|
|
68
|
+
)
|
|
69
|
+
existing = connection.execute(_SELECT_RELATION, key).scalar_one_or_none()
|
|
70
|
+
relation_id = existing if existing is not None else uuid4()
|
|
71
|
+
if existing is None:
|
|
72
|
+
# a re-occurring fact (a prior CLOSED row exists) starts a
|
|
73
|
+
# fresh row whose window opens at the re-occurrence boundary,
|
|
74
|
+
# so the EXCLUDE constraint's non-overlap holds and the new
|
|
75
|
+
# spell is adjudicable (Codex review; D41 refines the time):
|
|
76
|
+
reoccurrence_boundary = connection.execute(
|
|
77
|
+
_LATEST_CLOSED_UNTIL, key
|
|
78
|
+
).scalar_one_or_none()
|
|
79
|
+
connection.execute(
|
|
80
|
+
_INSERT_RELATION,
|
|
81
|
+
{
|
|
82
|
+
**key,
|
|
83
|
+
"relation_id": relation_id,
|
|
84
|
+
"valid_from": reoccurrence_boundary,
|
|
85
|
+
"normalizer_version": normalizer_version,
|
|
86
|
+
},
|
|
87
|
+
)
|
|
88
|
+
connection.execute( # the D5 promotion funnel's ranking input
|
|
89
|
+
_BUMP_PREDICATE_USAGE,
|
|
90
|
+
{"deployment_id": deployment_id, "predicate": predicate},
|
|
91
|
+
)
|
|
92
|
+
connection.execute(
|
|
93
|
+
_INSERT_RELATION_EVIDENCE,
|
|
94
|
+
{
|
|
95
|
+
"deployment_id": deployment_id,
|
|
96
|
+
"relation_id": relation_id,
|
|
97
|
+
"claim_id": claim_id,
|
|
98
|
+
"doc_id": doc_id,
|
|
99
|
+
"normalizer_version": normalizer_version,
|
|
100
|
+
},
|
|
101
|
+
)
|
|
102
|
+
connection.execute(_RECOUNT_RELATION, {"relation_id": relation_id})
|
|
103
|
+
return RelationUpsert(relation_id=relation_id, created=existing is None)
|
|
104
|
+
|
|
105
|
+
def upsert_observation(
|
|
106
|
+
self,
|
|
107
|
+
*,
|
|
108
|
+
deployment_id: UUID,
|
|
109
|
+
subject_entity_id: UUID,
|
|
110
|
+
statement: str,
|
|
111
|
+
claim_id: UUID,
|
|
112
|
+
doc_id: UUID,
|
|
113
|
+
normalizer_version: str,
|
|
114
|
+
) -> UUID:
|
|
115
|
+
"""Land one entity-anchored statement (D43) with the novelty gate.
|
|
116
|
+
|
|
117
|
+
The gate: an identical live statement on the entity is the same
|
|
118
|
+
observation (evidence collapses onto it); anything else coexists as a
|
|
119
|
+
new row — fail-safe, never silently resolved. Each mint records an
|
|
120
|
+
append-only `add` adjudication by the novelty_gate rung (D4).
|
|
121
|
+
"""
|
|
122
|
+
with self._engine.begin() as connection:
|
|
123
|
+
connection.execute(
|
|
124
|
+
_LOCK_FACT,
|
|
125
|
+
{"key": f"{deployment_id}:obs:{subject_entity_id}:{statement}"},
|
|
126
|
+
)
|
|
127
|
+
existing = connection.execute(
|
|
128
|
+
_SELECT_OBSERVATION,
|
|
129
|
+
{
|
|
130
|
+
"deployment_id": deployment_id,
|
|
131
|
+
"subject_entity_id": subject_entity_id,
|
|
132
|
+
"statement": statement,
|
|
133
|
+
},
|
|
134
|
+
).scalar_one_or_none()
|
|
135
|
+
observation_id = existing if existing is not None else uuid4()
|
|
136
|
+
if existing is None:
|
|
137
|
+
connection.execute(
|
|
138
|
+
_INSERT_OBSERVATION,
|
|
139
|
+
{
|
|
140
|
+
"observation_id": observation_id,
|
|
141
|
+
"deployment_id": deployment_id,
|
|
142
|
+
"subject_entity_id": subject_entity_id,
|
|
143
|
+
"statement": statement,
|
|
144
|
+
"normalizer_version": normalizer_version,
|
|
145
|
+
},
|
|
146
|
+
)
|
|
147
|
+
connection.execute(
|
|
148
|
+
_INSERT_OBS_ADJUDICATION,
|
|
149
|
+
{
|
|
150
|
+
"adjudication_id": uuid4(),
|
|
151
|
+
"deployment_id": deployment_id,
|
|
152
|
+
"observation_id": observation_id,
|
|
153
|
+
"triggering_claim_id": claim_id,
|
|
154
|
+
"features": {"statement": statement},
|
|
155
|
+
"adjudicator_version": normalizer_version,
|
|
156
|
+
},
|
|
157
|
+
)
|
|
158
|
+
connection.execute(
|
|
159
|
+
_INSERT_OBS_EVIDENCE,
|
|
160
|
+
{
|
|
161
|
+
"deployment_id": deployment_id,
|
|
162
|
+
"observation_id": observation_id,
|
|
163
|
+
"claim_id": claim_id,
|
|
164
|
+
"doc_id": doc_id,
|
|
165
|
+
"normalizer_version": normalizer_version,
|
|
166
|
+
},
|
|
167
|
+
)
|
|
168
|
+
connection.execute(_RECOUNT_OBSERVATION, {"observation_id": observation_id})
|
|
169
|
+
return observation_id
|
|
170
|
+
|
|
171
|
+
@contextmanager
|
|
172
|
+
def label_lock(self, *, deployment_id: UUID) -> Iterator[None]:
|
|
173
|
+
"""Serialize concurrent label sweeps for one deployment.
|
|
174
|
+
|
|
175
|
+
A session-scoped advisory lock on a dedicated connection, held for
|
|
176
|
+
the whole label+embed pass (which spans several transactions) — two
|
|
177
|
+
document jobs can never interleave labels and vectors on one fact.
|
|
178
|
+
"""
|
|
179
|
+
with self._engine.connect() as connection:
|
|
180
|
+
connection.execute(
|
|
181
|
+
_ACQUIRE_LABEL_LOCK, {"key": f"{deployment_id}:label-facts"}
|
|
182
|
+
)
|
|
183
|
+
connection.commit()
|
|
184
|
+
try:
|
|
185
|
+
yield
|
|
186
|
+
finally:
|
|
187
|
+
connection.execute(
|
|
188
|
+
_RELEASE_LABEL_LOCK, {"key": f"{deployment_id}:label-facts"}
|
|
189
|
+
)
|
|
190
|
+
connection.commit()
|
|
191
|
+
|
|
192
|
+
def relations_for_labeling(
|
|
193
|
+
self, *, deployment_id: UUID, doc_id: UUID, label_version: str
|
|
194
|
+
) -> tuple[FactForLabeling, ...]:
|
|
195
|
+
"""The document's relations still lacking this label generation.
|
|
196
|
+
|
|
197
|
+
Scoped by evidence doc_id so a document job's work is proportional to
|
|
198
|
+
the document, not the deployment.
|
|
199
|
+
"""
|
|
200
|
+
with self._engine.connect() as connection:
|
|
201
|
+
rows = (
|
|
202
|
+
connection.execute(
|
|
203
|
+
_SELECT_RELATIONS_FOR_LABELING,
|
|
204
|
+
{
|
|
205
|
+
"deployment_id": deployment_id,
|
|
206
|
+
"doc_id": doc_id,
|
|
207
|
+
"label_version": label_version,
|
|
208
|
+
},
|
|
209
|
+
)
|
|
210
|
+
.mappings()
|
|
211
|
+
.all()
|
|
212
|
+
)
|
|
213
|
+
return tuple(FactForLabeling.model_validate(dict(row)) for row in rows)
|
|
214
|
+
|
|
215
|
+
def record_fact_label(
|
|
216
|
+
self, *, relation_id: UUID, label: str, label_version: str
|
|
217
|
+
) -> None:
|
|
218
|
+
"""Stamp one relation's readable label, ref, and generation (D8)."""
|
|
219
|
+
with self._engine.begin() as connection:
|
|
220
|
+
connection.execute(
|
|
221
|
+
_STAMP_FACT_LABEL,
|
|
222
|
+
{
|
|
223
|
+
"relation_id": relation_id,
|
|
224
|
+
"label": label,
|
|
225
|
+
"label_version": label_version,
|
|
226
|
+
},
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
def observations_for_embedding(
|
|
230
|
+
self, *, deployment_id: UUID, doc_id: UUID, label_version: str
|
|
231
|
+
) -> tuple[ObservationForEmbedding, ...]:
|
|
232
|
+
"""The document's observations still lacking this label generation."""
|
|
233
|
+
with self._engine.connect() as connection:
|
|
234
|
+
rows = (
|
|
235
|
+
connection.execute(
|
|
236
|
+
_SELECT_OBSERVATIONS_FOR_EMBEDDING,
|
|
237
|
+
{
|
|
238
|
+
"deployment_id": deployment_id,
|
|
239
|
+
"doc_id": doc_id,
|
|
240
|
+
"label_version": label_version,
|
|
241
|
+
},
|
|
242
|
+
)
|
|
243
|
+
.mappings()
|
|
244
|
+
.all()
|
|
245
|
+
)
|
|
246
|
+
return tuple(ObservationForEmbedding.model_validate(dict(row)) for row in rows)
|
|
247
|
+
|
|
248
|
+
def record_observation_embedding(
|
|
249
|
+
self, *, observation_id: UUID, label_version: str
|
|
250
|
+
) -> None:
|
|
251
|
+
"""Stamp one observation's label ref and generation (D8)."""
|
|
252
|
+
with self._engine.begin() as connection:
|
|
253
|
+
connection.execute(
|
|
254
|
+
_STAMP_OBSERVATION_EMBEDDING,
|
|
255
|
+
{"observation_id": observation_id, "label_version": label_version},
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
def ensure_other_predicate(self, *, deployment_id: UUID, predicate: str) -> None:
|
|
259
|
+
"""Register one `other:<freetext>` escape value (tier=other, D5/D18).
|
|
260
|
+
|
|
261
|
+
The grammar is enforced HERE, at the spine authority (Codex review) —
|
|
262
|
+
callers' routing regexes are conveniences, not the gate. The FK holds
|
|
263
|
+
(the row exists before any relation uses it); the permissive core
|
|
264
|
+
parent `related_to` anchors it; usage_count ranks it for the periodic
|
|
265
|
+
promotion review (registries §7).
|
|
266
|
+
"""
|
|
267
|
+
if not OTHER_PREDICATE_GRAMMAR.fullmatch(predicate):
|
|
268
|
+
raise OtherPredicateGrammarError(
|
|
269
|
+
f"{predicate!r} is not a valid other:<short_snake_case> value"
|
|
270
|
+
)
|
|
271
|
+
with self._engine.begin() as connection:
|
|
272
|
+
connection.execute(
|
|
273
|
+
_INSERT_OTHER_PREDICATE,
|
|
274
|
+
{"deployment_id": deployment_id, "predicate": predicate},
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
def promotion_candidates(
|
|
278
|
+
self, *, deployment_id: UUID, limit: int = 20
|
|
279
|
+
) -> tuple[tuple[str, int], ...]:
|
|
280
|
+
"""The D5 funnel surface: tier=other predicates ranked by usage."""
|
|
281
|
+
with self._engine.connect() as connection:
|
|
282
|
+
rows = connection.execute(
|
|
283
|
+
_SELECT_PROMOTION_CANDIDATES,
|
|
284
|
+
{"deployment_id": deployment_id, "limit": limit},
|
|
285
|
+
).all()
|
|
286
|
+
return tuple((predicate, usage) for predicate, usage in rows)
|
|
287
|
+
|
|
288
|
+
def predicate_prompt_lines(self, *, deployment_id: UUID) -> str:
|
|
289
|
+
"""The governed vocabulary rendered for prompts (registries §4):
|
|
290
|
+
one line per active non-other predicate with meaning and synonyms."""
|
|
291
|
+
with self._engine.connect() as connection:
|
|
292
|
+
rows = connection.execute(
|
|
293
|
+
_SELECT_PREDICATE_PROMPT, {"deployment_id": deployment_id}
|
|
294
|
+
).all()
|
|
295
|
+
lines = []
|
|
296
|
+
for predicate, description, synonyms in rows:
|
|
297
|
+
line = f"- {predicate}: {description}"
|
|
298
|
+
if synonyms:
|
|
299
|
+
line += f" (synonyms: {', '.join(synonyms)})"
|
|
300
|
+
lines.append(line)
|
|
301
|
+
return "\n".join(lines)
|
|
302
|
+
|
|
303
|
+
def active_predicates(self, *, deployment_id: UUID) -> dict[str, str | None]:
|
|
304
|
+
"""The governed vocabulary: active predicate → its parent (D5/D18)."""
|
|
305
|
+
with self._engine.connect() as connection:
|
|
306
|
+
rows = connection.execute(
|
|
307
|
+
_SELECT_PREDICATES, {"deployment_id": deployment_id}
|
|
308
|
+
).all()
|
|
309
|
+
return {predicate: parent for predicate, parent in rows}
|
|
310
|
+
|
|
311
|
+
def predicate_signatures(
|
|
312
|
+
self, *, deployment_id: UUID
|
|
313
|
+
) -> dict[str, tuple[tuple[str, str], ...]]:
|
|
314
|
+
"""Allowed (subject_type, object_type) pairs per predicate (D18)."""
|
|
315
|
+
with self._engine.connect() as connection:
|
|
316
|
+
rows = connection.execute(
|
|
317
|
+
_SELECT_SIGNATURES, {"deployment_id": deployment_id}
|
|
318
|
+
).all()
|
|
319
|
+
signatures: dict[str, list[tuple[str, str]]] = {}
|
|
320
|
+
for predicate, subject_type, object_type in rows:
|
|
321
|
+
signatures.setdefault(predicate, []).append((subject_type, object_type))
|
|
322
|
+
return {predicate: tuple(pairs) for predicate, pairs in signatures.items()}
|
|
323
|
+
|
|
324
|
+
def entity_type_parents(self, *, deployment_id: UUID) -> dict[str, str | None]:
|
|
325
|
+
"""The registry type hierarchy: type → parent (extend-never-fork)."""
|
|
326
|
+
with self._engine.connect() as connection:
|
|
327
|
+
rows = connection.execute(
|
|
328
|
+
_SELECT_TYPE_PARENTS, {"deployment_id": deployment_id}
|
|
329
|
+
).all()
|
|
330
|
+
return {entity_type: parent for entity_type, parent in rows}
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
_LOCK_FACT = text("SELECT pg_advisory_xact_lock(hashtextextended(:key, 0))")
|
|
334
|
+
|
|
335
|
+
_SELECT_RELATION = text(
|
|
336
|
+
"""
|
|
337
|
+
SELECT relation_id FROM relations
|
|
338
|
+
WHERE deployment_id = :deployment_id
|
|
339
|
+
AND subject_entity_id = :subject_entity_id
|
|
340
|
+
AND predicate = :predicate
|
|
341
|
+
AND object_entity_id = :object_entity_id
|
|
342
|
+
AND invalidated_at IS NULL
|
|
343
|
+
AND (valid_until IS NULL OR valid_until > now())
|
|
344
|
+
"""
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
_INSERT_RELATION = text(
|
|
348
|
+
"""
|
|
349
|
+
INSERT INTO relations (
|
|
350
|
+
relation_id, deployment_id, subject_entity_id, predicate,
|
|
351
|
+
object_entity_id, valid_from, normalizer_version
|
|
352
|
+
) VALUES (
|
|
353
|
+
:relation_id, :deployment_id, :subject_entity_id, :predicate,
|
|
354
|
+
:object_entity_id, :valid_from, :normalizer_version
|
|
355
|
+
)
|
|
356
|
+
"""
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
_INSERT_RELATION_EVIDENCE = text(
|
|
360
|
+
"""
|
|
361
|
+
INSERT INTO relation_evidence (
|
|
362
|
+
deployment_id, relation_id, claim_id, doc_id, stance, normalizer_version
|
|
363
|
+
) VALUES (
|
|
364
|
+
:deployment_id, :relation_id, :claim_id, :doc_id, 'supports',
|
|
365
|
+
:normalizer_version
|
|
366
|
+
)
|
|
367
|
+
ON CONFLICT (relation_id, claim_id) DO NOTHING
|
|
368
|
+
"""
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
_RECOUNT_RELATION = text(
|
|
372
|
+
"""
|
|
373
|
+
UPDATE relations SET evidence_count = (
|
|
374
|
+
SELECT count(DISTINCT evidence.doc_id)
|
|
375
|
+
FROM relation_evidence evidence
|
|
376
|
+
JOIN claims ON claims.claim_id = evidence.claim_id
|
|
377
|
+
WHERE evidence.relation_id = :relation_id
|
|
378
|
+
AND evidence.stance = 'supports'
|
|
379
|
+
AND claims.is_current_testimony
|
|
380
|
+
), updated_at = now()
|
|
381
|
+
WHERE relation_id = :relation_id
|
|
382
|
+
"""
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
_SELECT_OBSERVATION = text(
|
|
386
|
+
"""
|
|
387
|
+
SELECT observation_id FROM observations
|
|
388
|
+
WHERE deployment_id = :deployment_id
|
|
389
|
+
AND subject_entity_id = :subject_entity_id
|
|
390
|
+
AND statement = :statement
|
|
391
|
+
AND invalidated_at IS NULL
|
|
392
|
+
"""
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
_INSERT_OBSERVATION = text(
|
|
396
|
+
"""
|
|
397
|
+
INSERT INTO observations (
|
|
398
|
+
observation_id, deployment_id, subject_entity_id, statement,
|
|
399
|
+
obs_label, normalizer_version
|
|
400
|
+
) VALUES (
|
|
401
|
+
:observation_id, :deployment_id, :subject_entity_id, :statement,
|
|
402
|
+
:statement, :normalizer_version
|
|
403
|
+
)
|
|
404
|
+
"""
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
_INSERT_OBS_ADJUDICATION = text(
|
|
408
|
+
"""
|
|
409
|
+
INSERT INTO observation_adjudications (
|
|
410
|
+
adjudication_id, deployment_id, observation_id, outcome, method,
|
|
411
|
+
confidence, triggering_claim_id, features, adjudicator_version
|
|
412
|
+
) VALUES (
|
|
413
|
+
:adjudication_id, :deployment_id, :observation_id, 'add', 'novelty_gate',
|
|
414
|
+
1.0, :triggering_claim_id, :features, :adjudicator_version
|
|
415
|
+
)
|
|
416
|
+
"""
|
|
417
|
+
).bindparams(bindparam("features", type_=JSON))
|
|
418
|
+
|
|
419
|
+
_INSERT_OBS_EVIDENCE = text(
|
|
420
|
+
"""
|
|
421
|
+
INSERT INTO observation_evidence (
|
|
422
|
+
deployment_id, observation_id, claim_id, doc_id, stance, normalizer_version
|
|
423
|
+
) VALUES (
|
|
424
|
+
:deployment_id, :observation_id, :claim_id, :doc_id, 'supports',
|
|
425
|
+
:normalizer_version
|
|
426
|
+
)
|
|
427
|
+
ON CONFLICT (observation_id, claim_id) DO NOTHING
|
|
428
|
+
"""
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
_RECOUNT_OBSERVATION = text(
|
|
432
|
+
"""
|
|
433
|
+
UPDATE observations SET evidence_count = (
|
|
434
|
+
SELECT count(DISTINCT evidence.doc_id)
|
|
435
|
+
FROM observation_evidence evidence
|
|
436
|
+
JOIN claims ON claims.claim_id = evidence.claim_id
|
|
437
|
+
WHERE evidence.observation_id = :observation_id
|
|
438
|
+
AND evidence.stance = 'supports'
|
|
439
|
+
AND claims.is_current_testimony
|
|
440
|
+
), updated_at = now()
|
|
441
|
+
WHERE observation_id = :observation_id
|
|
442
|
+
"""
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
_SELECT_PREDICATES = text(
|
|
446
|
+
"""
|
|
447
|
+
SELECT predicate, parent_predicate FROM predicates
|
|
448
|
+
WHERE deployment_id = :deployment_id AND status = 'active'
|
|
449
|
+
"""
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
_SELECT_SIGNATURES = text(
|
|
453
|
+
"""
|
|
454
|
+
SELECT predicate, subject_type, object_type FROM predicate_signatures
|
|
455
|
+
WHERE deployment_id = :deployment_id
|
|
456
|
+
"""
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
_SELECT_TYPE_PARENTS = text(
|
|
460
|
+
"""
|
|
461
|
+
SELECT type, parent_type FROM entity_types
|
|
462
|
+
WHERE deployment_id = :deployment_id
|
|
463
|
+
"""
|
|
464
|
+
)
|
|
465
|
+
|
|
466
|
+
_SELECT_RELATIONS_FOR_LABELING = text(
|
|
467
|
+
"""
|
|
468
|
+
SELECT r.relation_id, subject.canonical_name AS subject_name, r.predicate,
|
|
469
|
+
object.canonical_name AS object_name, r.status::text AS status
|
|
470
|
+
FROM relations r
|
|
471
|
+
JOIN entities subject ON subject.entity_id = r.subject_entity_id
|
|
472
|
+
JOIN entities object ON object.entity_id = r.object_entity_id
|
|
473
|
+
WHERE r.deployment_id = :deployment_id
|
|
474
|
+
AND (r.fact_label_version IS NULL OR r.fact_label_version <> :label_version)
|
|
475
|
+
AND EXISTS (
|
|
476
|
+
SELECT 1 FROM relation_evidence e
|
|
477
|
+
WHERE e.relation_id = r.relation_id AND e.doc_id = :doc_id
|
|
478
|
+
)
|
|
479
|
+
ORDER BY r.created_at, r.relation_id
|
|
480
|
+
"""
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
_ACQUIRE_LABEL_LOCK = text("SELECT pg_advisory_lock(hashtextextended(:key, 0))")
|
|
484
|
+
_RELEASE_LABEL_LOCK = text("SELECT pg_advisory_unlock(hashtextextended(:key, 0))")
|
|
485
|
+
|
|
486
|
+
_STAMP_FACT_LABEL = text(
|
|
487
|
+
"""
|
|
488
|
+
UPDATE relations
|
|
489
|
+
SET fact_label = :label,
|
|
490
|
+
fact_label_version = :label_version,
|
|
491
|
+
fact_label_embedding_ref = relation_id::text,
|
|
492
|
+
updated_at = now()
|
|
493
|
+
WHERE relation_id = :relation_id
|
|
494
|
+
"""
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
_SELECT_OBSERVATIONS_FOR_EMBEDDING = text(
|
|
498
|
+
"""
|
|
499
|
+
SELECT observation_id, obs_label, status::text AS status
|
|
500
|
+
FROM observations
|
|
501
|
+
WHERE observations.deployment_id = :deployment_id
|
|
502
|
+
AND (obs_label_version IS NULL OR obs_label_version <> :label_version)
|
|
503
|
+
AND EXISTS (
|
|
504
|
+
SELECT 1 FROM observation_evidence e
|
|
505
|
+
WHERE e.observation_id = observations.observation_id
|
|
506
|
+
AND e.doc_id = :doc_id
|
|
507
|
+
)
|
|
508
|
+
ORDER BY created_at, observation_id
|
|
509
|
+
"""
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
_STAMP_OBSERVATION_EMBEDDING = text(
|
|
513
|
+
"""
|
|
514
|
+
UPDATE observations
|
|
515
|
+
SET obs_label_version = :label_version,
|
|
516
|
+
obs_label_embedding_ref = observation_id::text,
|
|
517
|
+
updated_at = now()
|
|
518
|
+
WHERE observation_id = :observation_id
|
|
519
|
+
"""
|
|
520
|
+
)
|
|
521
|
+
|
|
522
|
+
_BUMP_PREDICATE_USAGE = text(
|
|
523
|
+
"""
|
|
524
|
+
UPDATE predicates SET usage_count = usage_count + 1
|
|
525
|
+
WHERE deployment_id = :deployment_id AND predicate = :predicate
|
|
526
|
+
"""
|
|
527
|
+
)
|
|
528
|
+
|
|
529
|
+
_INSERT_OTHER_PREDICATE = text(
|
|
530
|
+
"""
|
|
531
|
+
INSERT INTO predicates (
|
|
532
|
+
deployment_id, predicate, parent_predicate, description, tier
|
|
533
|
+
) VALUES (
|
|
534
|
+
:deployment_id, :predicate, 'related_to',
|
|
535
|
+
'normalizer-emitted other: escape value (D5 funnel; promote on demand)',
|
|
536
|
+
'other'
|
|
537
|
+
)
|
|
538
|
+
ON CONFLICT (deployment_id, predicate) DO NOTHING
|
|
539
|
+
"""
|
|
540
|
+
)
|
|
541
|
+
|
|
542
|
+
_SELECT_PROMOTION_CANDIDATES = text(
|
|
543
|
+
"""
|
|
544
|
+
SELECT predicate, usage_count FROM predicates
|
|
545
|
+
WHERE deployment_id = :deployment_id AND tier = 'other'
|
|
546
|
+
AND status = 'active'
|
|
547
|
+
ORDER BY usage_count DESC, predicate
|
|
548
|
+
LIMIT :limit
|
|
549
|
+
"""
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
_SELECT_PREDICATE_PROMPT = text(
|
|
553
|
+
"""
|
|
554
|
+
SELECT predicate, description, synonyms FROM predicates
|
|
555
|
+
WHERE deployment_id = :deployment_id AND status = 'active'
|
|
556
|
+
AND tier <> 'other'
|
|
557
|
+
ORDER BY predicate
|
|
558
|
+
"""
|
|
559
|
+
)
|
|
560
|
+
|
|
561
|
+
_LATEST_CLOSED_UNTIL = text(
|
|
562
|
+
"""
|
|
563
|
+
SELECT max(valid_until) FROM relations
|
|
564
|
+
WHERE deployment_id = :deployment_id
|
|
565
|
+
AND subject_entity_id = :subject_entity_id
|
|
566
|
+
AND predicate = :predicate
|
|
567
|
+
AND object_entity_id = :object_entity_id
|
|
568
|
+
AND invalidated_at IS NULL
|
|
569
|
+
AND valid_until IS NOT NULL
|
|
570
|
+
"""
|
|
571
|
+
)
|