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.
Files changed (186) hide show
  1. rememberstack/__init__.py +9 -0
  2. rememberstack/adapters/__init__.py +42 -0
  3. rememberstack/adapters/codex_writer.py +221 -0
  4. rememberstack/adapters/markitdown_converter.py +42 -0
  5. rememberstack/adapters/openrouter.py +136 -0
  6. rememberstack/adapters/selfhost/__init__.py +54 -0
  7. rememberstack/adapters/selfhost/forget.py +66 -0
  8. rememberstack/adapters/selfhost/git.py +374 -0
  9. rememberstack/adapters/selfhost/lance.py +328 -0
  10. rememberstack/adapters/selfhost/minio.py +279 -0
  11. rememberstack/adapters/selfhost/mounts.py +249 -0
  12. rememberstack/adapters/selfhost/object_store.py +130 -0
  13. rememberstack/adapters/selfhost/projection.py +80 -0
  14. rememberstack/adapters/selfhost/queue.py +137 -0
  15. rememberstack/adapters/selfhost/telemetry.py +45 -0
  16. rememberstack/adapters/selfhost/watcher.py +70 -0
  17. rememberstack/adapters/testing/__init__.py +15 -0
  18. rememberstack/adapters/testing/cost_meter.py +13 -0
  19. rememberstack/adapters/testing/model_provider.py +83 -0
  20. rememberstack/adapters/testing/queue.py +43 -0
  21. rememberstack/adapters/testing/telemetry.py +22 -0
  22. rememberstack/client.py +19 -0
  23. rememberstack/core/__init__.py +127 -0
  24. rememberstack/core/blockizer.py +189 -0
  25. rememberstack/core/chunker.py +216 -0
  26. rememberstack/core/consumption_skill.py +275 -0
  27. rememberstack/core/conversion.py +76 -0
  28. rememberstack/core/core_manifest.py +598 -0
  29. rememberstack/core/extension_packs.py +124 -0
  30. rememberstack/core/forget.py +17 -0
  31. rememberstack/core/knowledge_authored.py +276 -0
  32. rememberstack/core/knowledge_compile.py +215 -0
  33. rememberstack/core/knowledge_fact_sheet.py +210 -0
  34. rememberstack/core/knowledge_hashing.py +68 -0
  35. rememberstack/core/knowledge_planner.py +64 -0
  36. rememberstack/core/knowledge_writer.py +175 -0
  37. rememberstack/core/ranking.py +200 -0
  38. rememberstack/core/recipe_linter.py +149 -0
  39. rememberstack/core/section_snap.py +209 -0
  40. rememberstack/core/storage_routing.py +27 -0
  41. rememberstack/eval/__init__.py +53 -0
  42. rememberstack/eval/consumption.py +141 -0
  43. rememberstack/eval/contradiction.py +184 -0
  44. rememberstack/eval/harness.py +136 -0
  45. rememberstack/eval/lifecycle.py +400 -0
  46. rememberstack/eval/operational_scale.py +49 -0
  47. rememberstack/eval/resolution.py +255 -0
  48. rememberstack/eval/retrieval_spikes.py +50 -0
  49. rememberstack/eval/skeleton.py +231 -0
  50. rememberstack/llm/__init__.py +1 -0
  51. rememberstack/model/__init__.py +589 -0
  52. rememberstack/model/adjudication.py +100 -0
  53. rememberstack/model/auth.py +27 -0
  54. rememberstack/model/blocks.py +30 -0
  55. rememberstack/model/chunks.py +190 -0
  56. rememberstack/model/claims.py +162 -0
  57. rememberstack/model/client.py +98 -0
  58. rememberstack/model/clustering.py +54 -0
  59. rememberstack/model/component_version.py +124 -0
  60. rememberstack/model/consumption.py +88 -0
  61. rememberstack/model/conversion.py +31 -0
  62. rememberstack/model/deployment.py +53 -0
  63. rememberstack/model/documents.py +168 -0
  64. rememberstack/model/envelope.py +513 -0
  65. rememberstack/model/evaluation.py +72 -0
  66. rememberstack/model/forget.py +143 -0
  67. rememberstack/model/git.py +13 -0
  68. rememberstack/model/knowledge.py +840 -0
  69. rememberstack/model/knowledge_authored.py +325 -0
  70. rememberstack/model/knowledge_planner.py +431 -0
  71. rememberstack/model/lifecycle.py +42 -0
  72. rememberstack/model/model_provider.py +78 -0
  73. rememberstack/model/mounts.py +24 -0
  74. rememberstack/model/object_store.py +21 -0
  75. rememberstack/model/operational_scale.py +59 -0
  76. rememberstack/model/operations.py +153 -0
  77. rememberstack/model/processing.py +228 -0
  78. rememberstack/model/queue.py +73 -0
  79. rememberstack/model/recipes.py +83 -0
  80. rememberstack/model/relations.py +79 -0
  81. rememberstack/model/resolution.py +83 -0
  82. rememberstack/model/retrieval_spikes.py +62 -0
  83. rememberstack/model/sections.py +120 -0
  84. rememberstack/model/telemetry.py +30 -0
  85. rememberstack/ports/__init__.py +29 -0
  86. rememberstack/ports/auth.py +16 -0
  87. rememberstack/ports/connector.py +23 -0
  88. rememberstack/ports/cost_meter.py +17 -0
  89. rememberstack/ports/forget.py +20 -0
  90. rememberstack/ports/git.py +20 -0
  91. rememberstack/ports/model_provider.py +28 -0
  92. rememberstack/ports/mounts.py +16 -0
  93. rememberstack/ports/object_store.py +27 -0
  94. rememberstack/ports/p1_index.py +92 -0
  95. rememberstack/ports/purge.py +93 -0
  96. rememberstack/ports/queue.py +23 -0
  97. rememberstack/ports/telemetry.py +21 -0
  98. rememberstack/profiles/__init__.py +22 -0
  99. rememberstack/profiles/selfhost.py +324 -0
  100. rememberstack/profiles/selfhost_forget.py +158 -0
  101. rememberstack/profiles/selfhost_operations.py +95 -0
  102. rememberstack/py.typed +1 -0
  103. rememberstack/spine/__init__.py +93 -0
  104. rememberstack/spine/admission.py +26 -0
  105. rememberstack/spine/backfill.py +168 -0
  106. rememberstack/spine/catalog_contract.py +742 -0
  107. rememberstack/spine/chunk_catalog.py +237 -0
  108. rememberstack/spine/claim_catalog.py +298 -0
  109. rememberstack/spine/clustering.py +740 -0
  110. rememberstack/spine/component_versions.py +208 -0
  111. rememberstack/spine/consumption.py +81 -0
  112. rememberstack/spine/deployment_bootstrap.py +445 -0
  113. rememberstack/spine/document_catalog.py +621 -0
  114. rememberstack/spine/entity_registry.py +205 -0
  115. rememberstack/spine/extension_packs.py +220 -0
  116. rememberstack/spine/fact_catalog.py +571 -0
  117. rememberstack/spine/forget.py +1753 -0
  118. rememberstack/spine/knowledge.py +5467 -0
  119. rememberstack/spine/lifecycle.py +1071 -0
  120. rememberstack/spine/migrations/__init__.py +1 -0
  121. rememberstack/spine/migrations/_helpers.py +153 -0
  122. rememberstack/spine/migrations/env.py +58 -0
  123. rememberstack/spine/migrations/script.py.mako +27 -0
  124. rememberstack/spine/migrations/versions/__init__.py +1 -0
  125. rememberstack/spine/migrations/versions/p0_02_0001_extensions_enums.py +189 -0
  126. rememberstack/spine/migrations/versions/p0_02_0002_infrastructure_registries.py +321 -0
  127. rememberstack/spine/migrations/versions/p0_02_0003_entities_evaluation_e0_e1.py +631 -0
  128. rememberstack/spine/migrations/versions/p0_02_0004_claims_facts_evidence.py +411 -0
  129. rememberstack/spine/migrations/versions/p0_02_0005_projection_knowledge_retrieval.py +391 -0
  130. rememberstack/spine/migrations/versions/p0_02_0006_partitions_views.py +158 -0
  131. rememberstack/spine/migrations/versions/p2_06_0007_invalidated_outcome.py +26 -0
  132. rememberstack/spine/migrations/versions/p3_01_0008_document_version_target.py +58 -0
  133. rememberstack/spine/migrations/versions/p3_05_0009_reconcile_stage.py +27 -0
  134. rememberstack/spine/migrations/versions/p3_07_0010_lifecycle_eval_suite.py +25 -0
  135. rememberstack/spine/migrations/versions/p4_01_0011_survivor_view_rewrite.py +57 -0
  136. rememberstack/spine/migrations/versions/p6_02_0012_knowledge_compile_recovery.py +58 -0
  137. rememberstack/spine/migrations/versions/p6_04_0013_knowledge_writer_ledger.py +46 -0
  138. rememberstack/spine/migrations/versions/p6_05_0014_knowledge_planner_runtime.py +217 -0
  139. rememberstack/spine/migrations/versions/p6_06_0015_authored_dispatch_runtime.py +38 -0
  140. rememberstack/spine/migrations/versions/p7_02_0016_operational_eval_suite.py +19 -0
  141. rememberstack/spine/migrations/versions/p7_05_0017_hard_forget.py +55 -0
  142. rememberstack/spine/observation_adjudication.py +778 -0
  143. rememberstack/spine/operations.py +298 -0
  144. rememberstack/spine/projection.py +662 -0
  145. rememberstack/spine/recipes.py +276 -0
  146. rememberstack/spine/resolver.py +763 -0
  147. rememberstack/spine/review.py +650 -0
  148. rememberstack/spine/settings.py +22 -0
  149. rememberstack/spine/supersession.py +510 -0
  150. rememberstack/spine/sync.py +128 -0
  151. rememberstack/spine/work_ledger.py +816 -0
  152. rememberstack/surfaces/__init__.py +110 -0
  153. rememberstack/surfaces/cli.py +447 -0
  154. rememberstack/surfaces/consumption_skill.py +87 -0
  155. rememberstack/surfaces/graph_queries.py +698 -0
  156. rememberstack/surfaces/http_api.py +377 -0
  157. rememberstack/surfaces/mcp.py +67 -0
  158. rememberstack/surfaces/query_engine.py +1591 -0
  159. rememberstack/surfaces/recipe_executor.py +185 -0
  160. rememberstack/surfaces/recipe_surface.py +219 -0
  161. rememberstack/surfaces/remote_mcp.py +133 -0
  162. rememberstack/surfaces/sdk.py +324 -0
  163. rememberstack/workers/__init__.py +155 -0
  164. rememberstack/workers/base.py +312 -0
  165. rememberstack/workers/e0.py +577 -0
  166. rememberstack/workers/e1.py +425 -0
  167. rememberstack/workers/e2.py +525 -0
  168. rememberstack/workers/e3.py +434 -0
  169. rememberstack/workers/forget.py +299 -0
  170. rememberstack/workers/knowledge_authored.py +146 -0
  171. rememberstack/workers/knowledge_driver.py +735 -0
  172. rememberstack/workers/knowledge_fact_sheet.py +123 -0
  173. rememberstack/workers/knowledge_planner.py +325 -0
  174. rememberstack/workers/knowledge_writer.py +393 -0
  175. rememberstack/workers/operations.py +42 -0
  176. rememberstack/workers/p1.py +234 -0
  177. rememberstack/workers/p2.py +513 -0
  178. rememberstack/workers/p2_analytics.py +276 -0
  179. rememberstack/workers/p3.py +673 -0
  180. rememberstack/workers/reconcile.py +485 -0
  181. rememberstack/workers/sync.py +168 -0
  182. rememberstack-0.1.0.dist-info/METADATA +213 -0
  183. rememberstack-0.1.0.dist-info/RECORD +186 -0
  184. rememberstack-0.1.0.dist-info/WHEEL +4 -0
  185. rememberstack-0.1.0.dist-info/entry_points.txt +2 -0
  186. rememberstack-0.1.0.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,205 @@
1
+ """T0 entity resolution over the alias registry (D17): exact match or mint.
2
+
3
+ T0 is the cheapest cascade tier: an exact match on the normalized
4
+ `llm_canonical` lemma. A miss mints a new canonical entity (the novelty path)
5
+ with its alias, and every resolution — hit or mint — writes the immutable
6
+ mention and an append-only resolution verdict. Higher tiers (T1-T4 blocking
7
+ and adjudication) arrive with the truth-machinery phase.
8
+ """
9
+
10
+ from typing import Final
11
+ import unicodedata
12
+ from uuid import UUID
13
+ from uuid import uuid4
14
+
15
+ from sqlalchemy import bindparam
16
+ from sqlalchemy import JSON
17
+ from sqlalchemy import text
18
+ from sqlalchemy.engine import Engine
19
+
20
+ from rememberstack.model import ClaimForNormalization
21
+ from rememberstack.model import EntityRef
22
+ from rememberstack.model import ResolvedEntity
23
+
24
+ T0_RESOLVER_VERSION: Final = "e3-resolver-t0-2026.07"
25
+ """The T0-only resolver generation (walking skeleton; cascade tiers follow)."""
26
+
27
+
28
+ def normalized_lemma(*, surface: str) -> str:
29
+ """The registry match key: accent-stripped, lowercased, whitespace-folded."""
30
+ decomposed = unicodedata.normalize("NFKD", surface)
31
+ stripped = "".join(char for char in decomposed if not unicodedata.combining(char))
32
+ return " ".join(stripped.lower().split())
33
+
34
+
35
+ class EntityRegistry:
36
+ """Alias-registry resolution and mention/verdict writes for one deployment."""
37
+
38
+ def __init__(self, *, engine: Engine) -> None:
39
+ """Bind the registry to the spine database."""
40
+ self._engine = engine
41
+
42
+ def resolve_t0(
43
+ self, *, deployment_id: UUID, reference: EntityRef, claim: ClaimForNormalization
44
+ ) -> ResolvedEntity:
45
+ """Resolve one reference by exact lemma match, minting on a miss.
46
+
47
+ One transaction writes the mention, the entity + alias when minted,
48
+ and the append-only T0 verdict (D17: verdicts supersede, never edit).
49
+ """
50
+ lemma = normalized_lemma(surface=reference.name)
51
+ with self._engine.begin() as connection:
52
+ # serialize concurrent mints of one lemma (Codex review: without
53
+ # this, two workers each mint an entity and facts never collapse):
54
+ connection.execute(_LOCK_LEMMA, {"key": f"{deployment_id}:lemma:{lemma}"})
55
+ existing = (
56
+ connection.execute(
57
+ _SELECT_BY_LEMMA, {"deployment_id": deployment_id, "lemma": lemma}
58
+ )
59
+ .mappings()
60
+ .one_or_none()
61
+ )
62
+ created = existing is None
63
+ entity_id = existing["entity_id"] if existing else uuid4()
64
+ entity_type = existing["type"] if existing else reference.type
65
+ if created:
66
+ connection.execute(
67
+ _INSERT_ENTITY,
68
+ {
69
+ "entity_id": entity_id,
70
+ "deployment_id": deployment_id,
71
+ "type": reference.type,
72
+ "canonical_name": reference.name,
73
+ "normalized_name": lemma,
74
+ },
75
+ )
76
+ connection.execute(
77
+ _INSERT_ALIAS,
78
+ {
79
+ "alias_id": uuid4(),
80
+ "deployment_id": deployment_id,
81
+ "entity_id": entity_id,
82
+ "alias_text": reference.name,
83
+ "lemma": lemma,
84
+ },
85
+ )
86
+ mention_id = uuid4()
87
+ connection.execute(
88
+ _INSERT_MENTION,
89
+ {
90
+ "mention_id": mention_id,
91
+ "deployment_id": deployment_id,
92
+ "surface_form": reference.name,
93
+ "lemma": lemma,
94
+ "canonical_name_form": reference.name,
95
+ "emitted_type": reference.type,
96
+ "claim_id": claim.claim_id,
97
+ "chunk_id": claim.chunk_id,
98
+ "doc_id": claim.doc_id,
99
+ },
100
+ )
101
+ connection.execute(
102
+ _INSERT_DECISION,
103
+ {
104
+ "decision_id": uuid4(),
105
+ "deployment_id": deployment_id,
106
+ "mention_id": mention_id,
107
+ "entity_id": entity_id,
108
+ "is_new_entity": created,
109
+ "features": {"lemma": lemma},
110
+ "resolver_version": T0_RESOLVER_VERSION,
111
+ },
112
+ )
113
+ return ResolvedEntity(
114
+ entity_id=entity_id, created=created, entity_type=entity_type
115
+ )
116
+
117
+ def claim_already_normalized(self, *, claim_id: UUID) -> bool:
118
+ """Whether the claim's facts already landed (evidence-backed replay).
119
+
120
+ The marker is fact evidence, not mentions — a crash between subject
121
+ resolution and the fact write must not strand the claim (Codex
122
+ review). A claim that yielded no facts leaves no marker; its re-run on
123
+ a crash-retry is bounded by the attempt limit and lands idempotently
124
+ (mentions/verdicts are append-only transcript rows either way).
125
+ """
126
+ return claim_id in self.normalized_claim_ids(claim_ids=(claim_id,))
127
+
128
+ def normalized_claim_ids(self, *, claim_ids: tuple[UUID, ...]) -> frozenset[UUID]:
129
+ """Return every evidence-backed replay marker in one bounded read."""
130
+ if not claim_ids:
131
+ return frozenset()
132
+ with self._engine.connect() as connection:
133
+ rows = connection.execute(
134
+ _SELECT_NORMALIZED_CLAIMS, {"claim_ids": list(claim_ids)}
135
+ ).scalars()
136
+ return frozenset(rows)
137
+
138
+
139
+ _LOCK_LEMMA = text("SELECT pg_advisory_xact_lock(hashtextextended(:key, 0))")
140
+
141
+ _SELECT_BY_LEMMA = text(
142
+ """
143
+ SELECT aliases.entity_id, entities.type FROM aliases
144
+ JOIN entities ON entities.deployment_id = aliases.deployment_id
145
+ AND entities.entity_id = aliases.entity_id
146
+ WHERE aliases.deployment_id = :deployment_id
147
+ AND aliases.normalized_lemma = :lemma
148
+ AND entities.status = 'active' -- merged/retired never become endpoints;
149
+ -- redirect-following arrives with merges
150
+ ORDER BY aliases.first_seen
151
+ LIMIT 1
152
+ """
153
+ )
154
+
155
+ _INSERT_ENTITY = text(
156
+ """
157
+ INSERT INTO entities (
158
+ entity_id, deployment_id, type, canonical_name, normalized_name
159
+ ) VALUES (
160
+ :entity_id, :deployment_id, :type, :canonical_name, :normalized_name
161
+ )
162
+ """
163
+ )
164
+
165
+ _INSERT_ALIAS = text(
166
+ """
167
+ INSERT INTO aliases (
168
+ alias_id, deployment_id, entity_id, alias_text, normalized_lemma, provenance
169
+ ) VALUES (
170
+ :alias_id, :deployment_id, :entity_id, :alias_text, :lemma, 'llm_canonical'
171
+ )
172
+ """
173
+ )
174
+
175
+ _INSERT_MENTION = text(
176
+ """
177
+ INSERT INTO mentions (
178
+ mention_id, deployment_id, surface_form, normalized_lemma,
179
+ canonical_name_form, emitted_type, claim_id, chunk_id, doc_id
180
+ ) VALUES (
181
+ :mention_id, :deployment_id, :surface_form, :lemma,
182
+ :canonical_name_form, :emitted_type, :claim_id, :chunk_id, :doc_id
183
+ )
184
+ """
185
+ )
186
+
187
+ _INSERT_DECISION = text(
188
+ """
189
+ INSERT INTO resolution_decisions (
190
+ decision_id, deployment_id, mention_id, entity_id, method,
191
+ confidence, is_new_entity, features, resolver_version
192
+ ) VALUES (
193
+ :decision_id, :deployment_id, :mention_id, :entity_id, 'T0',
194
+ 1.0, :is_new_entity, :features, :resolver_version
195
+ )
196
+ """
197
+ ).bindparams(bindparam("features", type_=JSON))
198
+
199
+ _SELECT_NORMALIZED_CLAIMS = text(
200
+ """
201
+ SELECT claim_id FROM relation_evidence WHERE claim_id = ANY(:claim_ids)
202
+ UNION
203
+ SELECT claim_id FROM observation_evidence WHERE claim_id = ANY(:claim_ids)
204
+ """
205
+ )
@@ -0,0 +1,220 @@
1
+ """Extension-pack installation (registries §4/§7, D15): enable a pack as a unit.
2
+
3
+ Installing writes the pack's registry rows for one deployment — entity types,
4
+ predicates, and domain/range signatures, all `tier='extension'` with the pack
5
+ id — after verifying every anchor exists (extend-never-fork: an extension
6
+ type MUST declare a registered parent; a pack that forks is refused whole).
7
+ Idempotent: enabling twice is a no-op.
8
+ """
9
+
10
+ from uuid import UUID
11
+
12
+ from sqlalchemy import text
13
+ from sqlalchemy.engine import Connection
14
+ from sqlalchemy.engine import Engine
15
+
16
+ from rememberstack.core.extension_packs import ExtensionPack
17
+
18
+
19
+ class PackAnchorError(Exception):
20
+ """A pack type's parent is not registered — extend-never-fork refused it."""
21
+
22
+
23
+ class PackConflictError(Exception):
24
+ """A pack name collides with an existing, differently-defined registry row."""
25
+
26
+
27
+ def install_pack(*, engine: Engine, deployment_id: UUID, pack: ExtensionPack) -> None:
28
+ """Enable one pack for one deployment in a single transaction."""
29
+ with engine.begin() as connection:
30
+ _require_anchors(connection=connection, deployment_id=deployment_id, pack=pack)
31
+ _refuse_conflicts(connection=connection, deployment_id=deployment_id, pack=pack)
32
+ connection.execute(
33
+ _UPSERT_PACK,
34
+ {
35
+ "pack_id": pack.pack_id,
36
+ "name": pack.name,
37
+ "description": pack.description,
38
+ },
39
+ )
40
+ connection.execute(
41
+ _ENABLE_PACK, {"deployment_id": deployment_id, "pack_id": pack.pack_id}
42
+ )
43
+ for entity_type in pack.entity_types:
44
+ connection.execute(
45
+ _INSERT_TYPE,
46
+ {
47
+ "deployment_id": deployment_id,
48
+ "type": entity_type.type,
49
+ "parent_type": entity_type.parent_type,
50
+ "description": entity_type.description,
51
+ "examples": list(entity_type.examples),
52
+ "pack_id": pack.pack_id,
53
+ },
54
+ )
55
+ for predicate in pack.predicates:
56
+ connection.execute(
57
+ _INSERT_PREDICATE,
58
+ {
59
+ "deployment_id": deployment_id,
60
+ "predicate": predicate.predicate,
61
+ "description": predicate.description,
62
+ "synonyms": list(predicate.synonyms),
63
+ "is_change_prone": predicate.is_change_prone,
64
+ "pack_id": pack.pack_id,
65
+ },
66
+ )
67
+ for subject_type, object_type in predicate.signatures:
68
+ connection.execute(
69
+ _INSERT_SIGNATURE,
70
+ {
71
+ "deployment_id": deployment_id,
72
+ "predicate": predicate.predicate,
73
+ "subject_type": subject_type,
74
+ "object_type": object_type,
75
+ },
76
+ )
77
+
78
+
79
+ def _require_anchors(
80
+ *, connection: Connection, deployment_id: UUID, pack: ExtensionPack
81
+ ) -> None:
82
+ """Refuse the whole pack if any anchor is not ALREADY registered (D15).
83
+
84
+ Extend-never-fork means every pack type declares a parent that exists in
85
+ the deployment registry BEFORE the pack installs — a pack-local chain
86
+ (Child anchored to another type from the same pack) is refused too
87
+ (Codex review): the DDL's contract is a registered core-side parent.
88
+ Signatures may reference pack types (they install together).
89
+ """
90
+ pack_types = {entity_type.type for entity_type in pack.entity_types}
91
+ registered = {
92
+ row[0]
93
+ for row in connection.execute(_SELECT_TYPES, {"deployment_id": deployment_id})
94
+ }
95
+ for entity_type in pack.entity_types:
96
+ if entity_type.parent_type not in registered:
97
+ raise PackAnchorError(
98
+ f"pack {pack.pack_id!r} type {entity_type.type!r} anchors to "
99
+ f"{entity_type.parent_type!r}, which is not already registered "
100
+ "(extend-never-fork, D15: pack-local anchors are refused)"
101
+ )
102
+ signature_types = {
103
+ entity_type
104
+ for predicate in pack.predicates
105
+ for pair in predicate.signatures
106
+ for entity_type in pair
107
+ }
108
+ unknown = signature_types - (registered | pack_types)
109
+ if unknown:
110
+ raise PackAnchorError(
111
+ f"pack {pack.pack_id!r} signatures reference unregistered "
112
+ f"types {sorted(unknown)!r}"
113
+ )
114
+
115
+
116
+ def _refuse_conflicts(
117
+ *, connection: Connection, deployment_id: UUID, pack: ExtensionPack
118
+ ) -> None:
119
+ """Unit installation (Codex review): an existing row under a pack name
120
+ must BE this pack's row (same pack_id and parent) — anything else fails
121
+ the whole install rather than silently blending registries."""
122
+ type_rows = {
123
+ row[0]: (row[1], row[2])
124
+ for row in connection.execute(
125
+ _SELECT_TYPE_DEFS, {"deployment_id": deployment_id}
126
+ )
127
+ }
128
+ for entity_type in pack.entity_types:
129
+ existing = type_rows.get(entity_type.type)
130
+ if existing is not None and existing != (entity_type.parent_type, pack.pack_id):
131
+ raise PackConflictError(
132
+ f"type {entity_type.type!r} already registered with "
133
+ f"(parent, pack) = {existing!r}; pack {pack.pack_id!r} "
134
+ "cannot install over it"
135
+ )
136
+ predicate_rows = {
137
+ row[0]: row[1]
138
+ for row in connection.execute(
139
+ _SELECT_PREDICATE_DEFS, {"deployment_id": deployment_id}
140
+ )
141
+ }
142
+ for predicate in pack.predicates:
143
+ existing_pack = predicate_rows.get(predicate.predicate)
144
+ if existing_pack is not None and existing_pack != pack.pack_id:
145
+ raise PackConflictError(
146
+ f"predicate {predicate.predicate!r} already registered by "
147
+ f"pack {existing_pack!r}; pack {pack.pack_id!r} cannot "
148
+ "install over it"
149
+ )
150
+
151
+
152
+ _SELECT_TYPES = text(
153
+ "SELECT type FROM entity_types WHERE deployment_id = :deployment_id"
154
+ )
155
+
156
+ _UPSERT_PACK = text(
157
+ """
158
+ INSERT INTO extension_packs (pack_id, name, description)
159
+ VALUES (:pack_id, :name, :description)
160
+ ON CONFLICT (pack_id) DO NOTHING
161
+ """
162
+ )
163
+
164
+ _ENABLE_PACK = text(
165
+ """
166
+ INSERT INTO deployment_extension_packs (deployment_id, pack_id)
167
+ VALUES (:deployment_id, :pack_id)
168
+ ON CONFLICT (deployment_id, pack_id) DO NOTHING
169
+ """
170
+ )
171
+
172
+ _INSERT_TYPE = text(
173
+ """
174
+ INSERT INTO entity_types (
175
+ deployment_id, type, parent_type, description, examples, tier, pack_id
176
+ ) VALUES (
177
+ :deployment_id, :type, :parent_type, :description, :examples,
178
+ 'extension', :pack_id
179
+ )
180
+ ON CONFLICT (deployment_id, type) DO NOTHING
181
+ """
182
+ )
183
+
184
+ _INSERT_PREDICATE = text(
185
+ """
186
+ INSERT INTO predicates (
187
+ deployment_id, predicate, parent_predicate, description, synonyms,
188
+ is_change_prone, tier, pack_id
189
+ ) VALUES (
190
+ :deployment_id, :predicate, 'related_to', :description, :synonyms,
191
+ :is_change_prone, 'extension', :pack_id
192
+ )
193
+ ON CONFLICT (deployment_id, predicate) DO NOTHING
194
+ """
195
+ )
196
+
197
+ _INSERT_SIGNATURE = text(
198
+ """
199
+ INSERT INTO predicate_signatures (
200
+ deployment_id, predicate, subject_type, object_type
201
+ ) VALUES (
202
+ :deployment_id, :predicate, :subject_type, :object_type
203
+ )
204
+ ON CONFLICT (deployment_id, predicate, subject_type, object_type) DO NOTHING
205
+ """
206
+ )
207
+
208
+ _SELECT_TYPE_DEFS = text(
209
+ """
210
+ SELECT type, parent_type, pack_id FROM entity_types
211
+ WHERE deployment_id = :deployment_id
212
+ """
213
+ )
214
+
215
+ _SELECT_PREDICATE_DEFS = text(
216
+ """
217
+ SELECT predicate, pack_id FROM predicates
218
+ WHERE deployment_id = :deployment_id
219
+ """
220
+ )