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,431 @@
1
+ """Typed Plane-K planner, reflection, and quarantine values (D45/D46)."""
2
+
3
+ from decimal import Decimal
4
+ from enum import StrEnum
5
+ from pathlib import PurePosixPath
6
+ from typing import Annotated
7
+ from typing import Literal
8
+ from typing import TypeAlias
9
+ from uuid import UUID
10
+
11
+ from pydantic import AfterValidator
12
+ from pydantic import BaseModel
13
+ from pydantic import ConfigDict
14
+ from pydantic import Field
15
+ from pydantic import field_validator
16
+ from pydantic import model_validator
17
+
18
+ from rememberstack.model.knowledge import KnowledgeAgentSandboxPolicy
19
+ from rememberstack.model.knowledge import KnowledgeAgentSessionRequest
20
+ from rememberstack.model.knowledge import KnowledgeArtifactStatus
21
+ from rememberstack.model.knowledge import KnowledgeLayer
22
+ from rememberstack.model.knowledge import KnowledgePageKind
23
+ from rememberstack.model.knowledge import KnowledgePlanAction
24
+ from rememberstack.model.knowledge import KnowledgePlanStatus
25
+ from rememberstack.model.knowledge import KnowledgePlanTrigger
26
+ from rememberstack.model.knowledge import KnowledgeRuleParams
27
+ from rememberstack.model.knowledge import KnowledgeWriterSuggestion
28
+ from rememberstack.model.queue import UTCDateTime
29
+
30
+ KNOWLEDGE_PLANNER_OUTPUT_PATHS = ("output/decisions.json",)
31
+
32
+
33
+ def _canonical_uuids(values: tuple[UUID, ...]) -> tuple[UUID, ...]:
34
+ """Return stable unique UUIDs for set-valued planner fields."""
35
+ return tuple(sorted(set(values), key=str))
36
+
37
+
38
+ CanonicalUUIDs: TypeAlias = Annotated[
39
+ tuple[UUID, ...], AfterValidator(_canonical_uuids)
40
+ ]
41
+
42
+
43
+ def _normalized_git_path(*, value: str, field: str) -> str:
44
+ """Reject absolute, escaping, or non-normalized repository paths."""
45
+ path = PurePosixPath(value)
46
+ if path.is_absolute() or ".." in path.parts or str(path) != value:
47
+ raise ValueError(f"{field} must be a normalized repository path")
48
+ return value
49
+
50
+
51
+ class KnowledgePlanRunKind(StrEnum):
52
+ """The proposing seat that produced one structural planning run."""
53
+
54
+ PLANNER = "planner"
55
+ REFLECTION = "reflection"
56
+
57
+
58
+ class KnowledgePlanRunStatus(StrEnum):
59
+ """Terminal state of one transcript-bearing planning run."""
60
+
61
+ SUCCEEDED = "succeeded"
62
+ FAILED = "failed"
63
+
64
+
65
+ class KnowledgePlanBand(StrEnum):
66
+ """Deterministic D24-style consequence of a structural proposal."""
67
+
68
+ AUTO_APPLY = "auto_apply"
69
+ REVIEW = "review"
70
+
71
+
72
+ class KnowledgeQuarantineStatus(StrEnum):
73
+ """Resolution state of a direct edit to a compiled page."""
74
+
75
+ PROPOSED = "proposed"
76
+ CURATION_ACCEPTED = "curation_accepted"
77
+ ADOPTED = "adopted"
78
+ REJECTED = "rejected"
79
+
80
+
81
+ class KnowledgePlannedPage(BaseModel):
82
+ """One compiled page and its complete mechanically evaluable rule set."""
83
+
84
+ model_config = ConfigDict(frozen=True, extra="forbid")
85
+
86
+ layer: KnowledgeLayer
87
+ git_path: str = Field(min_length=1)
88
+ curation_path: str = Field(min_length=1)
89
+ writer_version: str = Field(min_length=1)
90
+ rules: tuple[KnowledgeRuleParams, ...] = Field(min_length=1)
91
+ scope_id: UUID | None = None
92
+ parent_artifact_id: UUID | None = None
93
+ artifact_kind: str | None = None
94
+
95
+ @field_validator("git_path", "curation_path")
96
+ @classmethod
97
+ def require_safe_paths(cls, value: str, info: object) -> str:
98
+ """Keep planner-created files inside the repository namespace."""
99
+ field_name = getattr(info, "field_name", "path")
100
+ return _normalized_git_path(value=value, field=str(field_name))
101
+
102
+ @model_validator(mode="after")
103
+ def require_disjoint_body_and_curation_paths(self) -> "KnowledgePlannedPage":
104
+ """Prevent the machine-owned body from aliasing its human-owned sidecar."""
105
+ if self.git_path == self.curation_path:
106
+ raise ValueError("planned page body and curation paths must differ")
107
+ return self
108
+
109
+
110
+ class _KnowledgePlanProposalBase(BaseModel):
111
+ """Fields every planner or reflection proposal must justify."""
112
+
113
+ model_config = ConfigDict(frozen=True, extra="forbid")
114
+
115
+ rationale: str = Field(min_length=1)
116
+ confidence: Decimal = Field(ge=Decimal("0"), le=Decimal("1"))
117
+
118
+
119
+ class KnowledgeCreatePageProposal(_KnowledgePlanProposalBase):
120
+ """Create one evidence-derived compiled page."""
121
+
122
+ action: Literal[KnowledgePlanAction.CREATE_PAGE] = KnowledgePlanAction.CREATE_PAGE
123
+ page: KnowledgePlannedPage
124
+
125
+
126
+ class KnowledgeSplitPageProposal(_KnowledgePlanProposalBase):
127
+ """Turn one compiled page into a parent over new mechanically routed children."""
128
+
129
+ action: Literal[KnowledgePlanAction.SPLIT_PAGE] = KnowledgePlanAction.SPLIT_PAGE
130
+ source_artifact_id: UUID
131
+ pages: tuple[KnowledgePlannedPage, ...] = Field(min_length=1)
132
+
133
+ @model_validator(mode="after")
134
+ def require_children_of_source(self) -> "KnowledgeSplitPageProposal":
135
+ """Make the split topology explicit instead of guessing parentage at apply time."""
136
+ if any(
137
+ page.parent_artifact_id != self.source_artifact_id for page in self.pages
138
+ ):
139
+ raise ValueError("every split page must name the source page as parent")
140
+ paths = [page.git_path for page in self.pages]
141
+ curation_paths = [page.curation_path for page in self.pages]
142
+ if len(set(paths)) != len(paths) or len(set(curation_paths)) != len(
143
+ curation_paths
144
+ ):
145
+ raise ValueError("split page paths must be unique")
146
+ return self
147
+
148
+
149
+ class KnowledgeMergePagesProposal(_KnowledgePlanProposalBase):
150
+ """Retire several compiled pages into one new compiled target."""
151
+
152
+ action: Literal[KnowledgePlanAction.MERGE_PAGES] = KnowledgePlanAction.MERGE_PAGES
153
+ source_artifact_ids: CanonicalUUIDs = Field(min_length=2)
154
+ page: KnowledgePlannedPage
155
+
156
+ @model_validator(mode="after")
157
+ def reject_source_as_target_parent(self) -> "KnowledgeMergePagesProposal":
158
+ """Prevent an immediate cycle through a page the merge will tombstone."""
159
+ if self.page.parent_artifact_id in self.source_artifact_ids:
160
+ raise ValueError("merge target parent cannot be a merge source")
161
+ return self
162
+
163
+
164
+ class KnowledgeMovePageProposal(_KnowledgePlanProposalBase):
165
+ """Move one compiled page without changing its evidence ownership."""
166
+
167
+ action: Literal[KnowledgePlanAction.MOVE_PAGE] = KnowledgePlanAction.MOVE_PAGE
168
+ artifact_id: UUID
169
+ old_git_path: str = Field(min_length=1)
170
+ new_git_path: str = Field(min_length=1)
171
+ old_curation_path: str = Field(min_length=1)
172
+ new_curation_path: str = Field(min_length=1)
173
+ old_parent_artifact_id: UUID | None
174
+ new_parent_artifact_id: UUID | None = None
175
+
176
+ @field_validator(
177
+ "old_git_path", "new_git_path", "old_curation_path", "new_curation_path"
178
+ )
179
+ @classmethod
180
+ def require_safe_paths(cls, value: str, info: object) -> str:
181
+ """Keep both sides of a move inside the repository namespace."""
182
+ field_name = getattr(info, "field_name", "path")
183
+ return _normalized_git_path(value=value, field=str(field_name))
184
+
185
+ @model_validator(mode="after")
186
+ def require_a_real_move(self) -> "KnowledgeMovePageProposal":
187
+ """Reject a path-preserving move that changes no structure."""
188
+ if self.new_parent_artifact_id == self.artifact_id:
189
+ raise ValueError("move_page cannot make an artifact its own parent")
190
+ if self.old_git_path == self.new_git_path and (
191
+ self.old_curation_path == self.new_curation_path
192
+ and self.old_parent_artifact_id == self.new_parent_artifact_id
193
+ ):
194
+ raise ValueError("move_page must change path or parent")
195
+ if self.new_git_path == self.new_curation_path:
196
+ raise ValueError("moved body and curation paths must differ")
197
+ return self
198
+
199
+
200
+ class KnowledgeRetirePageProposal(_KnowledgePlanProposalBase):
201
+ """Retire one compiled page whose evidence no longer needs a home."""
202
+
203
+ action: Literal[KnowledgePlanAction.RETIRE_PAGE] = KnowledgePlanAction.RETIRE_PAGE
204
+ artifact_id: UUID
205
+
206
+
207
+ class KnowledgeAdjustRuleProposal(_KnowledgePlanProposalBase):
208
+ """Replace one compiled page's routing-rule union atomically."""
209
+
210
+ action: Literal[KnowledgePlanAction.ADJUST_RULE] = KnowledgePlanAction.ADJUST_RULE
211
+ artifact_id: UUID
212
+ rules: tuple[KnowledgeRuleParams, ...] = Field(min_length=1)
213
+
214
+
215
+ class KnowledgeConvertKindProposal(_KnowledgePlanProposalBase):
216
+ """Adopt a compiled page or hand an authored page to the compiler."""
217
+
218
+ action: Literal[KnowledgePlanAction.CONVERT_KIND] = KnowledgePlanAction.CONVERT_KIND
219
+ artifact_id: UUID
220
+ from_kind: KnowledgePageKind
221
+ to_kind: KnowledgePageKind
222
+ writer_version: str | None = None
223
+ curation_path: str | None = None
224
+ rules: tuple[KnowledgeRuleParams, ...] = ()
225
+
226
+ @field_validator("curation_path")
227
+ @classmethod
228
+ def require_safe_optional_curation_path(cls, value: str | None) -> str | None:
229
+ """Keep handover sidecars inside the repository namespace."""
230
+ if value is None:
231
+ return None
232
+ return _normalized_git_path(value=value, field="curation_path")
233
+
234
+ @model_validator(mode="after")
235
+ def require_one_valid_direction(self) -> "KnowledgeConvertKindProposal":
236
+ """Bind adoption and handover to their distinct ownership inputs."""
237
+ if self.from_kind == self.to_kind:
238
+ raise ValueError("convert_kind must change page kind")
239
+ if self.to_kind is KnowledgePageKind.AUTHORED:
240
+ if (
241
+ self.writer_version is not None
242
+ or self.curation_path is not None
243
+ or self.rules
244
+ ):
245
+ raise ValueError("adoption preserves rules and removes writer inputs")
246
+ return self
247
+ if self.writer_version is None or self.curation_path is None or not self.rules:
248
+ raise ValueError(
249
+ "handover requires writer version, curation path, and rules"
250
+ )
251
+ return self
252
+
253
+
254
+ KnowledgePlanProposal: TypeAlias = Annotated[
255
+ KnowledgeCreatePageProposal
256
+ | KnowledgeSplitPageProposal
257
+ | KnowledgeMergePagesProposal
258
+ | KnowledgeMovePageProposal
259
+ | KnowledgeRetirePageProposal
260
+ | KnowledgeAdjustRuleProposal
261
+ | KnowledgeConvertKindProposal,
262
+ Field(discriminator="action"),
263
+ ]
264
+
265
+
266
+ class KnowledgePlannerSandboxPolicy(KnowledgeAgentSandboxPolicy):
267
+ """The fixed declared output surface of a planner or reflection session."""
268
+
269
+ accepted_output_paths: tuple[str, ...] = KNOWLEDGE_PLANNER_OUTPUT_PATHS
270
+
271
+ @field_validator("accepted_output_paths")
272
+ @classmethod
273
+ def require_declared_planner_output(cls, value: tuple[str, ...]) -> tuple[str, ...]:
274
+ """Prevent a structural agent from widening its accepted write surface."""
275
+ if value != KNOWLEDGE_PLANNER_OUTPUT_PATHS:
276
+ raise ValueError("accepted planner output paths are fixed by contract")
277
+ return value
278
+
279
+
280
+ class KnowledgePlannerSessionRequest(KnowledgeAgentSessionRequest):
281
+ """One planner or reflection invocation with a decisions-only write surface."""
282
+
283
+ sandbox: KnowledgeAgentSandboxPolicy = Field(
284
+ default_factory=KnowledgePlannerSandboxPolicy
285
+ )
286
+
287
+ @model_validator(mode="after")
288
+ def require_planner_output_contract(self) -> "KnowledgePlannerSessionRequest":
289
+ """Reject callers that replace the fixed decisions-only output surface."""
290
+ if self.sandbox.accepted_output_paths != KNOWLEDGE_PLANNER_OUTPUT_PATHS:
291
+ raise ValueError("accepted planner output paths are fixed by contract")
292
+ return self
293
+
294
+
295
+ class KnowledgeOrphanAggregate(BaseModel):
296
+ """New candidate identities with no compiled-page rule home for one entity."""
297
+
298
+ model_config = ConfigDict(frozen=True, extra="forbid")
299
+
300
+ entity_id: UUID
301
+ candidate_keys: tuple[str, ...] = Field(min_length=1)
302
+
303
+ @field_validator("candidate_keys")
304
+ @classmethod
305
+ def canonicalize_candidate_keys(cls, value: tuple[str, ...]) -> tuple[str, ...]:
306
+ """Count stable candidate identities once per entity."""
307
+ return tuple(sorted(set(value)))
308
+
309
+ @property
310
+ def candidate_count(self) -> int:
311
+ """Return the exact number of unique currently unhoused candidates."""
312
+ return len(self.candidate_keys)
313
+
314
+
315
+ class KnowledgePlannerArtifactState(BaseModel):
316
+ """The structural and health fields a planner may inspect for one page."""
317
+
318
+ model_config = ConfigDict(frozen=True, extra="forbid")
319
+
320
+ artifact_id: UUID
321
+ layer: KnowledgeLayer
322
+ page_kind: KnowledgePageKind
323
+ status: KnowledgeArtifactStatus
324
+ git_path: str
325
+ scope_id: UUID | None = None
326
+ parent_artifact_id: UUID | None = None
327
+ artifact_kind: str | None = None
328
+ candidate_count: int = Field(default=0, ge=0)
329
+ uncited_count: int = Field(default=0, ge=0)
330
+ page_size_bytes: int = Field(default=0, ge=0)
331
+
332
+
333
+ class KnowledgeCompiledContentState(BaseModel):
334
+ """Last accepted body identity used for deterministic drift detection."""
335
+
336
+ model_config = ConfigDict(frozen=True, extra="forbid")
337
+
338
+ artifact_id: UUID
339
+ deployment_id: UUID
340
+ git_path: str
341
+ content_hash: str
342
+
343
+
344
+ class KnowledgePlanningSnapshot(BaseModel):
345
+ """Bounded structural triggers and health metrics supplied to one agent run."""
346
+
347
+ model_config = ConfigDict(frozen=True, extra="forbid")
348
+
349
+ deployment_id: UUID
350
+ scope_id: UUID | None = None
351
+ artifacts: tuple[KnowledgePlannerArtifactState, ...]
352
+ orphan_aggregates: tuple[KnowledgeOrphanAggregate, ...] = ()
353
+ overflow_artifact_ids: CanonicalUUIDs = ()
354
+ community_ids: CanonicalUUIDs = ()
355
+ writer_suggestions: tuple[KnowledgeWriterSuggestion, ...] = ()
356
+
357
+
358
+ class KnowledgePlanRunWrite(BaseModel):
359
+ """One terminal transcript-bearing planner or reflection run."""
360
+
361
+ model_config = ConfigDict(frozen=True, extra="forbid")
362
+
363
+ run_id: UUID
364
+ deployment_id: UUID
365
+ scope_id: UUID | None = None
366
+ run_kind: KnowledgePlanRunKind
367
+ trigger: KnowledgePlanTrigger
368
+ component_version: str = Field(min_length=1)
369
+ input_hash: str = Field(min_length=1)
370
+ session_transcript_uri: str = Field(min_length=1)
371
+ status: KnowledgePlanRunStatus
372
+ failure: str | None = None
373
+ tokens: int | None = Field(default=None, ge=0)
374
+ cost_usd: Decimal | None = Field(default=None, ge=Decimal("0"))
375
+
376
+ @model_validator(mode="after")
377
+ def require_terminal_failure_shape(self) -> "KnowledgePlanRunWrite":
378
+ """Keep success and failure rows unambiguous without trimming exceptions."""
379
+ if (self.status is KnowledgePlanRunStatus.FAILED) != (self.failure is not None):
380
+ raise ValueError("only failed plan runs carry a failure traceback")
381
+ return self
382
+
383
+
384
+ class KnowledgePlanDecisionResult(BaseModel):
385
+ """The durable consequence assigned to one validated structural proposal."""
386
+
387
+ model_config = ConfigDict(frozen=True, extra="forbid")
388
+
389
+ decision_id: UUID
390
+ action: KnowledgePlanAction
391
+ status: KnowledgePlanStatus
392
+ band: KnowledgePlanBand
393
+ blast_radius: int = Field(ge=0)
394
+ expected_impact: Decimal = Field(ge=Decimal("0"))
395
+
396
+
397
+ class KnowledgePendingPlanDecision(BaseModel):
398
+ """One accepted structural decision not yet bound to a git revision."""
399
+
400
+ model_config = ConfigDict(frozen=True, extra="forbid")
401
+
402
+ decision_id: UUID
403
+ proposal: KnowledgePlanProposal
404
+ decided_at: UTCDateTime
405
+ artifact_paths: dict[UUID, str] = Field(default_factory=dict)
406
+
407
+ @field_validator("artifact_paths")
408
+ @classmethod
409
+ def require_safe_artifact_paths(cls, value: dict[UUID, str]) -> dict[UUID, str]:
410
+ """Keep reconciled file targets inside the repository namespace."""
411
+ return {
412
+ artifact_id: _normalized_git_path(value=path, field="artifact_paths")
413
+ for artifact_id, path in value.items()
414
+ }
415
+
416
+
417
+ class KnowledgeQuarantineRecord(BaseModel):
418
+ """A direct compiled-body edit preserved for explicit triage."""
419
+
420
+ model_config = ConfigDict(frozen=True, extra="forbid")
421
+
422
+ quarantine_id: UUID
423
+ decision_id: UUID
424
+ deployment_id: UUID
425
+ artifact_id: UUID
426
+ recorded_content_hash: str
427
+ detected_content_hash: str
428
+ proposed_sidecar_entry: str = Field(min_length=1)
429
+ status: KnowledgeQuarantineStatus
430
+ detected_at: UTCDateTime | None = None
431
+ resolved_at: UTCDateTime | None = None
@@ -0,0 +1,42 @@
1
+ """Lifecycle values (D54/D55): currency transitions and the reconciliation delta."""
2
+
3
+ from uuid import UUID
4
+
5
+ from pydantic import BaseModel
6
+ from pydantic import ConfigDict
7
+
8
+
9
+ class CurrencyTransition(BaseModel):
10
+ """One testimony-currency ledger event to append (D54).
11
+
12
+ Bookkeeping, never validity: nothing about the claim itself changes.
13
+ ``reason`` is a `currency_reason` enum value; the from_* fields name the
14
+ superseded coordinate (generation or version) for the audit trail.
15
+ """
16
+
17
+ model_config = ConfigDict(frozen=True, extra="forbid")
18
+
19
+ claim_id: UUID
20
+ doc_id: UUID
21
+ became_current: bool
22
+ reason: str
23
+ from_extractor_version: str | None = None
24
+ from_version_id: UUID | None = None
25
+
26
+
27
+ class ReconciliationDelta(BaseModel):
28
+ """The fact-level outcome of one reconciliation run (lifecycle §5).
29
+
30
+ Fact IDs and outcomes only — never raw claim IDs (the K stale-storm
31
+ guard); this is the `evidence_changed` payload shape.
32
+ """
33
+
34
+ model_config = ConfigDict(frozen=True, extra="forbid")
35
+
36
+ reconciliation_id: UUID
37
+ transitions: int = 0
38
+ recounted_relations: tuple[UUID, ...] = ()
39
+ recounted_observations: tuple[UUID, ...] = ()
40
+ relations_closed: tuple[UUID, ...] = ()
41
+ observations_closed: tuple[UUID, ...] = ()
42
+ flags_raised: tuple[UUID, ...] = ()
@@ -0,0 +1,78 @@
1
+ """Typed, provider-neutral model and embedding call values for the LLM boundary."""
2
+
3
+ from decimal import Decimal
4
+ from typing import Annotated
5
+ from typing import Generic
6
+ from typing import Self
7
+ from typing import TypeAlias
8
+ from typing import TypeVar
9
+
10
+ from pydantic import BaseModel
11
+ from pydantic import ConfigDict
12
+ from pydantic import Field
13
+ from pydantic import model_validator
14
+
15
+ _NonEmptyText = Annotated[str, Field(min_length=1)]
16
+ _EmbeddingVector = Annotated[tuple[float, ...], Field(min_length=1)]
17
+ StructuredResponseModel: TypeAlias = BaseModel
18
+ ResponseT = TypeVar("ResponseT", bound=StructuredResponseModel)
19
+
20
+
21
+ class ProviderAccountingError(Exception):
22
+ """A provider response omitted or malformed required usage accounting."""
23
+
24
+
25
+ class ProviderCallUsage(BaseModel):
26
+ """Provider-reported accounting for one successful generation or embedding call."""
27
+
28
+ model_config = ConfigDict(frozen=True, extra="forbid")
29
+
30
+ model_name: _NonEmptyText
31
+ tokens_in: int = Field(ge=0)
32
+ tokens_out: int = Field(ge=0)
33
+ cost_usd: Decimal = Field(ge=Decimal(0))
34
+ latency_ms: int = Field(ge=0)
35
+
36
+
37
+ class GeneratedResponse(BaseModel, Generic[ResponseT]):
38
+ """A validated structured output paired with its provider accounting."""
39
+
40
+ model_config = ConfigDict(frozen=True, extra="forbid")
41
+
42
+ output: ResponseT
43
+ usage: ProviderCallUsage
44
+
45
+
46
+ class ModelRequest(BaseModel):
47
+ """A rendered prompt and configured model identifier ready for invocation."""
48
+
49
+ model_config = ConfigDict(frozen=True, extra="forbid")
50
+
51
+ model: _NonEmptyText
52
+ prompt: _NonEmptyText
53
+
54
+
55
+ class EmbeddingRequest(BaseModel):
56
+ """One non-empty batch for a configured embedding model."""
57
+
58
+ model_config = ConfigDict(frozen=True, extra="forbid")
59
+
60
+ model: _NonEmptyText
61
+ texts: Annotated[tuple[_NonEmptyText, ...], Field(min_length=1)]
62
+
63
+
64
+ class EmbeddingResponse(BaseModel):
65
+ """A non-empty embedding batch whose vectors share one dimension."""
66
+
67
+ model_config = ConfigDict(frozen=True, extra="forbid")
68
+
69
+ vectors: Annotated[tuple[_EmbeddingVector, ...], Field(min_length=1)]
70
+ usage: ProviderCallUsage
71
+
72
+ @model_validator(mode="after")
73
+ def require_one_dimension(self) -> Self:
74
+ """Reject provider responses that mix embedding dimensions."""
75
+ expected_dimension = len(self.vectors[0])
76
+ if any(len(vector) != expected_dimension for vector in self.vectors[1:]):
77
+ raise ValueError("all embedding vectors must have the same dimension")
78
+ return self
@@ -0,0 +1,24 @@
1
+ """Portable result of publishing the four D51 read-only mount surfaces."""
2
+
3
+ from typing import Annotated
4
+ from typing import Literal
5
+ from uuid import UUID
6
+
7
+ from pydantic import BaseModel
8
+ from pydantic import ConfigDict
9
+ from pydantic import Field
10
+
11
+ _MountLocator = Annotated[str, Field(min_length=1)]
12
+
13
+
14
+ class PublishedMounts(BaseModel):
15
+ """Locations for the exact P3, artifact, raw, and Plane-K read-only views."""
16
+
17
+ model_config = ConfigDict(frozen=True, extra="forbid")
18
+
19
+ deployment_id: UUID
20
+ p3: _MountLocator
21
+ artifacts: _MountLocator
22
+ raw: _MountLocator
23
+ knowledge: _MountLocator
24
+ read_only: Literal[True]
@@ -0,0 +1,21 @@
1
+ """Provider-neutral object keys for immutable raw, artifact, and snapshot bytes."""
2
+
3
+ from typing import Annotated
4
+
5
+ from pydantic import ConfigDict
6
+ from pydantic import Field
7
+ from pydantic import RootModel
8
+
9
+
10
+ class ObjectKey(RootModel[Annotated[str, Field(min_length=1)]]):
11
+ """Opaque non-empty key in the configured immutable object store."""
12
+
13
+ model_config = ConfigDict(frozen=True)
14
+
15
+
16
+ class ObjectAlreadyExistsError(Exception):
17
+ """A write to an occupied key — objects are immutable, never replaced."""
18
+
19
+
20
+ class ObjectKeyEscapesRootError(Exception):
21
+ """An object key that would resolve outside the store root — refused."""
@@ -0,0 +1,59 @@
1
+ """Typed results for the WP-7.2 portable operational scale battery."""
2
+
3
+ from typing import Annotated
4
+ from typing import get_args
5
+ from typing import Literal
6
+
7
+ from pydantic import BaseModel
8
+ from pydantic import ConfigDict
9
+ from pydantic import Field
10
+ from pydantic import model_validator
11
+
12
+ OperationalScaleName = Literal[
13
+ "d23_schema_shape",
14
+ "hub_registry_blocking",
15
+ "hub_lineage_recount",
16
+ "provider_neutral_batching",
17
+ ]
18
+
19
+ OPERATIONAL_SCALE_NAMES = frozenset(get_args(OperationalScaleName))
20
+
21
+
22
+ class OperationalScaleMeasurement(BaseModel):
23
+ """One fixed-profile measurement and its non-SLA observations."""
24
+
25
+ model_config = ConfigDict(frozen=True, extra="forbid")
26
+
27
+ name: OperationalScaleName
28
+ profile: str
29
+ scale: dict[str, Annotated[int, Field(ge=0)]]
30
+ metrics: dict[str, object]
31
+ limitations: tuple[str, ...] = ()
32
+ passed: bool
33
+
34
+
35
+ class OperationalScaleReport(BaseModel):
36
+ """The complete four-measurement WP-7.2 report."""
37
+
38
+ model_config = ConfigDict(frozen=True, extra="forbid")
39
+
40
+ measurements: Annotated[
41
+ tuple[OperationalScaleMeasurement, ...], Field(min_length=4, max_length=4)
42
+ ]
43
+
44
+ @model_validator(mode="after")
45
+ def complete_battery(self) -> "OperationalScaleReport":
46
+ """Reject duplicate or absent measurements."""
47
+ names = {measurement.name for measurement in self.measurements}
48
+ if names != OPERATIONAL_SCALE_NAMES:
49
+ missing = sorted(OPERATIONAL_SCALE_NAMES - names)
50
+ extra = sorted(names - OPERATIONAL_SCALE_NAMES)
51
+ raise ValueError(
52
+ f"operational scale names mismatch: missing={missing}, extra={extra}"
53
+ )
54
+ return self
55
+
56
+ @property
57
+ def passed(self) -> bool:
58
+ """The report passes only when every structural invariant holds."""
59
+ return all(measurement.passed for measurement in self.measurements)