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,840 @@
1
+ """Typed Plane-K control-plane values (D45)."""
2
+
3
+ from enum import StrEnum
4
+ from pathlib import PurePosixPath
5
+ from typing import Annotated
6
+ from typing import Literal
7
+ from typing import TypeAlias
8
+ from uuid import UUID
9
+
10
+ from pydantic import AfterValidator
11
+ from pydantic import BaseModel
12
+ from pydantic import ConfigDict
13
+ from pydantic import Field
14
+ from pydantic import field_validator
15
+ from pydantic import JsonValue
16
+ from pydantic import model_validator
17
+
18
+ from rememberstack.model.mounts import PublishedMounts
19
+ from rememberstack.model.queue import UTCDateTime
20
+
21
+ SHA256: TypeAlias = Annotated[str, Field(pattern=r"^[0-9a-f]{64}$")]
22
+ KNOWLEDGE_WRITER_OUTPUT_PATHS = (
23
+ "output/prose.md",
24
+ "output/citations.json",
25
+ "output/summary.md",
26
+ "output/suggestions.json",
27
+ )
28
+
29
+
30
+ def _sorted_unique_strings(values: tuple[str, ...]) -> tuple[str, ...]:
31
+ """Canonicalize a set-valued string parameter."""
32
+ return tuple(sorted(set(values)))
33
+
34
+
35
+ def _sorted_unique_uuids(values: tuple[UUID, ...]) -> tuple[UUID, ...]:
36
+ """Canonicalize a set-valued UUID parameter."""
37
+ return tuple(sorted(set(values), key=str))
38
+
39
+
40
+ def _sorted_unique_candidate_layers(
41
+ values: tuple["KnowledgeCandidateLayer", ...],
42
+ ) -> tuple["KnowledgeCandidateLayer", ...]:
43
+ """Canonicalize a set-valued candidate-layer parameter."""
44
+ return tuple(sorted(set(values), key=lambda value: value.value))
45
+
46
+
47
+ CanonicalStrings: TypeAlias = Annotated[
48
+ tuple[str, ...], AfterValidator(_sorted_unique_strings)
49
+ ]
50
+ CanonicalUUIDs: TypeAlias = Annotated[
51
+ tuple[UUID, ...], AfterValidator(_sorted_unique_uuids)
52
+ ]
53
+
54
+
55
+ class KnowledgeRuleKind(StrEnum):
56
+ """The closed, mechanically evaluated D45 routing-rule vocabulary."""
57
+
58
+ ENTITY = "entity"
59
+ ENTITY_SUBTREE = "entity_subtree"
60
+ PREDICATE_BEAT = "predicate_beat"
61
+ COMMUNITY = "community"
62
+ DOC_SET = "doc_set"
63
+ SCOPE_INTERESTS = "scope_interests"
64
+ MANUAL = "manual"
65
+
66
+
67
+ class KnowledgeRuleKeyKind(StrEnum):
68
+ """The four coarse keys supported by the routing inverted index."""
69
+
70
+ ENTITY = "entity"
71
+ PREDICATE = "predicate"
72
+ COMMUNITY = "community"
73
+ DOC_SOURCE = "doc_source"
74
+
75
+
76
+ class KnowledgeCandidateLayer(StrEnum):
77
+ """Candidate grains a rule may select for a compiled page."""
78
+
79
+ RELATIONS = "relations"
80
+ OBSERVATIONS = "observations"
81
+ CLAIMS = "claims"
82
+
83
+
84
+ CanonicalCandidateLayers: TypeAlias = Annotated[
85
+ tuple[KnowledgeCandidateLayer, ...], AfterValidator(_sorted_unique_candidate_layers)
86
+ ]
87
+
88
+
89
+ class KnowledgeLayer(StrEnum):
90
+ """The three content tiers sharing one compile mechanism (D47)."""
91
+
92
+ K1 = "K1"
93
+ K2 = "K2"
94
+ K3 = "K3"
95
+
96
+
97
+ class KnowledgePageKind(StrEnum):
98
+ """The D46 page ownership contract."""
99
+
100
+ COMPILED = "compiled"
101
+ AUTHORED = "authored"
102
+
103
+
104
+ class KnowledgeArtifactStatus(StrEnum):
105
+ """Lifecycle states of one Plane-K artifact handle."""
106
+
107
+ ACTIVE = "active"
108
+ STALE = "stale"
109
+ QUARANTINED = "quarantined"
110
+ TOMBSTONED = "tombstoned"
111
+
112
+
113
+ class KnowledgePlanAction(StrEnum):
114
+ """Append-only planner structure actions supported by the schema."""
115
+
116
+ CREATE_PAGE = "create_page"
117
+ SPLIT_PAGE = "split_page"
118
+ MERGE_PAGES = "merge_pages"
119
+ MOVE_PAGE = "move_page"
120
+ RETIRE_PAGE = "retire_page"
121
+ ADJUST_RULE = "adjust_rule"
122
+ CONVERT_KIND = "convert_kind"
123
+
124
+
125
+ class KnowledgePlanTrigger(StrEnum):
126
+ """Reasons a planner may make a durable structure decision."""
127
+
128
+ ORPHAN_EVIDENCE = "orphan_evidence"
129
+ SIZE_OVERFLOW = "size_overflow"
130
+ COMMUNITY_CHANGE = "community_change"
131
+ REFLECTION = "reflection"
132
+ WRITER_SUGGESTION = "writer_suggestion"
133
+ HUMAN = "human"
134
+
135
+
136
+ class KnowledgePlanStatus(StrEnum):
137
+ """Review state for a structure decision."""
138
+
139
+ PROPOSED = "proposed"
140
+ APPLIED = "applied"
141
+ REJECTED = "rejected"
142
+
143
+
144
+ class KnowledgeEvidenceRole(StrEnum):
145
+ """How a K citation uses one evidence target."""
146
+
147
+ SUPPORTS = "supports"
148
+ CONTRADICTS = "contradicts"
149
+ CITES = "cites"
150
+
151
+
152
+ class EntityRuleParams(BaseModel):
153
+ """Everything about one entity, optionally narrowed by fact layer/predicate."""
154
+
155
+ model_config = ConfigDict(frozen=True, extra="forbid")
156
+
157
+ kind: Literal[KnowledgeRuleKind.ENTITY] = KnowledgeRuleKind.ENTITY
158
+ entity_id: UUID
159
+ predicates: CanonicalStrings = ()
160
+ layers: CanonicalCandidateLayers = (
161
+ KnowledgeCandidateLayer.RELATIONS,
162
+ KnowledgeCandidateLayer.OBSERVATIONS,
163
+ KnowledgeCandidateLayer.CLAIMS,
164
+ )
165
+
166
+
167
+ class EntitySubtreeRuleParams(BaseModel):
168
+ """One entity plus the transitive subjects that are ``part_of`` it."""
169
+
170
+ model_config = ConfigDict(frozen=True, extra="forbid")
171
+
172
+ kind: Literal[KnowledgeRuleKind.ENTITY_SUBTREE] = KnowledgeRuleKind.ENTITY_SUBTREE
173
+ root_entity_id: UUID
174
+ predicates: CanonicalStrings = ()
175
+ layers: CanonicalCandidateLayers = (
176
+ KnowledgeCandidateLayer.RELATIONS,
177
+ KnowledgeCandidateLayer.OBSERVATIONS,
178
+ KnowledgeCandidateLayer.CLAIMS,
179
+ )
180
+
181
+
182
+ class PredicateBeatRuleParams(BaseModel):
183
+ """Relations of one governed predicate, optionally pinned to either endpoint."""
184
+
185
+ model_config = ConfigDict(frozen=True, extra="forbid")
186
+
187
+ kind: Literal[KnowledgeRuleKind.PREDICATE_BEAT] = KnowledgeRuleKind.PREDICATE_BEAT
188
+ predicate: str
189
+ subject_entity_id: UUID | None = None
190
+ object_entity_id: UUID | None = None
191
+
192
+
193
+ class CommunityRuleParams(BaseModel):
194
+ """Evidence about members of one detected entity community."""
195
+
196
+ model_config = ConfigDict(frozen=True, extra="forbid")
197
+
198
+ kind: Literal[KnowledgeRuleKind.COMMUNITY] = KnowledgeRuleKind.COMMUNITY
199
+ community_id: UUID
200
+ layers: CanonicalCandidateLayers = (
201
+ KnowledgeCandidateLayer.RELATIONS,
202
+ KnowledgeCandidateLayer.OBSERVATIONS,
203
+ KnowledgeCandidateLayer.CLAIMS,
204
+ )
205
+
206
+
207
+ class DocSetRuleParams(BaseModel):
208
+ """Evidence from one source family, with optional snapshot metadata filters.
209
+
210
+ ``source_kind`` is required because it is the only document key represented
211
+ by ``knowledge_rule_keys``. MIME, origin, and time are exact secondary SQL
212
+ filters after that coarse key hit.
213
+ """
214
+
215
+ model_config = ConfigDict(frozen=True, extra="forbid")
216
+
217
+ kind: Literal[KnowledgeRuleKind.DOC_SET] = KnowledgeRuleKind.DOC_SET
218
+ source_kind: str
219
+ mime: str | None = None
220
+ origin: Literal["external", "system_generated"] | None = None
221
+ source_modified_from: UTCDateTime | None = None
222
+ source_modified_until: UTCDateTime | None = None
223
+
224
+ @model_validator(mode="after")
225
+ def require_ordered_time_range(self) -> "DocSetRuleParams":
226
+ """Reject a document window whose upper bound precedes its lower bound."""
227
+ if (
228
+ self.source_modified_from is not None
229
+ and self.source_modified_until is not None
230
+ and self.source_modified_until < self.source_modified_from
231
+ ):
232
+ raise ValueError(
233
+ "source_modified_until must not precede source_modified_from"
234
+ )
235
+ return self
236
+
237
+
238
+ class ScopeInterestsRuleParams(BaseModel):
239
+ """Delegate selection to the existing registry interests of one scope."""
240
+
241
+ model_config = ConfigDict(frozen=True, extra="forbid")
242
+
243
+ kind: Literal[KnowledgeRuleKind.SCOPE_INTERESTS] = KnowledgeRuleKind.SCOPE_INTERESTS
244
+ scope_id: UUID
245
+
246
+
247
+ class ManualRuleParams(BaseModel):
248
+ """An explicit editorial assignment of entities or evidence IDs."""
249
+
250
+ model_config = ConfigDict(frozen=True, extra="forbid")
251
+
252
+ kind: Literal[KnowledgeRuleKind.MANUAL] = KnowledgeRuleKind.MANUAL
253
+ entity_ids: CanonicalUUIDs = ()
254
+ relation_ids: CanonicalUUIDs = ()
255
+ observation_ids: CanonicalUUIDs = ()
256
+ claim_ids: CanonicalUUIDs = ()
257
+ doc_ids: CanonicalUUIDs = ()
258
+
259
+ @model_validator(mode="after")
260
+ def require_a_target(self) -> "ManualRuleParams":
261
+ """Reject a manual rule that can never select anything."""
262
+ if not any(
263
+ (
264
+ self.entity_ids,
265
+ self.relation_ids,
266
+ self.observation_ids,
267
+ self.claim_ids,
268
+ self.doc_ids,
269
+ )
270
+ ):
271
+ raise ValueError("manual rule requires at least one target")
272
+ return self
273
+
274
+
275
+ KnowledgeRuleParams = Annotated[
276
+ EntityRuleParams
277
+ | EntitySubtreeRuleParams
278
+ | PredicateBeatRuleParams
279
+ | CommunityRuleParams
280
+ | DocSetRuleParams
281
+ | ScopeInterestsRuleParams
282
+ | ManualRuleParams,
283
+ Field(discriminator="kind"),
284
+ ]
285
+
286
+
287
+ class KnowledgePlanDecisionCreate(BaseModel):
288
+ """One planner structure decision to append to the durable transcript."""
289
+
290
+ model_config = ConfigDict(frozen=True, extra="forbid")
291
+
292
+ decision_id: UUID
293
+ deployment_id: UUID
294
+ scope_id: UUID | None = None
295
+ action: KnowledgePlanAction
296
+ payload: dict[str, JsonValue]
297
+ trigger: KnowledgePlanTrigger
298
+ planner_version: str
299
+ status: KnowledgePlanStatus = KnowledgePlanStatus.PROPOSED
300
+
301
+
302
+ class KnowledgeArtifactCreate(BaseModel):
303
+ """The Postgres handle for one Plane-K git path."""
304
+
305
+ model_config = ConfigDict(frozen=True, extra="forbid")
306
+
307
+ artifact_id: UUID
308
+ deployment_id: UUID
309
+ layer: KnowledgeLayer
310
+ page_kind: KnowledgePageKind
311
+ git_path: str
312
+ scope_id: UUID | None = None
313
+ parent_artifact_id: UUID | None = None
314
+ curation_path: str | None = None
315
+ artifact_kind: str | None = None
316
+ writer_version: str | None = None
317
+
318
+
319
+ class KnowledgePageRuleCreate(BaseModel):
320
+ """One page-owned routing rule and the plan decision that authorized it."""
321
+
322
+ model_config = ConfigDict(frozen=True, extra="forbid")
323
+
324
+ rule_id: UUID
325
+ deployment_id: UUID
326
+ artifact_id: UUID
327
+ plan_decision_id: UUID
328
+ params: KnowledgeRuleParams
329
+
330
+
331
+ class KnowledgeRuleKey(BaseModel):
332
+ """One materialized coarse key for routing changed evidence."""
333
+
334
+ model_config = ConfigDict(frozen=True, extra="forbid")
335
+
336
+ kind: KnowledgeRuleKeyKind
337
+ value: str
338
+
339
+
340
+ class KnowledgeCitation(BaseModel):
341
+ """One citation with exactly one schema-supported evidence target."""
342
+
343
+ model_config = ConfigDict(frozen=True, extra="forbid")
344
+
345
+ role: KnowledgeEvidenceRole
346
+ claim_lineage_id: UUID | None = None
347
+ claim_chunk_content_hash: str | None = None
348
+ relation_id: UUID | None = None
349
+ doc_id: UUID | None = None
350
+
351
+ @model_validator(mode="after")
352
+ def require_exactly_one_target(self) -> "KnowledgeCitation":
353
+ """Mirror the ``knowledge_artifact_evidence`` exactly-one CHECK."""
354
+ claim_coordinate_present = self.claim_lineage_id is not None
355
+ if claim_coordinate_present != (self.claim_chunk_content_hash is not None):
356
+ raise ValueError("claim citation requires its complete stable coordinate")
357
+ if (
358
+ sum(
359
+ target
360
+ for target in (
361
+ claim_coordinate_present,
362
+ self.relation_id is not None,
363
+ self.doc_id is not None,
364
+ )
365
+ )
366
+ != 1
367
+ ):
368
+ raise ValueError("citation requires exactly one evidence target")
369
+ return self
370
+
371
+
372
+ class KnowledgeEvidenceTarget(BaseModel):
373
+ """One role-independent claim, relation, or document exclusion target."""
374
+
375
+ model_config = ConfigDict(frozen=True, extra="forbid")
376
+
377
+ claim_lineage_id: UUID | None = None
378
+ claim_chunk_content_hash: str | None = None
379
+ relation_id: UUID | None = None
380
+ doc_id: UUID | None = None
381
+
382
+ @model_validator(mode="after")
383
+ def require_exactly_one_target(self) -> "KnowledgeEvidenceTarget":
384
+ """Keep exclusions at the same exactly-one evidence grain as citations."""
385
+ claim_coordinate_present = self.claim_lineage_id is not None
386
+ if claim_coordinate_present != (self.claim_chunk_content_hash is not None):
387
+ raise ValueError("claim exclusion requires its complete stable coordinate")
388
+ if (
389
+ sum(
390
+ target
391
+ for target in (
392
+ claim_coordinate_present,
393
+ self.relation_id is not None,
394
+ self.doc_id is not None,
395
+ )
396
+ )
397
+ != 1
398
+ ):
399
+ raise ValueError("evidence target requires exactly one target")
400
+ return self
401
+
402
+
403
+ class KnowledgeCompilationWrite(BaseModel):
404
+ """Validated compile metadata to commit atomically to the control plane."""
405
+
406
+ model_config = ConfigDict(frozen=True, extra="forbid")
407
+
408
+ compilation_id: UUID
409
+ deployment_id: UUID
410
+ artifact_id: UUID
411
+ inputs_hash: SHA256
412
+ candidate_count: int = Field(ge=0)
413
+ uncited_count: int = Field(ge=0)
414
+ claims_cut_count: int = Field(default=0, ge=0)
415
+ citations: tuple[KnowledgeCitation, ...]
416
+ suggestions: tuple["KnowledgeWriterSuggestion", ...] = ()
417
+ evidence_invalidated: int = Field(default=0, ge=0)
418
+ writer_version: str
419
+ page_summary: str
420
+ content_hash: SHA256
421
+ tokens: int | None = Field(default=None, ge=0)
422
+ cost_usd: float | None = Field(default=None, ge=0)
423
+ session_transcript_uri: str | None = None
424
+
425
+ @model_validator(mode="after")
426
+ def require_bounded_uncited_count(self) -> "KnowledgeCompilationWrite":
427
+ """Keep the offered-but-unused count within the candidate manifest."""
428
+ if self.uncited_count > self.candidate_count:
429
+ raise ValueError("uncited_count cannot exceed candidate_count")
430
+ return self
431
+
432
+
433
+ class KnowledgeCompilationFailure(BaseModel):
434
+ """One visible terminal writer attempt that never became publishable output."""
435
+
436
+ model_config = ConfigDict(frozen=True, extra="forbid")
437
+
438
+ compilation_id: UUID
439
+ deployment_id: UUID
440
+ artifact_id: UUID
441
+ inputs_hash: SHA256
442
+ candidate_count: int = Field(ge=0)
443
+ claims_cut_count: int = Field(default=0, ge=0)
444
+ writer_version: str = Field(min_length=1)
445
+ failure: str = Field(min_length=1)
446
+ session_transcript_uri: str | None = None
447
+
448
+
449
+ class KnowledgeFactFingerprint(BaseModel):
450
+ """The D45 state of one relation or observation candidate."""
451
+
452
+ model_config = ConfigDict(frozen=True, extra="forbid")
453
+
454
+ kind: Literal["relation", "observation"]
455
+ fact_id: UUID
456
+ valid_from: UTCDateTime | None = None
457
+ valid_until: UTCDateTime | None = None
458
+ invalidated_at: UTCDateTime | None = None
459
+ evidence_count: int = Field(ge=0)
460
+ contradict_count: int = Field(ge=0)
461
+ contradiction_group: UUID | None = None
462
+
463
+
464
+ class KnowledgeClaimFingerprint(BaseModel):
465
+ """Stable D54 claim grain: document lineage plus chunk content, never claim ID."""
466
+
467
+ model_config = ConfigDict(frozen=True, extra="forbid")
468
+
469
+ lineage_id: UUID
470
+ chunk_content_hash: str
471
+
472
+
473
+ class KnowledgeRuleConfiguration(BaseModel):
474
+ """The hash-visible configuration of one active page rule."""
475
+
476
+ model_config = ConfigDict(frozen=True, extra="forbid")
477
+
478
+ rule_id: UUID
479
+ kind: KnowledgeRuleKind
480
+ params: dict[str, JsonValue]
481
+
482
+
483
+ class KnowledgeInputSnapshot(BaseModel):
484
+ """Every deterministic input that decides whether a compiled page is stale."""
485
+
486
+ model_config = ConfigDict(frozen=True, extra="forbid")
487
+
488
+ facts: tuple[KnowledgeFactFingerprint, ...] = ()
489
+ claims: tuple[KnowledgeClaimFingerprint, ...] = ()
490
+ rules: tuple[KnowledgeRuleConfiguration, ...] = ()
491
+ curation_hash: str | None = None
492
+ child_summary_hashes: tuple[str, ...] = ()
493
+ shared_model_summary_hash: str | None = None
494
+ writer_version: str
495
+
496
+
497
+ class KnowledgeFactSheetFact(BaseModel):
498
+ """One display-grade relation or observation selected by a page rule."""
499
+
500
+ model_config = ConfigDict(frozen=True, extra="forbid")
501
+
502
+ kind: Literal["relation", "observation"]
503
+ fact_id: UUID
504
+ label: str = Field(min_length=1)
505
+ valid_from: UTCDateTime | None = None
506
+ valid_until: UTCDateTime | None = None
507
+ ingested_at: UTCDateTime
508
+ invalidated_at: UTCDateTime | None = None
509
+ evidence_count: int = Field(ge=0)
510
+ contradict_count: int = Field(ge=0)
511
+ contradiction_group: UUID | None = None
512
+
513
+
514
+ class KnowledgeFactSheetSnapshot(BaseModel):
515
+ """One repeatable-read rule result ready for deterministic rendering."""
516
+
517
+ model_config = ConfigDict(frozen=True, extra="forbid")
518
+
519
+ artifact_id: UUID
520
+ deployment_id: UUID
521
+ evidence_as_of: UTCDateTime
522
+ input_snapshot: KnowledgeInputSnapshot
523
+ facts: tuple[KnowledgeFactSheetFact, ...]
524
+
525
+
526
+ class KnowledgeRenderedFactSheet(BaseModel):
527
+ """Rendered band plus the exact counts used by deterministic summaries."""
528
+
529
+ model_config = ConfigDict(frozen=True, extra="forbid")
530
+
531
+ markdown: str
532
+ current_relation_count: int = Field(ge=0)
533
+ observation_count: int = Field(ge=0)
534
+ contradiction_group_count: int = Field(ge=0)
535
+
536
+
537
+ class KnowledgeWriterFactReference(BaseModel):
538
+ """One claim-to-fact evidence edge exposed inside a writer bundle."""
539
+
540
+ model_config = ConfigDict(frozen=True, extra="forbid")
541
+
542
+ kind: Literal["relation", "observation"]
543
+ fact_id: UUID
544
+ stance: Literal["supports", "contradicts"]
545
+
546
+
547
+ class KnowledgeWriterClaim(BaseModel):
548
+ """One current claim body carrying its stable D54 candidate coordinate."""
549
+
550
+ model_config = ConfigDict(frozen=True, extra="forbid")
551
+
552
+ claim_id: UUID
553
+ lineage_id: UUID
554
+ chunk_content_hash: str = Field(min_length=1)
555
+ claim_text: str = Field(min_length=1)
556
+ source_span: str = Field(min_length=1)
557
+ document_title: str = Field(min_length=1)
558
+ source_kind: str = Field(min_length=1)
559
+ fact_references: tuple[KnowledgeWriterFactReference, ...] = ()
560
+
561
+
562
+ class KnowledgeWriterClaimGroup(BaseModel):
563
+ """All current claims at one stable lineage-plus-chunk candidate coordinate."""
564
+
565
+ model_config = ConfigDict(frozen=True, extra="forbid")
566
+
567
+ lineage_id: UUID
568
+ chunk_content_hash: str = Field(min_length=1)
569
+ claims: tuple[KnowledgeWriterClaim, ...] = Field(min_length=1)
570
+
571
+
572
+ class KnowledgeWriterBundle(BaseModel):
573
+ """Exact fact candidates plus the deterministic capped claim offering."""
574
+
575
+ model_config = ConfigDict(frozen=True, extra="forbid")
576
+
577
+ fact_sheet: KnowledgeFactSheetSnapshot
578
+ claim_groups: tuple[KnowledgeWriterClaimGroup, ...] = ()
579
+ claim_candidate_count: int = Field(ge=0)
580
+ claims_cut_count: int = Field(ge=0)
581
+
582
+ @model_validator(mode="after")
583
+ def require_honest_claim_cut(self) -> "KnowledgeWriterBundle":
584
+ """Make the capped bundle account for every rule-matched claim coordinate."""
585
+ if len(self.claim_groups) + self.claims_cut_count != self.claim_candidate_count:
586
+ raise ValueError("offered and cut claim counts must equal claim candidates")
587
+ return self
588
+
589
+
590
+ class KnowledgeWriterCoverage(BaseModel):
591
+ """Mechanical offered/cited/uncited candidate accounting for one writer."""
592
+
593
+ model_config = ConfigDict(frozen=True, extra="forbid")
594
+
595
+ candidate_count: int = Field(ge=0)
596
+ cited_candidate_count: int = Field(ge=0)
597
+ uncited_count: int = Field(ge=0)
598
+
599
+ @model_validator(mode="after")
600
+ def require_complete_partition(self) -> "KnowledgeWriterCoverage":
601
+ """Partition every offered candidate into cited or uncited exactly once."""
602
+ if self.cited_candidate_count + self.uncited_count != self.candidate_count:
603
+ raise ValueError("cited and uncited counts must partition candidates")
604
+ return self
605
+
606
+
607
+ class KnowledgeWriterSuggestion(BaseModel):
608
+ """A writer-proposed planner input that WP-6.4 records but never applies."""
609
+
610
+ model_config = ConfigDict(frozen=True, extra="forbid")
611
+
612
+ action: KnowledgePlanAction
613
+ rationale: str = Field(min_length=1)
614
+ payload: dict[str, JsonValue] = Field(default_factory=dict)
615
+
616
+
617
+ class KnowledgeAgentSandboxPolicy(BaseModel):
618
+ """The binding D52 limits applied to one stock-harness Plane-K session."""
619
+
620
+ model_config = ConfigDict(frozen=True, extra="forbid")
621
+
622
+ network_access: Literal["none"] = "none"
623
+ memory_access: Literal["read_only"] = "read_only"
624
+ repository_write_access: Literal[False] = False
625
+ accepted_output_paths: tuple[str, ...]
626
+
627
+ @field_validator("accepted_output_paths")
628
+ @classmethod
629
+ def require_safe_declared_outputs(cls, value: tuple[str, ...]) -> tuple[str, ...]:
630
+ """Keep each worker's declared output surface normalized and inside output/."""
631
+ if not value or len(set(value)) != len(value):
632
+ raise ValueError("agent output paths must be non-empty and unique")
633
+ for raw_path in value:
634
+ path = PurePosixPath(raw_path)
635
+ if (
636
+ path.is_absolute()
637
+ or ".." in path.parts
638
+ or str(path) != raw_path
639
+ or not raw_path.startswith("output/")
640
+ ):
641
+ raise ValueError("agent output path must be normalized under output/")
642
+ return value
643
+
644
+
645
+ class KnowledgeWriterSandboxPolicy(KnowledgeAgentSandboxPolicy):
646
+ """The fixed declared output surface of one prose-writer session."""
647
+
648
+ accepted_output_paths: tuple[str, ...] = KNOWLEDGE_WRITER_OUTPUT_PATHS
649
+
650
+ @field_validator("accepted_output_paths")
651
+ @classmethod
652
+ def require_declared_writer_outputs(cls, value: tuple[str, ...]) -> tuple[str, ...]:
653
+ """Prevent runtime configuration from widening the writer's output surface."""
654
+ if value != KNOWLEDGE_WRITER_OUTPUT_PATHS:
655
+ raise ValueError("accepted writer output paths are fixed by contract")
656
+ return value
657
+
658
+
659
+ class KnowledgeAgentSessionRequest(BaseModel):
660
+ """One isolated stock-harness invocation prepared by a Plane-K worker."""
661
+
662
+ model_config = ConfigDict(frozen=True, extra="forbid")
663
+
664
+ session_id: UUID
665
+ model: str = Field(min_length=1)
666
+ prompt: str = Field(min_length=1)
667
+ timeout_seconds: int = Field(gt=0)
668
+ input_files: dict[str, str]
669
+ mounts: PublishedMounts
670
+ sandbox: KnowledgeAgentSandboxPolicy
671
+
672
+ @field_validator("input_files")
673
+ @classmethod
674
+ def require_safe_input_paths(cls, value: dict[str, str]) -> dict[str, str]:
675
+ """Keep compiler-prepared inputs normalized and outside the output surface."""
676
+ for raw_path in value:
677
+ path = PurePosixPath(raw_path)
678
+ if (
679
+ not raw_path
680
+ or path.is_absolute()
681
+ or ".." in path.parts
682
+ or str(path) != raw_path
683
+ or raw_path.startswith("output/")
684
+ ):
685
+ raise ValueError("agent input path must be normalized and read-only")
686
+ return value
687
+
688
+
689
+ class KnowledgeWriterSessionRequest(KnowledgeAgentSessionRequest):
690
+ """One prose-writer invocation with the fixed writer output contract."""
691
+
692
+ sandbox: KnowledgeAgentSandboxPolicy = Field(
693
+ default_factory=KnowledgeWriterSandboxPolicy
694
+ )
695
+
696
+ @model_validator(mode="after")
697
+ def require_writer_output_contract(self) -> "KnowledgeWriterSessionRequest":
698
+ """Reject callers that replace the fixed prose-writer output surface."""
699
+ if self.sandbox.accepted_output_paths != KNOWLEDGE_WRITER_OUTPUT_PATHS:
700
+ raise ValueError("accepted writer output paths are fixed by contract")
701
+ return self
702
+
703
+
704
+ class KnowledgeAgentSessionResult(BaseModel):
705
+ """Raw declared files and complete process transcript from one harness session."""
706
+
707
+ model_config = ConfigDict(frozen=True, extra="forbid")
708
+
709
+ session_id: UUID
710
+ exit_code: int | None
711
+ timed_out: bool = False
712
+ output_files: dict[str, str] = Field(default_factory=dict)
713
+ transcript: str = Field(min_length=1)
714
+ tokens: int | None = Field(default=None, ge=0)
715
+ cost_usd: float | None = Field(default=None, ge=0)
716
+
717
+ @model_validator(mode="after")
718
+ def require_consistent_terminal_state(self) -> "KnowledgeWriterSessionResult":
719
+ """Distinguish a timeout from an ordinary process exit unambiguously."""
720
+ if self.timed_out == (self.exit_code is not None):
721
+ raise ValueError("timed-out sessions have no exit code; exited sessions do")
722
+ return self
723
+
724
+
725
+ KnowledgeWriterSessionResult = KnowledgeAgentSessionResult
726
+
727
+
728
+ class KnowledgeCompileContext(BaseModel):
729
+ """Git/model inputs supplied to the Postgres-owned manifest computation."""
730
+
731
+ model_config = ConfigDict(frozen=True, extra="forbid")
732
+
733
+ curation_hash: str | None = None
734
+ shared_model_summary_hash: str | None = None
735
+ writer_version: str
736
+
737
+
738
+ class KnowledgeCompileArtifact(BaseModel):
739
+ """One compiled artifact as seen by the dependency scheduler."""
740
+
741
+ model_config = ConfigDict(frozen=True, extra="forbid")
742
+
743
+ artifact_id: UUID
744
+ deployment_id: UUID
745
+ scope_id: UUID | None = None
746
+ parent_artifact_id: UUID | None = None
747
+ git_path: str
748
+ curation_path: str | None = None
749
+ artifact_kind: str | None = None
750
+ page_summary: str | None = None
751
+ content_hash: SHA256 | None = None
752
+ stale: bool
753
+
754
+ @field_validator("git_path", "curation_path")
755
+ @classmethod
756
+ def require_safe_markdown_path(cls, value: str | None) -> str | None:
757
+ """Keep driver-owned writes relative, normalized, and Markdown-only."""
758
+ if value is None:
759
+ return None
760
+ path = PurePosixPath(value)
761
+ if (
762
+ not value
763
+ or path.is_absolute()
764
+ or ".." in path.parts
765
+ or str(path) != value
766
+ or path.suffix != ".md"
767
+ ):
768
+ raise ValueError("git_path must be a normalized relative Markdown path")
769
+ return value
770
+
771
+
772
+ class KnowledgePageCompileRequest(BaseModel):
773
+ """Deterministic context passed to the future per-page compiler seam."""
774
+
775
+ model_config = ConfigDict(frozen=True, extra="forbid")
776
+
777
+ artifact: KnowledgeCompileArtifact
778
+ child_summaries: dict[UUID, str] = Field(default_factory=dict)
779
+ shared_model_summary: str | None = None
780
+ curation_hash: str | None = None
781
+ curation_markdown: str | None = None
782
+ previous_markdown: str | None = None
783
+ exclusions: tuple[KnowledgeEvidenceTarget, ...] = ()
784
+
785
+
786
+ class KnowledgePageCompileOutput(BaseModel):
787
+ """Structured page output consumed and validated by the commit driver."""
788
+
789
+ model_config = ConfigDict(frozen=True, extra="forbid")
790
+
791
+ compilation: KnowledgeCompilationWrite
792
+ markdown: str
793
+
794
+
795
+ class KnowledgePendingCycle(BaseModel):
796
+ """One recoverable publish batch whose Postgres finalize step is pending."""
797
+
798
+ model_config = ConfigDict(frozen=True, extra="forbid")
799
+
800
+ cycle_id: UUID
801
+ deployment_id: UUID
802
+ compilations: tuple[KnowledgeCompilationWrite, ...] = Field(min_length=1)
803
+
804
+
805
+ class KnowledgeCommitCycleResult(BaseModel):
806
+ """Observable outcome of one locked checkout/compile/publish cycle."""
807
+
808
+ model_config = ConfigDict(frozen=True, extra="forbid")
809
+
810
+ checkout_revision: str
811
+ published_revision: str | None = None
812
+ compiled_artifact_ids: tuple[UUID, ...] = ()
813
+ recovered_cycle_ids: tuple[UUID, ...] = ()
814
+ quarantined_artifact_ids: tuple[UUID, ...] = ()
815
+ stamped_plan_decision_ids: tuple[UUID, ...] = ()
816
+ registered_authored_artifact_ids: tuple[UUID, ...] = ()
817
+ synced_authored_artifact_ids: tuple[UUID, ...] = ()
818
+ authored_lint_flag_artifact_ids: tuple[UUID, ...] = ()
819
+
820
+
821
+ class KnowledgeEvidenceDelta(BaseModel):
822
+ """Fact/document/community identifiers changed since the previous K cycle."""
823
+
824
+ model_config = ConfigDict(frozen=True, extra="forbid")
825
+
826
+ relation_ids: tuple[UUID, ...] = ()
827
+ observation_ids: tuple[UUID, ...] = ()
828
+ claim_ids: tuple[UUID, ...] = ()
829
+ doc_ids: tuple[UUID, ...] = ()
830
+ community_ids: tuple[UUID, ...] = ()
831
+
832
+
833
+ class KnowledgeArtifactHash(BaseModel):
834
+ """One artifact's recorded and freshly computed staleness keys."""
835
+
836
+ model_config = ConfigDict(frozen=True, extra="forbid")
837
+
838
+ artifact_id: UUID
839
+ recorded_hash: str | None
840
+ computed_hash: str