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,312 @@
|
|
|
1
|
+
"""The handler-registration model and the worker runner (WP-0.3, D12/D52/D67).
|
|
2
|
+
|
|
3
|
+
A stage handler transforms one claimed unit of work and names its chain
|
|
4
|
+
follow-ups; the runner is the single exception boundary (core value 6): a
|
|
5
|
+
handler failure is logged with its full traceback, recorded on the row, and
|
|
6
|
+
either retried with backoff or dead-lettered — it never disappears.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from datetime import UTC
|
|
11
|
+
import logging
|
|
12
|
+
import time
|
|
13
|
+
import traceback
|
|
14
|
+
from typing import Protocol
|
|
15
|
+
from typing import runtime_checkable
|
|
16
|
+
from uuid import UUID
|
|
17
|
+
|
|
18
|
+
from pydantic import BaseModel
|
|
19
|
+
from pydantic import ConfigDict
|
|
20
|
+
|
|
21
|
+
from rememberstack.model import BudgetParked
|
|
22
|
+
from rememberstack.model import ClaimedWork
|
|
23
|
+
from rememberstack.model import EnqueueWork
|
|
24
|
+
from rememberstack.model import HandlerAlreadyRegisteredError
|
|
25
|
+
from rememberstack.model import NonRetryableHandlerError
|
|
26
|
+
from rememberstack.model import PipelineStage
|
|
27
|
+
from rememberstack.model import ProcessingLane
|
|
28
|
+
from rememberstack.model import ProviderCallUsage
|
|
29
|
+
from rememberstack.model import QueueRoute
|
|
30
|
+
from rememberstack.model import RecordCall
|
|
31
|
+
from rememberstack.model import RunResultOutcome
|
|
32
|
+
from rememberstack.model import TelemetryAttribute
|
|
33
|
+
from rememberstack.model import TelemetryEvent
|
|
34
|
+
from rememberstack.model import UnknownStageHandlerError
|
|
35
|
+
from rememberstack.ports.cost_meter import CostMeterPort
|
|
36
|
+
from rememberstack.ports.queue import TaskQueuePort
|
|
37
|
+
from rememberstack.ports.telemetry import TelemetryPort
|
|
38
|
+
from rememberstack.spine.work_ledger import WorkLedger
|
|
39
|
+
|
|
40
|
+
_logger = logging.getLogger(__name__)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class HandlerOutcome(BaseModel):
|
|
44
|
+
"""What a successful handler produced: the chain follow-ups to enqueue."""
|
|
45
|
+
|
|
46
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
47
|
+
|
|
48
|
+
follow_up: tuple[EnqueueWork, ...] = ()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@runtime_checkable
|
|
52
|
+
class StageHandler(Protocol):
|
|
53
|
+
"""One stage's transformation over one claimed unit of work."""
|
|
54
|
+
|
|
55
|
+
def handle(self, *, work: ClaimedWork, meter: CostMeterPort) -> HandlerOutcome:
|
|
56
|
+
"""Process the claimed work and return its chain follow-ups.
|
|
57
|
+
|
|
58
|
+
Raise `NonRetryableHandlerError` for permanent failures (the work
|
|
59
|
+
dead-letters immediately); any other exception is a retryable failure.
|
|
60
|
+
"""
|
|
61
|
+
...
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class _LedgerCostMeter:
|
|
65
|
+
"""Bind provider accounting to one claimed processing attempt."""
|
|
66
|
+
|
|
67
|
+
def __init__(self, *, ledger: WorkLedger, processing_id: UUID) -> None:
|
|
68
|
+
"""Bind every call record to the authoritative running row."""
|
|
69
|
+
self._ledger = ledger
|
|
70
|
+
self._processing_id = processing_id
|
|
71
|
+
|
|
72
|
+
def record(
|
|
73
|
+
self, *, call_key: str, tier: str | None, usage: ProviderCallUsage
|
|
74
|
+
) -> None:
|
|
75
|
+
"""Persist one provider-reported call through the spine attribution path."""
|
|
76
|
+
self._ledger.record_call(
|
|
77
|
+
call=RecordCall(
|
|
78
|
+
processing_id=self._processing_id,
|
|
79
|
+
call_key=call_key,
|
|
80
|
+
model_name=usage.model_name,
|
|
81
|
+
tier=tier,
|
|
82
|
+
tokens_in=usage.tokens_in,
|
|
83
|
+
tokens_out=usage.tokens_out,
|
|
84
|
+
cost_usd=usage.cost_usd,
|
|
85
|
+
latency_ms=usage.latency_ms,
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class RunResult(BaseModel):
|
|
91
|
+
"""What one runner pass did: which row it ran and how the attempt ended."""
|
|
92
|
+
|
|
93
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
94
|
+
|
|
95
|
+
processing_id: UUID | None
|
|
96
|
+
outcome: RunResultOutcome
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class HandlerRegistry:
|
|
100
|
+
"""Exactly one handler per stage; registration conflicts are typed errors."""
|
|
101
|
+
|
|
102
|
+
def __init__(self) -> None:
|
|
103
|
+
"""Start empty; stages register at composition time (profiles own wiring)."""
|
|
104
|
+
self._handlers: dict[PipelineStage, StageHandler] = {}
|
|
105
|
+
|
|
106
|
+
def register(self, *, stage: PipelineStage, handler: StageHandler) -> None:
|
|
107
|
+
"""Bind a handler to a stage; a second binding for the stage is an error."""
|
|
108
|
+
if stage in self._handlers:
|
|
109
|
+
raise HandlerAlreadyRegisteredError(
|
|
110
|
+
f"stage {stage} already has a registered handler"
|
|
111
|
+
)
|
|
112
|
+
self._handlers[stage] = handler
|
|
113
|
+
|
|
114
|
+
def handler_for(self, *, stage: PipelineStage) -> StageHandler:
|
|
115
|
+
"""Return the stage's handler; an unregistered stage is a typed error."""
|
|
116
|
+
handler = self._handlers.get(stage)
|
|
117
|
+
if handler is None:
|
|
118
|
+
raise UnknownStageHandlerError(f"no handler registered for stage {stage}")
|
|
119
|
+
return handler
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class Worker:
|
|
123
|
+
"""Claims due work on one route and runs its handler behind one error boundary."""
|
|
124
|
+
|
|
125
|
+
def __init__(
|
|
126
|
+
self,
|
|
127
|
+
*,
|
|
128
|
+
ledger: WorkLedger,
|
|
129
|
+
registry: HandlerRegistry,
|
|
130
|
+
queue: TaskQueuePort | None = None,
|
|
131
|
+
telemetry: TelemetryPort | None = None,
|
|
132
|
+
) -> None:
|
|
133
|
+
"""Bind the runner to its ledger, handler registry, and optional queue port.
|
|
134
|
+
|
|
135
|
+
When a queue port is provided, scheduled retries are re-announced
|
|
136
|
+
through it (packaging §3: retry paths call the port in both profiles).
|
|
137
|
+
"""
|
|
138
|
+
self._ledger = ledger
|
|
139
|
+
self._registry = registry
|
|
140
|
+
self._queue = queue
|
|
141
|
+
self._telemetry = telemetry
|
|
142
|
+
|
|
143
|
+
def run_one(
|
|
144
|
+
self, *, deployment_id: UUID, stage: PipelineStage, lane: ProcessingLane | None
|
|
145
|
+
) -> RunResult:
|
|
146
|
+
"""Claim and run at most one due unit of work on the route.
|
|
147
|
+
|
|
148
|
+
This is the worker's single catch-to-log boundary (core value 6): the
|
|
149
|
+
full traceback is logged and recorded on the row; a
|
|
150
|
+
`NonRetryableHandlerError` dead-letters immediately, anything else
|
|
151
|
+
retries with backoff until the attempt limit dead-letters it.
|
|
152
|
+
"""
|
|
153
|
+
started_ns = time.monotonic_ns()
|
|
154
|
+
handler = self._registry.handler_for(stage=stage) # before any claim:
|
|
155
|
+
# an unregistered stage must never strand a claimed row as running.
|
|
156
|
+
claimed = self._ledger.claim_one(
|
|
157
|
+
deployment_id=deployment_id, stage=stage, lane=lane
|
|
158
|
+
)
|
|
159
|
+
if claimed is None:
|
|
160
|
+
return RunResult(processing_id=None, outcome=RunResultOutcome.NO_WORK)
|
|
161
|
+
if isinstance(claimed, BudgetParked):
|
|
162
|
+
if self._queue is not None:
|
|
163
|
+
self._queue.announce(
|
|
164
|
+
processing_id=claimed.processing_id,
|
|
165
|
+
route_snapshot=QueueRoute(
|
|
166
|
+
deployment_id=deployment_id, stage=stage, lane=lane
|
|
167
|
+
),
|
|
168
|
+
not_before_snapshot=claimed.resume_at,
|
|
169
|
+
)
|
|
170
|
+
self._export_event(
|
|
171
|
+
event=_worker_event(
|
|
172
|
+
deployment_id=deployment_id,
|
|
173
|
+
processing_id=claimed.processing_id,
|
|
174
|
+
stage=stage,
|
|
175
|
+
lane=lane,
|
|
176
|
+
attempt=None,
|
|
177
|
+
outcome=RunResultOutcome.BUDGET_PARKED,
|
|
178
|
+
started_ns=started_ns,
|
|
179
|
+
)
|
|
180
|
+
)
|
|
181
|
+
return RunResult(
|
|
182
|
+
processing_id=claimed.processing_id,
|
|
183
|
+
outcome=RunResultOutcome.BUDGET_PARKED,
|
|
184
|
+
)
|
|
185
|
+
meter = _LedgerCostMeter(
|
|
186
|
+
ledger=self._ledger, processing_id=claimed.processing_id
|
|
187
|
+
)
|
|
188
|
+
try:
|
|
189
|
+
outcome = handler.handle(work=claimed, meter=meter)
|
|
190
|
+
except NonRetryableHandlerError as exception:
|
|
191
|
+
_logger.exception(
|
|
192
|
+
"non-retryable failure in stage %s for %s",
|
|
193
|
+
claimed.stage,
|
|
194
|
+
claimed.processing_id,
|
|
195
|
+
)
|
|
196
|
+
self._ledger.fail(
|
|
197
|
+
processing_id=claimed.processing_id,
|
|
198
|
+
error=traceback.format_exc(),
|
|
199
|
+
retryable=False,
|
|
200
|
+
)
|
|
201
|
+
self._export_exception(
|
|
202
|
+
event=_worker_event(
|
|
203
|
+
deployment_id=claimed.deployment_id,
|
|
204
|
+
processing_id=claimed.processing_id,
|
|
205
|
+
stage=claimed.stage,
|
|
206
|
+
lane=claimed.lane,
|
|
207
|
+
attempt=claimed.attempt,
|
|
208
|
+
outcome=RunResultOutcome.DEAD_LETTERED,
|
|
209
|
+
started_ns=started_ns,
|
|
210
|
+
),
|
|
211
|
+
exception=exception,
|
|
212
|
+
)
|
|
213
|
+
return RunResult(
|
|
214
|
+
processing_id=claimed.processing_id,
|
|
215
|
+
outcome=RunResultOutcome.DEAD_LETTERED,
|
|
216
|
+
)
|
|
217
|
+
except Exception as exception:
|
|
218
|
+
_logger.exception(
|
|
219
|
+
"retryable failure in stage %s for %s",
|
|
220
|
+
claimed.stage,
|
|
221
|
+
claimed.processing_id,
|
|
222
|
+
)
|
|
223
|
+
retry_at = self._ledger.fail(
|
|
224
|
+
processing_id=claimed.processing_id,
|
|
225
|
+
error=traceback.format_exc(),
|
|
226
|
+
retryable=True,
|
|
227
|
+
)
|
|
228
|
+
result_outcome = (
|
|
229
|
+
RunResultOutcome.RETRY_SCHEDULED
|
|
230
|
+
if retry_at is not None
|
|
231
|
+
else RunResultOutcome.DEAD_LETTERED
|
|
232
|
+
)
|
|
233
|
+
if retry_at is not None and self._queue is not None:
|
|
234
|
+
self._queue.announce(
|
|
235
|
+
processing_id=claimed.processing_id,
|
|
236
|
+
route_snapshot=QueueRoute(
|
|
237
|
+
deployment_id=deployment_id, stage=stage, lane=lane
|
|
238
|
+
),
|
|
239
|
+
not_before_snapshot=retry_at,
|
|
240
|
+
)
|
|
241
|
+
self._export_exception(
|
|
242
|
+
event=_worker_event(
|
|
243
|
+
deployment_id=claimed.deployment_id,
|
|
244
|
+
processing_id=claimed.processing_id,
|
|
245
|
+
stage=claimed.stage,
|
|
246
|
+
lane=claimed.lane,
|
|
247
|
+
attempt=claimed.attempt,
|
|
248
|
+
outcome=result_outcome,
|
|
249
|
+
started_ns=started_ns,
|
|
250
|
+
),
|
|
251
|
+
exception=exception,
|
|
252
|
+
)
|
|
253
|
+
return RunResult(
|
|
254
|
+
processing_id=claimed.processing_id, outcome=result_outcome
|
|
255
|
+
)
|
|
256
|
+
self._ledger.complete(
|
|
257
|
+
processing_id=claimed.processing_id, follow_up=outcome.follow_up
|
|
258
|
+
)
|
|
259
|
+
self._export_event(
|
|
260
|
+
event=_worker_event(
|
|
261
|
+
deployment_id=claimed.deployment_id,
|
|
262
|
+
processing_id=claimed.processing_id,
|
|
263
|
+
stage=claimed.stage,
|
|
264
|
+
lane=claimed.lane,
|
|
265
|
+
attempt=claimed.attempt,
|
|
266
|
+
outcome=RunResultOutcome.SUCCEEDED,
|
|
267
|
+
started_ns=started_ns,
|
|
268
|
+
)
|
|
269
|
+
)
|
|
270
|
+
return RunResult(
|
|
271
|
+
processing_id=claimed.processing_id, outcome=RunResultOutcome.SUCCEEDED
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
def _export_event(self, *, event: TelemetryEvent) -> None:
|
|
275
|
+
"""Export one completed transition when telemetry is configured."""
|
|
276
|
+
if self._telemetry is not None:
|
|
277
|
+
self._telemetry.export_event(event=event)
|
|
278
|
+
|
|
279
|
+
def _export_exception(
|
|
280
|
+
self, *, event: TelemetryEvent, exception: BaseException
|
|
281
|
+
) -> None:
|
|
282
|
+
"""Export the original exception object without hiding exporter failure."""
|
|
283
|
+
if self._telemetry is not None:
|
|
284
|
+
self._telemetry.export_exception(event=event, exception=exception)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def _worker_event(
|
|
288
|
+
*,
|
|
289
|
+
deployment_id: UUID,
|
|
290
|
+
processing_id: UUID,
|
|
291
|
+
stage: PipelineStage,
|
|
292
|
+
lane: ProcessingLane | None,
|
|
293
|
+
attempt: int | None,
|
|
294
|
+
outcome: RunResultOutcome,
|
|
295
|
+
started_ns: int,
|
|
296
|
+
) -> TelemetryEvent:
|
|
297
|
+
"""Build the one stable provider-neutral worker event vocabulary."""
|
|
298
|
+
return TelemetryEvent(
|
|
299
|
+
name="worker.run",
|
|
300
|
+
occurred_at=datetime.now(UTC),
|
|
301
|
+
attributes=(
|
|
302
|
+
TelemetryAttribute(name="deployment_id", value=str(deployment_id)),
|
|
303
|
+
TelemetryAttribute(name="processing_id", value=str(processing_id)),
|
|
304
|
+
TelemetryAttribute(name="stage", value=stage.value),
|
|
305
|
+
TelemetryAttribute(name="lane", value=None if lane is None else lane.value),
|
|
306
|
+
TelemetryAttribute(name="attempt", value=attempt),
|
|
307
|
+
TelemetryAttribute(name="outcome", value=outcome.value),
|
|
308
|
+
TelemetryAttribute(
|
|
309
|
+
name="duration_ms", value=(time.monotonic_ns() - started_ns) / 1e6
|
|
310
|
+
),
|
|
311
|
+
),
|
|
312
|
+
)
|