sourcecode 2.3.0__py3-none-any.whl → 2.5.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.
@@ -0,0 +1,135 @@
1
+ """IntentResolver — bind a request's raw anchors to STRUCTURED graph identities.
2
+
3
+ Single responsibility (design §4.2): name/scope → structured identity. It plans
4
+ nothing and executes nothing. Resolution uses structured indexes (`types_by_simple`,
5
+ module ids, integration scheme vocabulary) — NEVER fuzzy text search. An anchor that
6
+ cannot be bound is marked `unresolved`; the resolver never guesses.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import dataclass
11
+ from typing import TYPE_CHECKING, Callable
12
+
13
+ from sourcecode.retrieval.errors import UnknownIntentError
14
+ from sourcecode.retrieval.request import RetrievalAnchor, RetrievalIntent, RetrievalRequest
15
+
16
+ if TYPE_CHECKING: # avoid importing the knowledge layer at module load
17
+ from sourcecode.retrieval.context import KnowledgeContext
18
+
19
+
20
+ @dataclass(frozen=True)
21
+ class ResolvedAnchor:
22
+ """A raw anchor bound to zero or more structured identities. Ambiguity is kept
23
+ (multiple identities), never silently collapsed."""
24
+
25
+ anchor: RetrievalAnchor
26
+ identities: tuple[str, ...]
27
+ unresolved_reason: str | None = None
28
+
29
+ @property
30
+ def resolved(self) -> bool:
31
+ return self.unresolved_reason is None and bool(self.identities)
32
+
33
+
34
+ @dataclass(frozen=True)
35
+ class ResolvedIntent:
36
+ """The intent plus its bound anchors, ready for planning."""
37
+
38
+ intent: RetrievalIntent
39
+ anchors: tuple[ResolvedAnchor, ...] = ()
40
+
41
+ @property
42
+ def fully_resolved(self) -> bool:
43
+ return all(a.resolved for a in self.anchors)
44
+
45
+
46
+ class IntentResolver:
47
+ """Resolves a `RetrievalRequest` into a `ResolvedIntent`.
48
+
49
+ Per-intent strategies live in a registry so adding an intent adds a strategy
50
+ (often the trivial no-anchor one) without touching the resolver's core.
51
+ """
52
+
53
+ def __init__(self) -> None:
54
+ self._strategies: dict[
55
+ RetrievalIntent, Callable[[RetrievalRequest, "KnowledgeContext"], ResolvedIntent]
56
+ ] = {
57
+ # The archetype-dimension intents characterize the WHOLE repository — no
58
+ # anchor to bind. Both reuse the same (trivial) resolution strategy.
59
+ RetrievalIntent.ARCHITECTURAL_CORE: self._resolve_no_anchors,
60
+ RetrievalIntent.ARCHITECTURAL_STYLE: self._resolve_no_anchors,
61
+ RetrievalIntent.DEPLOYMENT_SHAPE: self._resolve_no_anchors,
62
+ RetrievalIntent.PRIMARY_INTERFACE: self._resolve_no_anchors,
63
+ # IMPACT pack: the blast engine does its own (legacy-identical) resolution of
64
+ # the target token, so anchors are passed through structurally (no text search,
65
+ # no premature ContextGraph build). ResolveTarget picks the target at runtime.
66
+ RetrievalIntent.BLAST_RADIUS: self._resolve_passthrough,
67
+ RetrievalIntent.AFFECTED_ENDPOINTS: self._resolve_passthrough,
68
+ RetrievalIntent.AFFECTED_INTEGRATIONS: self._resolve_passthrough,
69
+ RetrievalIntent.TRANSACTION_BOUNDARIES: self._resolve_passthrough,
70
+ RetrievalIntent.CROSS_MODULE_IMPACT: self._resolve_passthrough,
71
+ # TRANSACTIONS & SECURITY: whole-repo questions need no anchor; SECURITY_CHAIN
72
+ # scopes to a target symbol (passed through, resolved by the security engine).
73
+ RetrievalIntent.TRANSACTION_FLOW: self._resolve_no_anchors,
74
+ RetrievalIntent.TRANSACTION_ENTRYPOINTS: self._resolve_no_anchors,
75
+ RetrievalIntent.TRANSACTION_PROPAGATION: self._resolve_no_anchors,
76
+ RetrievalIntent.SECURITY_SURFACE: self._resolve_no_anchors,
77
+ RetrievalIntent.SECURITY_CHAIN: self._resolve_passthrough,
78
+ RetrievalIntent.VALIDATION_POINTS: self._resolve_no_anchors,
79
+ # INTERFACES & INTEGRATIONS: whole-repo inventories, no anchor to bind.
80
+ RetrievalIntent.ENDPOINT_INVENTORY: self._resolve_no_anchors,
81
+ RetrievalIntent.INTEGRATION_INVENTORY: self._resolve_no_anchors,
82
+ RetrievalIntent.EVENT_TOPOLOGY: self._resolve_no_anchors,
83
+ # SUBSYSTEMS & DEPENDENCIES: inventory is whole-repo; the scoped intents pass
84
+ # their subsystem/module/package anchor through (matched structurally by the
85
+ # projection step against the partition / graph node ids).
86
+ RetrievalIntent.SUBSYSTEM_INVENTORY: self._resolve_no_anchors,
87
+ RetrievalIntent.SUBSYSTEM_MEMBERS: self._resolve_passthrough,
88
+ RetrievalIntent.MODULE_DEPENDENTS: self._resolve_passthrough,
89
+ RetrievalIntent.MODULE_DEPENDENCIES: self._resolve_passthrough,
90
+ RetrievalIntent.TYPE_DEPENDENTS: self._resolve_passthrough,
91
+ # ENDPOINT & CALL deep-dive: all anchored (endpoint / path / symbol), passed
92
+ # through and matched structurally by the projection step.
93
+ RetrievalIntent.ENDPOINT_CONTRACT: self._resolve_passthrough,
94
+ RetrievalIntent.ENDPOINT_SECURITY: self._resolve_passthrough,
95
+ RetrievalIntent.CALL_NEIGHBORHOOD: self._resolve_passthrough,
96
+ # GRAPH-analysis: the two path/reachability intents pass their symbol through;
97
+ # subsystem-coupling is whole-repo (no anchor).
98
+ RetrievalIntent.EXECUTION_PATHS_THROUGH: self._resolve_passthrough,
99
+ RetrievalIntent.TRANSACTIONS_REACHING: self._resolve_passthrough,
100
+ RetrievalIntent.SUBSYSTEM_COUPLING: self._resolve_no_anchors,
101
+ }
102
+
103
+ def resolve(self, request: RetrievalRequest, ctx: "KnowledgeContext") -> ResolvedIntent:
104
+ strategy = self._strategies.get(request.intent)
105
+ if strategy is None:
106
+ raise UnknownIntentError(f"no resolver strategy for intent {request.intent!r}")
107
+ return strategy(request, ctx)
108
+
109
+ # ── strategies ─────────────────────────────────────────────────────────────
110
+ @staticmethod
111
+ def _resolve_no_anchors(
112
+ request: RetrievalRequest, ctx: "KnowledgeContext"
113
+ ) -> ResolvedIntent:
114
+ return ResolvedIntent(request.intent, ())
115
+
116
+ @staticmethod
117
+ def _resolve_passthrough(
118
+ request: RetrievalRequest, ctx: "KnowledgeContext"
119
+ ) -> ResolvedIntent:
120
+ """Bind each anchor to its own raw token as the identity (structural passthrough).
121
+ No fuzzy search, no guessing; the downstream engine resolves the token."""
122
+ return ResolvedIntent(
123
+ request.intent,
124
+ tuple(ResolvedAnchor(a, (a.raw,)) for a in request.anchors),
125
+ )
126
+
127
+ # ── structured binding helpers (for future anchored intents) ────────────────
128
+ # Kept here to fix the ONE sanctioned resolution mechanism: a structured index
129
+ # lookup, never a text scan. Not exercised by the first (anchor-free) intent.
130
+ @staticmethod
131
+ def _bind_symbol(anchor: RetrievalAnchor, ctx: "KnowledgeContext") -> ResolvedAnchor:
132
+ fqns = tuple(sorted(ctx.services.types_by_simple().get(anchor.raw, ())))
133
+ if not fqns:
134
+ return ResolvedAnchor(anchor, (), unresolved_reason=f"unknown symbol {anchor.raw!r}")
135
+ return ResolvedAnchor(anchor, fqns)
@@ -0,0 +1,144 @@
1
+ """KnowledgeResult and its typed parts (design §4.6, §5.3).
2
+
3
+ Everything the engine returns is **structured** — no prose, no generated natural
4
+ language. The one free-text field, `Explanation.engine_explanation`, is passed through
5
+ VERBATIM from the reused engine; the retriever synthesizes no sentences of its own.
6
+ Every object is JSON-serializable via `to_dict()`.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import dataclass, field
11
+ from typing import Any
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class RetrievalEvidence:
16
+ """Structured evidence for a result, mirroring the ADR-0003 evidence contract
17
+ (signal / weight / strength / mass / contribution). It CAN carry a
18
+ ``source_file:line`` grounding when the backing fact is span-anchored; for the
19
+ archetype signals it is ``None`` (they are mass-derived, not span-anchored)."""
20
+
21
+ signal: str
22
+ detail: str
23
+ weight: float
24
+ strength: float
25
+ mass: float
26
+ contribution: float
27
+ provider: str
28
+ source_file: str | None = None
29
+ source_line: int | None = None
30
+
31
+ def to_dict(self) -> dict[str, Any]:
32
+ return {
33
+ "signal": self.signal,
34
+ "detail": self.detail,
35
+ "weight": self.weight,
36
+ "strength": round(self.strength, 3),
37
+ "mass": round(self.mass, 4),
38
+ "contribution": round(self.contribution, 4),
39
+ "provider": self.provider,
40
+ "source_file": self.source_file,
41
+ "source_line": self.source_line,
42
+ }
43
+
44
+
45
+ @dataclass(frozen=True)
46
+ class KnowledgeEntity:
47
+ """One structured hit. `identity` is a graph identity (label / FQN / id), never
48
+ prose; `attributes` carries structured extras."""
49
+
50
+ identity: str
51
+ kind: str
52
+ score: float
53
+ attributes: dict[str, Any] = field(default_factory=dict)
54
+
55
+ def to_dict(self) -> dict[str, Any]:
56
+ return {
57
+ "identity": self.identity,
58
+ "kind": self.kind,
59
+ "score": round(self.score, 4),
60
+ "attributes": self.attributes,
61
+ }
62
+
63
+
64
+ @dataclass(frozen=True)
65
+ class Observation:
66
+ """A structured, named measurement reused from a Knowledge-Layer provider
67
+ (e.g. a module-graph topology metric). Not a hit; supporting context."""
68
+
69
+ name: str
70
+ value: Any
71
+ detail: str = ""
72
+
73
+ def to_dict(self) -> dict[str, Any]:
74
+ return {"name": self.name, "value": self.value, "detail": self.detail}
75
+
76
+
77
+ @dataclass(frozen=True)
78
+ class Provenance:
79
+ """Which existing capabilities produced this result, and what knowledge state
80
+ backed it. Makes every answer auditable back to the reused engines."""
81
+
82
+ services: tuple[str, ...]
83
+ signals_used: tuple[str, ...]
84
+ signals_missing: tuple[str, ...]
85
+ knowledge_source: str
86
+
87
+ def to_dict(self) -> dict[str, Any]:
88
+ return {
89
+ "services": list(self.services),
90
+ "signals_used": list(self.signals_used),
91
+ "signals_missing": list(self.signals_missing),
92
+ "knowledge_source": self.knowledge_source,
93
+ }
94
+
95
+
96
+ @dataclass(frozen=True)
97
+ class Explanation:
98
+ """Structured, machine-readable rationale. `engine_explanation` is verbatim from
99
+ the reused engine — the retriever generates NO natural language of its own."""
100
+
101
+ winner: str | None
102
+ score: float
103
+ confidence: str
104
+ top_contributions: tuple[tuple[str, float], ...]
105
+ runner_up: str | None
106
+ engine_explanation: str
107
+
108
+ def to_dict(self) -> dict[str, Any]:
109
+ return {
110
+ "winner": self.winner,
111
+ "score": round(self.score, 4),
112
+ "confidence": self.confidence,
113
+ "top_contributions": [
114
+ {"signal": s, "contribution": round(c, 4)} for s, c in self.top_contributions
115
+ ],
116
+ "runner_up": self.runner_up,
117
+ "engine_explanation": self.engine_explanation,
118
+ }
119
+
120
+
121
+ @dataclass(frozen=True)
122
+ class KnowledgeResult:
123
+ """The typed answer to a `RetrievalRequest`. Fully structured; no prose."""
124
+
125
+ intent: str
126
+ entities: tuple[KnowledgeEntity, ...]
127
+ evidence: tuple[RetrievalEvidence, ...]
128
+ confidence: str
129
+ observations: tuple[Observation, ...]
130
+ explanation: Explanation
131
+ provenance: Provenance
132
+ query: tuple[str, ...] # executed plan, as step names (traceability)
133
+
134
+ def to_dict(self) -> dict[str, Any]:
135
+ return {
136
+ "intent": self.intent,
137
+ "entities": [e.to_dict() for e in self.entities],
138
+ "evidence": [e.to_dict() for e in self.evidence],
139
+ "confidence": self.confidence,
140
+ "observations": [o.to_dict() for o in self.observations],
141
+ "explanation": self.explanation.to_dict(),
142
+ "provenance": self.provenance.to_dict(),
143
+ "query": list(self.query),
144
+ }
@@ -0,0 +1,62 @@
1
+ """SemanticRetriever — the façade / orchestrator (design §4.1).
2
+
3
+ The single public entry point. It wires `IntentResolver → QueryPlanner →
4
+ KnowledgeExecutor` over a `KnowledgeContext` and returns a typed `KnowledgeResult`. It
5
+ performs NO graph logic itself — it is composition only, so consumers depend on one
6
+ stable surface and never on the internal stages.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ from pathlib import Path
11
+
12
+ from sourcecode.retrieval.context import KnowledgeContext
13
+ from sourcecode.retrieval.executor import KnowledgeExecutor
14
+ from sourcecode.retrieval.planner import QueryPlanner
15
+ from sourcecode.retrieval.query import KnowledgeQuery
16
+ from sourcecode.retrieval.request import RetrievalRequest
17
+ from sourcecode.retrieval.resolution import IntentResolver
18
+ from sourcecode.retrieval.result import KnowledgeResult
19
+ from sourcecode.retrieval.runtime import RuntimeResult
20
+
21
+
22
+ class SemanticRetriever:
23
+ """Orchestrates the retrieval pipeline over a bundle of already-built knowledge."""
24
+
25
+ def __init__(
26
+ self,
27
+ context: KnowledgeContext,
28
+ *,
29
+ resolver: IntentResolver | None = None,
30
+ planner: QueryPlanner | None = None,
31
+ executor: KnowledgeExecutor | None = None,
32
+ ) -> None:
33
+ self._ctx = context
34
+ self._resolver = resolver or IntentResolver()
35
+ self._planner = planner or QueryPlanner()
36
+ self._executor = executor or KnowledgeExecutor()
37
+
38
+ @classmethod
39
+ def for_repo(cls, root: Path) -> "SemanticRetriever":
40
+ """Convenience constructor: acquire existing knowledge for `root` (the
41
+ acquisition boundary in `KnowledgeContext.for_repo`) and wire the pipeline."""
42
+ return cls(KnowledgeContext.for_repo(Path(root)))
43
+
44
+ @property
45
+ def context(self) -> KnowledgeContext:
46
+ return self._ctx
47
+
48
+ def plan(self, request: RetrievalRequest) -> KnowledgeQuery:
49
+ """Resolve + plan WITHOUT executing — for explaining "which plan would run"."""
50
+ resolved = self._resolver.resolve(request, self._ctx)
51
+ return self._planner.plan(resolved)
52
+
53
+ def retrieve(self, request: RetrievalRequest) -> KnowledgeResult:
54
+ """Run the full pipeline and return the typed result."""
55
+ return self.run(request).result
56
+
57
+ def run(self, request: RetrievalRequest) -> RuntimeResult:
58
+ """Run the full pipeline and return the typed result plus the execution report
59
+ (per-step trace + profiling + Knowledge-Cache status)."""
60
+ resolved = self._resolver.resolve(request, self._ctx)
61
+ query = self._planner.plan(resolved)
62
+ return self._executor.run(query, self._ctx, resolved, request.options)
@@ -0,0 +1,259 @@
1
+ """Query Plan Runtime — the engine that executes ANY plan of registered Steps.
2
+
3
+ This is the definitive Semantic Retrieval infrastructure. It is **generic**: it knows how
4
+ to sequence steps, thread a shared execution context, pass results between steps, time and
5
+ trace each step, and surface errors — and it knows **nothing** about any specific intent.
6
+ All execution logic lives in registered step **handlers** (`steps.py`); intents are
7
+ declarative plans (`planner.py`). Consequently:
8
+
9
+ Adding a new RetrievalIntent = declaring a new QueryPlan. The Runtime is NOT modified.
10
+
11
+ Responsibilities delivered here (design §4.5, Fase 4):
12
+ * sequential execution of a plan's steps;
13
+ * a shared `StepContext` (knowledge handles + a blackboard for inter-step results);
14
+ * result passing between steps via the blackboard;
15
+ * error handling (typed, with the failing step captured);
16
+ * traceability (an ordered `StepTrace` per step);
17
+ * profiling (per-step + total wall time);
18
+ * Knowledge-Cache reuse (the single `KnowledgeContext` is shared across every step, so
19
+ the cached CIR / SourceMap is acquired at most once — no new cache is introduced).
20
+ """
21
+ from __future__ import annotations
22
+
23
+ from dataclasses import dataclass, field
24
+ from time import perf_counter
25
+ from typing import Any, Callable, Optional, TYPE_CHECKING
26
+
27
+ from sourcecode.retrieval.context import KnowledgeContext
28
+ from sourcecode.retrieval.errors import (
29
+ IncompletePlanError,
30
+ RetrievalError,
31
+ StepContractError,
32
+ StepExecutionError,
33
+ UnknownStepError,
34
+ )
35
+ from sourcecode.retrieval.query import KnowledgeQuery, Step
36
+ from sourcecode.retrieval.request import RetrievalIntent, RetrievalOptions
37
+
38
+ if TYPE_CHECKING:
39
+ from sourcecode.retrieval.resolution import ResolvedAnchor, ResolvedIntent
40
+ from sourcecode.retrieval.result import KnowledgeResult
41
+
42
+ # A step handler is a pure procedure over (step, context). It reads/writes the shared
43
+ # blackboard and (for the terminal step) sets the context result. It returns nothing.
44
+ StepHandler = Callable[[Step, "StepContext"], None]
45
+
46
+
47
+ # ── shared execution context ─────────────────────────────────────────────────────
48
+ class StepContext:
49
+ """The execution scope threaded across every step of one plan run.
50
+
51
+ * `knowledge` — read-only handles to already-built knowledge (shared → cache reuse).
52
+ * `anchors` — the request's resolved anchors, so anchored intents reach their target.
53
+ * blackboard — a keyed store steps use to pass intermediate results to later steps.
54
+ * `result` — the terminal `KnowledgeResult`, set by the BuildResult step.
55
+ * `trace` — accumulated per-step traces.
56
+ """
57
+
58
+ def __init__(
59
+ self,
60
+ knowledge: KnowledgeContext,
61
+ intent: RetrievalIntent,
62
+ resolved: "Optional[ResolvedIntent]" = None,
63
+ options: "Optional[RetrievalOptions]" = None,
64
+ ) -> None:
65
+ self.knowledge = knowledge
66
+ self.intent = intent
67
+ self._resolved = resolved
68
+ self.options = options or RetrievalOptions()
69
+ self._slots: dict[str, Any] = {}
70
+ self._result: "Optional[KnowledgeResult]" = None
71
+ self.trace: list["StepTrace"] = []
72
+
73
+ @property
74
+ def anchors(self) -> "tuple[ResolvedAnchor, ...]":
75
+ """The request's resolved anchors (empty for anchor-free intents)."""
76
+ return self._resolved.anchors if self._resolved is not None else ()
77
+
78
+ # blackboard ------------------------------------------------------------------
79
+ def put(self, key: str, value: Any) -> None:
80
+ self._slots[key] = value
81
+
82
+ def get(self, key: str, default: Any = None) -> Any:
83
+ return self._slots.get(key, default)
84
+
85
+ def has(self, key: str) -> bool:
86
+ return key in self._slots
87
+
88
+ def require(self, key: str) -> Any:
89
+ """Fetch a slot a prior step must have produced; a missing slot is a plan
90
+ ordering fault, not a runtime bug."""
91
+ if key not in self._slots:
92
+ raise StepContractError(f"step requires slot {key!r} which no prior step produced")
93
+ return self._slots[key]
94
+
95
+ # terminal result -------------------------------------------------------------
96
+ def set_result(self, result: "KnowledgeResult") -> None:
97
+ self._result = result
98
+
99
+ @property
100
+ def result(self) -> "Optional[KnowledgeResult]":
101
+ return self._result
102
+
103
+
104
+ # ── trace / profiling ────────────────────────────────────────────────────────────
105
+ @dataclass(frozen=True)
106
+ class StepTrace:
107
+ """One executed step's record: identity, params, status, wall time, error (if any)."""
108
+
109
+ index: int
110
+ kind: str
111
+ step: str # Python class name
112
+ params: dict[str, Any]
113
+ status: str # "ok" | "error"
114
+ duration_ms: float
115
+ error: Optional[str] = None
116
+
117
+ def to_dict(self) -> dict[str, Any]:
118
+ return {
119
+ "index": self.index,
120
+ "kind": self.kind,
121
+ "step": self.step,
122
+ "params": self.params,
123
+ "status": self.status,
124
+ "duration_ms": round(self.duration_ms, 3),
125
+ "error": self.error,
126
+ }
127
+
128
+
129
+ @dataclass(frozen=True)
130
+ class RuntimeReport:
131
+ """Inspectable execution report: the plan, per-step traces, totals, cache status, and
132
+ the knowledge-acquisition timings (B2)."""
133
+
134
+ intent: str
135
+ plan: tuple[str, ...]
136
+ steps: tuple[StepTrace, ...]
137
+ total_ms: float
138
+ knowledge_source: str # Knowledge-Cache status (HIT/MISS/disabled/not-acquired)
139
+ acquisitions: tuple[tuple[str, float], ...] = () # (name, ms) lazy acquisitions
140
+
141
+ def to_dict(self) -> dict[str, Any]:
142
+ return {
143
+ "intent": self.intent,
144
+ "plan": list(self.plan),
145
+ "steps": [s.to_dict() for s in self.steps],
146
+ "total_ms": round(self.total_ms, 3),
147
+ "knowledge_source": self.knowledge_source,
148
+ "acquisitions": [
149
+ {"name": n, "ms": round(ms, 3)} for n, ms in self.acquisitions
150
+ ],
151
+ "acquisition_ms": round(sum(ms for _, ms in self.acquisitions), 3),
152
+ }
153
+
154
+
155
+ @dataclass(frozen=True)
156
+ class RuntimeResult:
157
+ """A run's output: the typed result plus the execution report."""
158
+
159
+ result: "KnowledgeResult"
160
+ report: RuntimeReport
161
+
162
+
163
+ # ── step registry ────────────────────────────────────────────────────────────────
164
+ class StepRegistry:
165
+ """Maps a step `kind` to its handler. The ONLY place execution logic is bound to a
166
+ step. New capabilities register a handler here; plans then reference the kind."""
167
+
168
+ def __init__(self) -> None:
169
+ self._handlers: dict[str, StepHandler] = {}
170
+
171
+ def register(self, kind: str, handler: StepHandler) -> None:
172
+ if kind in self._handlers:
173
+ raise ValueError(f"step kind {kind!r} already registered")
174
+ self._handlers[kind] = handler
175
+
176
+ def handler_for(self, step: Step) -> StepHandler:
177
+ handler = self._handlers.get(step.kind)
178
+ if handler is None:
179
+ raise UnknownStepError(f"no handler registered for step kind {step.kind!r}")
180
+ return handler
181
+
182
+ def kinds(self) -> frozenset[str]:
183
+ return frozenset(self._handlers)
184
+
185
+
186
+ # ── the runtime ──────────────────────────────────────────────────────────────────
187
+ class QueryPlanRuntime:
188
+ """Executes any `KnowledgeQuery` composed of registered steps."""
189
+
190
+ def __init__(self, registry: Optional[StepRegistry] = None) -> None:
191
+ if registry is None:
192
+ # Lazy import to avoid a cycle (steps import the result/context types).
193
+ from sourcecode.retrieval.steps import default_registry
194
+
195
+ registry = default_registry()
196
+ self._registry = registry
197
+
198
+ @property
199
+ def registry(self) -> StepRegistry:
200
+ return self._registry
201
+
202
+ def run(
203
+ self,
204
+ query: KnowledgeQuery,
205
+ knowledge: KnowledgeContext,
206
+ resolved: "Optional[ResolvedIntent]" = None,
207
+ options: "Optional[RetrievalOptions]" = None,
208
+ ) -> RuntimeResult:
209
+ # `resolved` carries the request's bound anchors so anchored intents (the whole
210
+ # IMPACT pack) can reach their target; `options` carries the request's presentation
211
+ # knobs (e.g. `limit`, applied by the terminal BuildResult). Both are additive and
212
+ # optional — anchor-free / default-option requests pass None and behave exactly as
213
+ # before. Neither changes execution semantics (sequence/trace/profiling).
214
+ ctx = StepContext(knowledge, query.intent, resolved, options)
215
+ t0 = perf_counter()
216
+ for index, step in enumerate(query.steps):
217
+ handler = self._registry.handler_for(step) # UnknownStepError propagates (plan fault)
218
+ s0 = perf_counter()
219
+ try:
220
+ handler(step, ctx)
221
+ except RetrievalError as exc: # typed fault (e.g. bad slot/dimension) — keep as-is
222
+ self._record(ctx, index, step, s0, status="error", error=repr(exc))
223
+ raise
224
+ except Exception as exc: # unexpected fault — wrap, but preserve the trace
225
+ self._record(ctx, index, step, s0, status="error", error=repr(exc))
226
+ raise StepExecutionError(
227
+ f"step {type(step).__name__} ({step.kind}) failed: {exc!r}"
228
+ ) from exc
229
+ self._record(ctx, index, step, s0, status="ok", error=None)
230
+ total_ms = (perf_counter() - t0) * 1000.0
231
+
232
+ if ctx.result is None:
233
+ raise IncompletePlanError("plan finished without producing a result (no BuildResult step)")
234
+
235
+ report = RuntimeReport(
236
+ intent=query.intent.value,
237
+ plan=query.describe(),
238
+ steps=tuple(ctx.trace),
239
+ total_ms=total_ms,
240
+ knowledge_source=knowledge.cache_render,
241
+ acquisitions=knowledge.acquisitions, # B2: intent-agnostic infra timings
242
+ )
243
+ return RuntimeResult(result=ctx.result, report=report)
244
+
245
+ @staticmethod
246
+ def _record(
247
+ ctx: StepContext, index: int, step: Step, s0: float, *, status: str, error: Optional[str]
248
+ ) -> None:
249
+ ctx.trace.append(
250
+ StepTrace(
251
+ index=index,
252
+ kind=step.kind,
253
+ step=type(step).__name__,
254
+ params=step.params(),
255
+ status=status,
256
+ duration_ms=(perf_counter() - s0) * 1000.0,
257
+ error=error,
258
+ )
259
+ )