rememberstack 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rememberstack/__init__.py +9 -0
- rememberstack/adapters/__init__.py +42 -0
- rememberstack/adapters/codex_writer.py +221 -0
- rememberstack/adapters/markitdown_converter.py +42 -0
- rememberstack/adapters/openrouter.py +136 -0
- rememberstack/adapters/selfhost/__init__.py +54 -0
- rememberstack/adapters/selfhost/forget.py +66 -0
- rememberstack/adapters/selfhost/git.py +374 -0
- rememberstack/adapters/selfhost/lance.py +328 -0
- rememberstack/adapters/selfhost/minio.py +279 -0
- rememberstack/adapters/selfhost/mounts.py +249 -0
- rememberstack/adapters/selfhost/object_store.py +130 -0
- rememberstack/adapters/selfhost/projection.py +80 -0
- rememberstack/adapters/selfhost/queue.py +137 -0
- rememberstack/adapters/selfhost/telemetry.py +45 -0
- rememberstack/adapters/selfhost/watcher.py +70 -0
- rememberstack/adapters/testing/__init__.py +15 -0
- rememberstack/adapters/testing/cost_meter.py +13 -0
- rememberstack/adapters/testing/model_provider.py +83 -0
- rememberstack/adapters/testing/queue.py +43 -0
- rememberstack/adapters/testing/telemetry.py +22 -0
- rememberstack/client.py +19 -0
- rememberstack/core/__init__.py +127 -0
- rememberstack/core/blockizer.py +189 -0
- rememberstack/core/chunker.py +216 -0
- rememberstack/core/consumption_skill.py +275 -0
- rememberstack/core/conversion.py +76 -0
- rememberstack/core/core_manifest.py +598 -0
- rememberstack/core/extension_packs.py +124 -0
- rememberstack/core/forget.py +17 -0
- rememberstack/core/knowledge_authored.py +276 -0
- rememberstack/core/knowledge_compile.py +215 -0
- rememberstack/core/knowledge_fact_sheet.py +210 -0
- rememberstack/core/knowledge_hashing.py +68 -0
- rememberstack/core/knowledge_planner.py +64 -0
- rememberstack/core/knowledge_writer.py +175 -0
- rememberstack/core/ranking.py +200 -0
- rememberstack/core/recipe_linter.py +149 -0
- rememberstack/core/section_snap.py +209 -0
- rememberstack/core/storage_routing.py +27 -0
- rememberstack/eval/__init__.py +53 -0
- rememberstack/eval/consumption.py +141 -0
- rememberstack/eval/contradiction.py +184 -0
- rememberstack/eval/harness.py +136 -0
- rememberstack/eval/lifecycle.py +400 -0
- rememberstack/eval/operational_scale.py +49 -0
- rememberstack/eval/resolution.py +255 -0
- rememberstack/eval/retrieval_spikes.py +50 -0
- rememberstack/eval/skeleton.py +231 -0
- rememberstack/llm/__init__.py +1 -0
- rememberstack/model/__init__.py +589 -0
- rememberstack/model/adjudication.py +100 -0
- rememberstack/model/auth.py +27 -0
- rememberstack/model/blocks.py +30 -0
- rememberstack/model/chunks.py +190 -0
- rememberstack/model/claims.py +162 -0
- rememberstack/model/client.py +98 -0
- rememberstack/model/clustering.py +54 -0
- rememberstack/model/component_version.py +124 -0
- rememberstack/model/consumption.py +88 -0
- rememberstack/model/conversion.py +31 -0
- rememberstack/model/deployment.py +53 -0
- rememberstack/model/documents.py +168 -0
- rememberstack/model/envelope.py +513 -0
- rememberstack/model/evaluation.py +72 -0
- rememberstack/model/forget.py +143 -0
- rememberstack/model/git.py +13 -0
- rememberstack/model/knowledge.py +840 -0
- rememberstack/model/knowledge_authored.py +325 -0
- rememberstack/model/knowledge_planner.py +431 -0
- rememberstack/model/lifecycle.py +42 -0
- rememberstack/model/model_provider.py +78 -0
- rememberstack/model/mounts.py +24 -0
- rememberstack/model/object_store.py +21 -0
- rememberstack/model/operational_scale.py +59 -0
- rememberstack/model/operations.py +153 -0
- rememberstack/model/processing.py +228 -0
- rememberstack/model/queue.py +73 -0
- rememberstack/model/recipes.py +83 -0
- rememberstack/model/relations.py +79 -0
- rememberstack/model/resolution.py +83 -0
- rememberstack/model/retrieval_spikes.py +62 -0
- rememberstack/model/sections.py +120 -0
- rememberstack/model/telemetry.py +30 -0
- rememberstack/ports/__init__.py +29 -0
- rememberstack/ports/auth.py +16 -0
- rememberstack/ports/connector.py +23 -0
- rememberstack/ports/cost_meter.py +17 -0
- rememberstack/ports/forget.py +20 -0
- rememberstack/ports/git.py +20 -0
- rememberstack/ports/model_provider.py +28 -0
- rememberstack/ports/mounts.py +16 -0
- rememberstack/ports/object_store.py +27 -0
- rememberstack/ports/p1_index.py +92 -0
- rememberstack/ports/purge.py +93 -0
- rememberstack/ports/queue.py +23 -0
- rememberstack/ports/telemetry.py +21 -0
- rememberstack/profiles/__init__.py +22 -0
- rememberstack/profiles/selfhost.py +324 -0
- rememberstack/profiles/selfhost_forget.py +158 -0
- rememberstack/profiles/selfhost_operations.py +95 -0
- rememberstack/py.typed +1 -0
- rememberstack/spine/__init__.py +93 -0
- rememberstack/spine/admission.py +26 -0
- rememberstack/spine/backfill.py +168 -0
- rememberstack/spine/catalog_contract.py +742 -0
- rememberstack/spine/chunk_catalog.py +237 -0
- rememberstack/spine/claim_catalog.py +298 -0
- rememberstack/spine/clustering.py +740 -0
- rememberstack/spine/component_versions.py +208 -0
- rememberstack/spine/consumption.py +81 -0
- rememberstack/spine/deployment_bootstrap.py +445 -0
- rememberstack/spine/document_catalog.py +621 -0
- rememberstack/spine/entity_registry.py +205 -0
- rememberstack/spine/extension_packs.py +220 -0
- rememberstack/spine/fact_catalog.py +571 -0
- rememberstack/spine/forget.py +1753 -0
- rememberstack/spine/knowledge.py +5467 -0
- rememberstack/spine/lifecycle.py +1071 -0
- rememberstack/spine/migrations/__init__.py +1 -0
- rememberstack/spine/migrations/_helpers.py +153 -0
- rememberstack/spine/migrations/env.py +58 -0
- rememberstack/spine/migrations/script.py.mako +27 -0
- rememberstack/spine/migrations/versions/__init__.py +1 -0
- rememberstack/spine/migrations/versions/p0_02_0001_extensions_enums.py +189 -0
- rememberstack/spine/migrations/versions/p0_02_0002_infrastructure_registries.py +321 -0
- rememberstack/spine/migrations/versions/p0_02_0003_entities_evaluation_e0_e1.py +631 -0
- rememberstack/spine/migrations/versions/p0_02_0004_claims_facts_evidence.py +411 -0
- rememberstack/spine/migrations/versions/p0_02_0005_projection_knowledge_retrieval.py +391 -0
- rememberstack/spine/migrations/versions/p0_02_0006_partitions_views.py +158 -0
- rememberstack/spine/migrations/versions/p2_06_0007_invalidated_outcome.py +26 -0
- rememberstack/spine/migrations/versions/p3_01_0008_document_version_target.py +58 -0
- rememberstack/spine/migrations/versions/p3_05_0009_reconcile_stage.py +27 -0
- rememberstack/spine/migrations/versions/p3_07_0010_lifecycle_eval_suite.py +25 -0
- rememberstack/spine/migrations/versions/p4_01_0011_survivor_view_rewrite.py +57 -0
- rememberstack/spine/migrations/versions/p6_02_0012_knowledge_compile_recovery.py +58 -0
- rememberstack/spine/migrations/versions/p6_04_0013_knowledge_writer_ledger.py +46 -0
- rememberstack/spine/migrations/versions/p6_05_0014_knowledge_planner_runtime.py +217 -0
- rememberstack/spine/migrations/versions/p6_06_0015_authored_dispatch_runtime.py +38 -0
- rememberstack/spine/migrations/versions/p7_02_0016_operational_eval_suite.py +19 -0
- rememberstack/spine/migrations/versions/p7_05_0017_hard_forget.py +55 -0
- rememberstack/spine/observation_adjudication.py +778 -0
- rememberstack/spine/operations.py +298 -0
- rememberstack/spine/projection.py +662 -0
- rememberstack/spine/recipes.py +276 -0
- rememberstack/spine/resolver.py +763 -0
- rememberstack/spine/review.py +650 -0
- rememberstack/spine/settings.py +22 -0
- rememberstack/spine/supersession.py +510 -0
- rememberstack/spine/sync.py +128 -0
- rememberstack/spine/work_ledger.py +816 -0
- rememberstack/surfaces/__init__.py +110 -0
- rememberstack/surfaces/cli.py +447 -0
- rememberstack/surfaces/consumption_skill.py +87 -0
- rememberstack/surfaces/graph_queries.py +698 -0
- rememberstack/surfaces/http_api.py +377 -0
- rememberstack/surfaces/mcp.py +67 -0
- rememberstack/surfaces/query_engine.py +1591 -0
- rememberstack/surfaces/recipe_executor.py +185 -0
- rememberstack/surfaces/recipe_surface.py +219 -0
- rememberstack/surfaces/remote_mcp.py +133 -0
- rememberstack/surfaces/sdk.py +324 -0
- rememberstack/workers/__init__.py +155 -0
- rememberstack/workers/base.py +312 -0
- rememberstack/workers/e0.py +577 -0
- rememberstack/workers/e1.py +425 -0
- rememberstack/workers/e2.py +525 -0
- rememberstack/workers/e3.py +434 -0
- rememberstack/workers/forget.py +299 -0
- rememberstack/workers/knowledge_authored.py +146 -0
- rememberstack/workers/knowledge_driver.py +735 -0
- rememberstack/workers/knowledge_fact_sheet.py +123 -0
- rememberstack/workers/knowledge_planner.py +325 -0
- rememberstack/workers/knowledge_writer.py +393 -0
- rememberstack/workers/operations.py +42 -0
- rememberstack/workers/p1.py +234 -0
- rememberstack/workers/p2.py +513 -0
- rememberstack/workers/p2_analytics.py +276 -0
- rememberstack/workers/p3.py +673 -0
- rememberstack/workers/reconcile.py +485 -0
- rememberstack/workers/sync.py +168 -0
- rememberstack-0.1.0.dist-info/METADATA +213 -0
- rememberstack-0.1.0.dist-info/RECORD +186 -0
- rememberstack-0.1.0.dist-info/WHEEL +4 -0
- rememberstack-0.1.0.dist-info/entry_points.txt +2 -0
- rememberstack-0.1.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
"""Straight-line D74 request admission and portable-manifest acceptance."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Protocol
|
|
6
|
+
from uuid import UUID
|
|
7
|
+
|
|
8
|
+
from rememberstack.model import ClaimedWork
|
|
9
|
+
from rememberstack.model import ForgetInProgressError
|
|
10
|
+
from rememberstack.model import ForgetManifest
|
|
11
|
+
from rememberstack.model import ForgetManifestStatus
|
|
12
|
+
from rememberstack.model import ForgetRedactionRequiredError
|
|
13
|
+
from rememberstack.model import NonRetryableHandlerError
|
|
14
|
+
from rememberstack.ports import ForgetManifestPort
|
|
15
|
+
from rememberstack.ports import KGitPurgePort
|
|
16
|
+
from rememberstack.ports import ObjectPurgePort
|
|
17
|
+
from rememberstack.ports import P1PurgePort
|
|
18
|
+
from rememberstack.ports import ProjectionPurgePort
|
|
19
|
+
from rememberstack.ports.cost_meter import CostMeterPort
|
|
20
|
+
from rememberstack.spine import ForgetCatalog
|
|
21
|
+
from rememberstack.workers.base import HandlerOutcome
|
|
22
|
+
from rememberstack.workers.knowledge_driver import KnowledgeCommitDriver
|
|
23
|
+
from rememberstack.workers.p2 import GraphRebuildWorker
|
|
24
|
+
from rememberstack.workers.p3 import CorpusFsBuilder
|
|
25
|
+
from rememberstack.workers.reconcile import DeletionService
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ForgetProjectionRebuilder(Protocol):
|
|
29
|
+
"""Publish clean P2/P3 snapshots from the scrubbed authoritative spine."""
|
|
30
|
+
|
|
31
|
+
def rebuild_without_lineage(self, *, deployment_id: UUID, forget_id: UUID) -> None:
|
|
32
|
+
"""Build and publish both clean projections idempotently."""
|
|
33
|
+
...
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ForgetKnowledgeRebuilder(Protocol):
|
|
37
|
+
"""Recompile affected machine-owned K pages after source removal."""
|
|
38
|
+
|
|
39
|
+
def recompile_without_lineage(
|
|
40
|
+
self, *, deployment_id: UUID, artifact_ids: tuple[UUID, ...]
|
|
41
|
+
) -> None:
|
|
42
|
+
"""Publish sanitized current compiled files before history erasure."""
|
|
43
|
+
...
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class ProjectionPairForgetRebuilder:
|
|
47
|
+
"""Publish clean P2 and P3 through the existing whole-rebuild workers."""
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self, *, graph: GraphRebuildWorker, corpus: CorpusFsBuilder, workdir: Path
|
|
51
|
+
) -> None:
|
|
52
|
+
"""Bind the two production builders and one explicit scratch root."""
|
|
53
|
+
self._graph = graph
|
|
54
|
+
self._corpus = corpus
|
|
55
|
+
self._workdir = workdir
|
|
56
|
+
|
|
57
|
+
def rebuild_without_lineage(self, *, deployment_id: UUID, forget_id: UUID) -> None:
|
|
58
|
+
"""Publish both clean pointers; retries may safely publish a newer pair."""
|
|
59
|
+
self._graph.rebuild(
|
|
60
|
+
deployment_id=deployment_id, workdir=self._workdir / str(forget_id)
|
|
61
|
+
)
|
|
62
|
+
self._corpus.build(deployment_id=deployment_id)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class KnowledgeCycleForgetRebuilder:
|
|
66
|
+
"""Compile the affected stale pages through the existing single K driver."""
|
|
67
|
+
|
|
68
|
+
def __init__(self, *, driver: KnowledgeCommitDriver) -> None:
|
|
69
|
+
"""Bind the production commit cycle after PostgreSQL marks pages stale."""
|
|
70
|
+
self._driver = driver
|
|
71
|
+
|
|
72
|
+
def recompile_without_lineage(
|
|
73
|
+
self, *, deployment_id: UUID, artifact_ids: tuple[UUID, ...]
|
|
74
|
+
) -> None:
|
|
75
|
+
"""Run one K commit only when the manifest names affected artifacts."""
|
|
76
|
+
if artifact_ids:
|
|
77
|
+
self._driver.run_cycle(
|
|
78
|
+
deployment_id=deployment_id, exclusions_by_artifact={}
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class HardForgetService:
|
|
83
|
+
"""Prepare, freeze, durably append, and enqueue one lineage forget."""
|
|
84
|
+
|
|
85
|
+
def __init__(
|
|
86
|
+
self,
|
|
87
|
+
*,
|
|
88
|
+
catalog: ForgetCatalog,
|
|
89
|
+
manifest_store: ForgetManifestPort,
|
|
90
|
+
k_git: KGitPurgePort,
|
|
91
|
+
) -> None:
|
|
92
|
+
"""Bind the local materialization and the two portable boundaries."""
|
|
93
|
+
self._catalog = catalog
|
|
94
|
+
self._manifest_store = manifest_store
|
|
95
|
+
self._k_git = k_git
|
|
96
|
+
|
|
97
|
+
def request(
|
|
98
|
+
self,
|
|
99
|
+
*,
|
|
100
|
+
deployment_id: UUID,
|
|
101
|
+
doc_id: UUID,
|
|
102
|
+
forget_id: UUID,
|
|
103
|
+
requested_at: datetime,
|
|
104
|
+
) -> ForgetManifest:
|
|
105
|
+
"""Accept one request only after its exact manifest is durably appended."""
|
|
106
|
+
existing = self._catalog.record_for_doc(
|
|
107
|
+
deployment_id=deployment_id, doc_id=doc_id
|
|
108
|
+
)
|
|
109
|
+
if existing is None:
|
|
110
|
+
self._require_redacted(deployment_id=deployment_id, doc_id=doc_id)
|
|
111
|
+
progress = self._catalog.prepare(
|
|
112
|
+
deployment_id=deployment_id, doc_id=doc_id, forget_id=forget_id
|
|
113
|
+
)
|
|
114
|
+
if progress.status in (
|
|
115
|
+
ForgetManifestStatus.ACCEPTED,
|
|
116
|
+
ForgetManifestStatus.COMPLETE,
|
|
117
|
+
):
|
|
118
|
+
if progress.manifest is None:
|
|
119
|
+
raise RuntimeError(
|
|
120
|
+
f"accepted forget_id {forget_id} has no local manifest"
|
|
121
|
+
)
|
|
122
|
+
self._manifest_store.append(manifest=progress.manifest)
|
|
123
|
+
return progress.manifest
|
|
124
|
+
manifest = progress.manifest
|
|
125
|
+
if manifest is None:
|
|
126
|
+
if not self._catalog.ordinary_work_is_drained(deployment_id=deployment_id):
|
|
127
|
+
raise ForgetInProgressError(
|
|
128
|
+
f"forget_id {forget_id} is waiting for ordinary work to drain"
|
|
129
|
+
)
|
|
130
|
+
try:
|
|
131
|
+
self._require_redacted(deployment_id=deployment_id, doc_id=doc_id)
|
|
132
|
+
except ForgetRedactionRequiredError:
|
|
133
|
+
self._catalog.cancel_unstored_preparation(
|
|
134
|
+
deployment_id=deployment_id, forget_id=forget_id
|
|
135
|
+
)
|
|
136
|
+
raise
|
|
137
|
+
manifest = self._catalog.inventory_and_store_manifest(
|
|
138
|
+
deployment_id=deployment_id,
|
|
139
|
+
doc_id=doc_id,
|
|
140
|
+
forget_id=forget_id,
|
|
141
|
+
requested_at=requested_at,
|
|
142
|
+
)
|
|
143
|
+
self._manifest_store.append(manifest=manifest)
|
|
144
|
+
self._catalog.accept_and_enqueue(manifest=manifest)
|
|
145
|
+
return manifest
|
|
146
|
+
|
|
147
|
+
def _require_redacted(self, *, deployment_id: UUID, doc_id: UUID) -> None:
|
|
148
|
+
"""Refuse owner-controlled K citations without rewriting their prose."""
|
|
149
|
+
paths = tuple(
|
|
150
|
+
sorted(
|
|
151
|
+
set(
|
|
152
|
+
self._k_git.blocking_redaction_paths(
|
|
153
|
+
deployment_id=deployment_id, doc_id=doc_id
|
|
154
|
+
)
|
|
155
|
+
)
|
|
156
|
+
)
|
|
157
|
+
)
|
|
158
|
+
if paths:
|
|
159
|
+
raise ForgetRedactionRequiredError(paths=paths)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
class HardForgetHandler:
|
|
163
|
+
"""Run one accepted manifest through the single resumable purge sequence."""
|
|
164
|
+
|
|
165
|
+
def __init__(
|
|
166
|
+
self,
|
|
167
|
+
*,
|
|
168
|
+
catalog: ForgetCatalog,
|
|
169
|
+
deletion: DeletionService,
|
|
170
|
+
object_purgers: tuple[ObjectPurgePort, ...],
|
|
171
|
+
p1: P1PurgePort,
|
|
172
|
+
projection_rebuilder: ForgetProjectionRebuilder,
|
|
173
|
+
projection_purger: ProjectionPurgePort,
|
|
174
|
+
knowledge_rebuilder: ForgetKnowledgeRebuilder,
|
|
175
|
+
k_git: KGitPurgePort,
|
|
176
|
+
) -> None:
|
|
177
|
+
"""Bind existing lifecycle/rebuild services and narrow purge capabilities."""
|
|
178
|
+
if not object_purgers:
|
|
179
|
+
raise ValueError("hard-forget requires at least one object-store purger")
|
|
180
|
+
self._catalog = catalog
|
|
181
|
+
self._deletion = deletion
|
|
182
|
+
self._object_purgers = object_purgers
|
|
183
|
+
self._p1 = p1
|
|
184
|
+
self._projection_rebuilder = projection_rebuilder
|
|
185
|
+
self._projection_purger = projection_purger
|
|
186
|
+
self._knowledge_rebuilder = knowledge_rebuilder
|
|
187
|
+
self._k_git = k_git
|
|
188
|
+
|
|
189
|
+
def handle(self, *, work: ClaimedWork, meter: CostMeterPort) -> HandlerOutcome:
|
|
190
|
+
"""Honor every idempotent stage in order and reopen only after verification."""
|
|
191
|
+
del meter
|
|
192
|
+
forget_id = _forget_id(work=work)
|
|
193
|
+
manifest = self._catalog.manifest_for(
|
|
194
|
+
deployment_id=work.deployment_id, forget_id=forget_id
|
|
195
|
+
)
|
|
196
|
+
self.honor(manifest=manifest)
|
|
197
|
+
return HandlerOutcome()
|
|
198
|
+
|
|
199
|
+
def honor(self, *, manifest: ForgetManifest) -> None:
|
|
200
|
+
"""Run the shared purge path used by both the worker and readiness replay."""
|
|
201
|
+
self._deletion.delete_lineage(
|
|
202
|
+
deployment_id=manifest.deployment_id, doc_id=manifest.doc_id
|
|
203
|
+
)
|
|
204
|
+
self._catalog.scrub_postgres(manifest=manifest)
|
|
205
|
+
for purger in self._object_purgers:
|
|
206
|
+
purger.purge_objects(keys=manifest.object_keys, prefixes=())
|
|
207
|
+
self._p1.purge_rows(
|
|
208
|
+
deployment_id=manifest.deployment_id,
|
|
209
|
+
chunk_ids=manifest.chunk_ids,
|
|
210
|
+
claim_ids=manifest.claim_ids,
|
|
211
|
+
fact_ids=manifest.fact_ids,
|
|
212
|
+
entity_ids=manifest.entity_ids,
|
|
213
|
+
)
|
|
214
|
+
self._projection_rebuilder.rebuild_without_lineage(
|
|
215
|
+
deployment_id=manifest.deployment_id, forget_id=manifest.forget_id
|
|
216
|
+
)
|
|
217
|
+
self._projection_purger.purge_projections(
|
|
218
|
+
deployment_id=manifest.deployment_id, prefixes=manifest.projection_prefixes
|
|
219
|
+
)
|
|
220
|
+
self._knowledge_rebuilder.recompile_without_lineage(
|
|
221
|
+
deployment_id=manifest.deployment_id, artifact_ids=manifest.k_artifact_ids
|
|
222
|
+
)
|
|
223
|
+
self._k_git.purge_artifacts(
|
|
224
|
+
deployment_id=manifest.deployment_id,
|
|
225
|
+
forget_id=manifest.forget_id,
|
|
226
|
+
artifact_ids=manifest.k_artifact_ids,
|
|
227
|
+
)
|
|
228
|
+
for purger in self._object_purgers:
|
|
229
|
+
purger.verify_objects_purged(keys=manifest.object_keys, prefixes=())
|
|
230
|
+
self._p1.verify_rows_purged(
|
|
231
|
+
deployment_id=manifest.deployment_id,
|
|
232
|
+
chunk_ids=manifest.chunk_ids,
|
|
233
|
+
claim_ids=manifest.claim_ids,
|
|
234
|
+
fact_ids=manifest.fact_ids,
|
|
235
|
+
entity_ids=manifest.entity_ids,
|
|
236
|
+
)
|
|
237
|
+
self._projection_purger.verify_projections_purged(
|
|
238
|
+
deployment_id=manifest.deployment_id, prefixes=manifest.projection_prefixes
|
|
239
|
+
)
|
|
240
|
+
self._k_git.verify_artifacts_purged(
|
|
241
|
+
deployment_id=manifest.deployment_id,
|
|
242
|
+
forget_id=manifest.forget_id,
|
|
243
|
+
artifact_ids=manifest.k_artifact_ids,
|
|
244
|
+
)
|
|
245
|
+
self._catalog.verify_postgres_scrubbed(manifest=manifest)
|
|
246
|
+
self._catalog.mark_complete(manifest=manifest)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
class HardForgetReadiness:
|
|
250
|
+
"""Re-honor portable intent before one deployment accepts serving traffic."""
|
|
251
|
+
|
|
252
|
+
def __init__(
|
|
253
|
+
self,
|
|
254
|
+
*,
|
|
255
|
+
catalog: ForgetCatalog,
|
|
256
|
+
manifest_store: ForgetManifestPort,
|
|
257
|
+
request_service: HardForgetService,
|
|
258
|
+
handler: HardForgetHandler,
|
|
259
|
+
) -> None:
|
|
260
|
+
"""Bind one source of intent to the same request and purge implementations."""
|
|
261
|
+
self._catalog = catalog
|
|
262
|
+
self._manifest_store = manifest_store
|
|
263
|
+
self._request_service = request_service
|
|
264
|
+
self._handler = handler
|
|
265
|
+
|
|
266
|
+
def ensure_ready(self, *, deployment_id: UUID) -> tuple[UUID, ...]:
|
|
267
|
+
"""Recover preparation, rematerialize, and re-honor every portable manifest."""
|
|
268
|
+
pending = self._catalog.preparing_record(deployment_id=deployment_id)
|
|
269
|
+
if pending is not None:
|
|
270
|
+
self._request_service.request(
|
|
271
|
+
deployment_id=pending.deployment_id,
|
|
272
|
+
doc_id=pending.doc_id,
|
|
273
|
+
forget_id=pending.forget_id,
|
|
274
|
+
requested_at=(
|
|
275
|
+
pending.manifest.requested_at
|
|
276
|
+
if pending.manifest is not None
|
|
277
|
+
else pending.prepared_at
|
|
278
|
+
),
|
|
279
|
+
)
|
|
280
|
+
manifests = self._manifest_store.manifests(deployment_id=deployment_id)
|
|
281
|
+
for manifest in manifests:
|
|
282
|
+
self._catalog.materialize_portable(manifest=manifest)
|
|
283
|
+
self._handler.honor(manifest=manifest)
|
|
284
|
+
return tuple(manifest.forget_id for manifest in manifests)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def _forget_id(*, work: ClaimedWork) -> UUID:
|
|
288
|
+
"""Read the required portable identity from one accepted worker payload."""
|
|
289
|
+
value = (work.payload or {}).get("forget_id")
|
|
290
|
+
if not isinstance(value, str):
|
|
291
|
+
raise NonRetryableHandlerError(
|
|
292
|
+
f"hard-forget work {work.processing_id} carries no forget_id"
|
|
293
|
+
)
|
|
294
|
+
try:
|
|
295
|
+
return UUID(value)
|
|
296
|
+
except ValueError as error:
|
|
297
|
+
raise NonRetryableHandlerError(
|
|
298
|
+
f"hard-forget work {work.processing_id} has invalid forget_id"
|
|
299
|
+
) from error
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"""Authored checkout synchronization and reliable subscription dispatch delivery."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Protocol
|
|
5
|
+
from uuid import UUID
|
|
6
|
+
|
|
7
|
+
from rememberstack.core import knowledge_content_hash
|
|
8
|
+
from rememberstack.core import parse_knowledge_authored_frontmatter
|
|
9
|
+
from rememberstack.model import ClaimedWork
|
|
10
|
+
from rememberstack.model import KnowledgeAuthoredPageSync
|
|
11
|
+
from rememberstack.model import KnowledgeAuthoredSyncResult
|
|
12
|
+
from rememberstack.model import KnowledgeDispatchStatus
|
|
13
|
+
from rememberstack.model import KnowledgePageKind
|
|
14
|
+
from rememberstack.model import KnowledgeWorkflowDelivery
|
|
15
|
+
from rememberstack.model import NonRetryableHandlerError
|
|
16
|
+
from rememberstack.model import PipelineStage
|
|
17
|
+
from rememberstack.model import ProcessingTarget
|
|
18
|
+
from rememberstack.ports.cost_meter import CostMeterPort
|
|
19
|
+
from rememberstack.spine.knowledge import KnowledgeCompilationError
|
|
20
|
+
from rememberstack.spine.knowledge import KnowledgeControlPlane
|
|
21
|
+
from rememberstack.spine.knowledge import KnowledgeDispatchUnavailableError
|
|
22
|
+
from rememberstack.workers.base import HandlerOutcome
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class KnowledgeWorkflowDispatcher(Protocol):
|
|
26
|
+
"""External workflow-delivery port; consumers are idempotent by dispatch ID."""
|
|
27
|
+
|
|
28
|
+
def deliver(self, *, delivery: KnowledgeWorkflowDelivery) -> None:
|
|
29
|
+
"""Deliver one delta-carrying authored/subscription notification."""
|
|
30
|
+
...
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class KnowledgeAuthoredSynchronizer:
|
|
34
|
+
"""Discover authored Markdown in one exact checkout and sync its declarations."""
|
|
35
|
+
|
|
36
|
+
def __init__(self, *, control_plane: KnowledgeControlPlane) -> None:
|
|
37
|
+
"""Bind checkout discovery to the authoritative Plane-K control state."""
|
|
38
|
+
self._control_plane = control_plane
|
|
39
|
+
|
|
40
|
+
def sync_checkout(
|
|
41
|
+
self, *, deployment_id: UUID, worktree: Path, git_revision: str
|
|
42
|
+
) -> KnowledgeAuthoredSyncResult:
|
|
43
|
+
"""Register new Markdown as authored and resync every known authored body."""
|
|
44
|
+
states = self._control_plane.artifact_path_states(deployment_id=deployment_id)
|
|
45
|
+
bodies = {state.git_path: state for state in states}
|
|
46
|
+
curation_paths = {
|
|
47
|
+
state.curation_path for state in states if state.curation_path is not None
|
|
48
|
+
}
|
|
49
|
+
registered: list[UUID] = []
|
|
50
|
+
synced: list[UUID] = []
|
|
51
|
+
linted: list[UUID] = []
|
|
52
|
+
for target in sorted(worktree.rglob("*.md")):
|
|
53
|
+
relative = target.relative_to(worktree)
|
|
54
|
+
if ".git" in relative.parts:
|
|
55
|
+
continue
|
|
56
|
+
_require_safe_authored_target(
|
|
57
|
+
worktree=worktree, target=target, git_path=relative.as_posix()
|
|
58
|
+
)
|
|
59
|
+
git_path = relative.as_posix()
|
|
60
|
+
state = bodies.get(git_path)
|
|
61
|
+
if git_path in curation_paths or (
|
|
62
|
+
state is not None and state.page_kind is KnowledgePageKind.COMPILED
|
|
63
|
+
):
|
|
64
|
+
continue
|
|
65
|
+
markdown = target.read_text(encoding="utf-8")
|
|
66
|
+
result = self._control_plane.sync_authored_page(
|
|
67
|
+
sync=KnowledgeAuthoredPageSync(
|
|
68
|
+
deployment_id=deployment_id,
|
|
69
|
+
git_path=git_path,
|
|
70
|
+
markdown=markdown,
|
|
71
|
+
content_hash=knowledge_content_hash(markdown=markdown),
|
|
72
|
+
git_revision=git_revision,
|
|
73
|
+
declaration=parse_knowledge_authored_frontmatter(markdown=markdown),
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
if result.registered:
|
|
77
|
+
registered.append(result.artifact_id)
|
|
78
|
+
else:
|
|
79
|
+
synced.append(result.artifact_id)
|
|
80
|
+
if result.lint_flagged:
|
|
81
|
+
linted.append(result.artifact_id)
|
|
82
|
+
return KnowledgeAuthoredSyncResult(
|
|
83
|
+
registered_artifact_ids=tuple(registered),
|
|
84
|
+
synced_artifact_ids=tuple(synced),
|
|
85
|
+
lint_flag_artifact_ids=tuple(linted),
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class KnowledgeDispatchHandler:
|
|
90
|
+
"""Deliver one D67 knowledge-dispatch target through the external workflow port."""
|
|
91
|
+
|
|
92
|
+
def __init__(
|
|
93
|
+
self,
|
|
94
|
+
*,
|
|
95
|
+
control_plane: KnowledgeControlPlane,
|
|
96
|
+
dispatcher: KnowledgeWorkflowDispatcher,
|
|
97
|
+
) -> None:
|
|
98
|
+
"""Bind domain dispatch state to one delivery adapter."""
|
|
99
|
+
self._control_plane = control_plane
|
|
100
|
+
self._dispatcher = dispatcher
|
|
101
|
+
|
|
102
|
+
def handle(self, *, work: ClaimedWork, meter: CostMeterPort) -> HandlerOutcome:
|
|
103
|
+
"""Deliver at least once and keep both delivery/mirror failures visible."""
|
|
104
|
+
del meter
|
|
105
|
+
if (
|
|
106
|
+
work.target_kind is not ProcessingTarget.KNOWLEDGE_DISPATCH
|
|
107
|
+
or work.stage is not PipelineStage.DISPATCH_KNOWLEDGE
|
|
108
|
+
):
|
|
109
|
+
raise NonRetryableHandlerError(
|
|
110
|
+
"dispatch handler requires the knowledge_dispatch/dispatch_knowledge route"
|
|
111
|
+
)
|
|
112
|
+
try:
|
|
113
|
+
record = self._control_plane.begin_dispatch(dispatch_id=work.target_id)
|
|
114
|
+
except KnowledgeDispatchUnavailableError as error:
|
|
115
|
+
raise NonRetryableHandlerError(str(error)) from error
|
|
116
|
+
if record.status is KnowledgeDispatchStatus.DONE:
|
|
117
|
+
return HandlerOutcome()
|
|
118
|
+
delivery = KnowledgeWorkflowDelivery(
|
|
119
|
+
dispatch_id=record.dispatch_id,
|
|
120
|
+
workflow_endpoint=record.workflow_endpoint,
|
|
121
|
+
payload=record.payload,
|
|
122
|
+
)
|
|
123
|
+
try:
|
|
124
|
+
self._dispatcher.deliver(delivery=delivery)
|
|
125
|
+
except Exception as delivery_error:
|
|
126
|
+
try:
|
|
127
|
+
self._control_plane.fail_dispatch(dispatch_id=record.dispatch_id)
|
|
128
|
+
except Exception as mirror_error:
|
|
129
|
+
raise ExceptionGroup(
|
|
130
|
+
"workflow delivery and dispatch failure-mirror both failed",
|
|
131
|
+
[delivery_error, mirror_error],
|
|
132
|
+
) from delivery_error
|
|
133
|
+
raise
|
|
134
|
+
self._control_plane.complete_dispatch(dispatch_id=record.dispatch_id)
|
|
135
|
+
return HandlerOutcome()
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _require_safe_authored_target(
|
|
139
|
+
*, worktree: Path, target: Path, git_path: str
|
|
140
|
+
) -> None:
|
|
141
|
+
"""Reject symlinked or resolved Markdown paths outside the exact checkout."""
|
|
142
|
+
root = worktree.resolve()
|
|
143
|
+
if target.is_symlink() or not target.resolve().is_relative_to(root):
|
|
144
|
+
raise KnowledgeCompilationError(
|
|
145
|
+
f"authored page path escapes worktree: {git_path}"
|
|
146
|
+
)
|