1"""Pure selected-study admission of producer-bound request-feed dependencies.
3Only ``read_request_feed_inventory`` reads artifact files. Neither entry point
4loads a plugin, reads feeds, creates a strategy, nor enumerates trial products.
5The optional codegen inventory module supplies the wire-token canonicalizer.
8from __future__
import annotations
12from pathlib
import Path
14from typing
import Any, Callable, Literal, Mapping
16from .error
import HpoError
17from .study_spec
import ParameterSpec
18from .transpile
import REQUEST_FEED_INVENTORY_SCHEMA
24 "primary_chart_timeframes": (
"1",
"3",
"5",
"15",
"30",
"60",
"120",
"240"),
25 "preroll":
"supported",
26 "request_feed_timeframes": (
"1",
"D"),
29RequestInventoryReason = Literal[
37 "producer_unavailable",
39RequestTimeframeCapability = Literal[
40 "selected_window_chart_timeframe",
41 "selected_window_request_feed_timeframe",
62 """An inventory refusal with closed arguments and a bounded diagnostic."""
64 def __init__(self, reason: RequestInventoryReason, message: str) ->
None:
65 super().
__init__(_diagnostic(message))
67 "hpo_walkforward_inventory_invalid",
69 "capability":
"selected_window_request_feed_inventory",
76 """A policy refusal; offending tokens belong in text, not catalog args."""
78 def __init__(self, capability: RequestTimeframeCapability, message: str) ->
None:
79 super().
__init__(_diagnostic(message))
81 "hpo_walkforward_timeframe_unsupported", {
"capability": capability}
152 artifact_dir_or_manifest: str | os.PathLike[str] | Mapping[str, Any],
153) -> Mapping[str, Any]:
154 """Read the manifest block and compare an optional adjacent JSON sidecar.
156 Pass an artifact directory, a manifest path, or an already parsed manifest
157 (the last form performs no I/O). The block is required; a sidecar alone never
158 supplies it. When present, the sidecar must have identical parsed JSON bytes.
159 Duplicate keys at every JSON depth are invalid, as required by
160 E/docker/request_feed_inventory.py:160-221 (report-v2 section 2).
162 A missing block with ``request_identity.request_feed_inventory: null`` is
163 ``producer_unavailable``: the build explicitly lacked the producer. An older
164 identity without the key is ``missing``. A present null block is ``invalid``.
165 Ordinary artifact loading never calls this selected-only helper.
168 if isinstance(artifact_dir_or_manifest, Mapping):
169 return _manifest_inventory(artifact_dir_or_manifest,
"manifest")
170 path = Path(artifact_dir_or_manifest)
171 manifest_path = path /
"manifest.json" if path.is_dir()
else path
172 inventory = _manifest_inventory(_read_json(manifest_path), str(manifest_path))
173 sidecar = manifest_path.parent /
"request_feed_inventory.json"
175 companion = _read_json(sidecar)
176 if _json_bytes(companion) != _json_bytes(inventory):
179 f
"{sidecar} disagrees with {manifest_path}.request_feed_inventory",
281 inventory: Mapping[str, Any] |
None,
284 generated_cpp_sha256: str,
285 chart_timeframe: str,
286 fixed_inputs: Mapping[str, Any],
287 input_domains: Mapping[str, ParameterSpec],
289 """Return every reachable request token, sorted, unique and canonical.
291 Rules from report-v2 section 2 [SOURCE], E/docker/request_feed_inventory.py:
292 :190-221 requires exactly five schema keys and exact token/input/unknown
293 entry shapes, with no extras, missing or duplicate JSON keys (duplicates
294 must be rejected while parsing; ``read_request_feed_inventory`` does so).
295 :258-264 requires lowercase 64-hex digests and an artifact binding. HPO also
296 compares the recorded source digest against supplied generated C++ identity.
297 Null artifact binding is ``unbound``; either differing digest is
298 ``artifact_mismatch``. :239-280 accepts a null primary (chart-unbound),
299 otherwise requires its canonical token to match the supplied canonical chart.
300 An ``unknown`` entry always refuses; empty entries is complete and admitted.
301 Token entries and non-null ``primary_chart_timeframe`` must already equal
302 codegen's ``canonical_wire_timeframe`` result (:99-157). This is stricter than
303 the engine resolver, which accepts any spelling that canonicalizes, and
304 matches codegen's binder ``_is_wire_token`` (canonical == value). Codegen
305 never emits aliases here, so this rule only refuses hand-edited inventories.
306 Input values may use aliases, resolve from effective fixed values or declared
307 defaults, and invalid present values never fall back (:239-280).
309 Study admission additionally checks every choice of each referenced finite
310 ParameterSpec domain, independently, before trials. A swept input takes
311 precedence over any fixed value; no non-enumerable or non-string domain can
312 prove admission. No Cartesian products are constructed. :283-321 checks the
313 primary chart policy first, then resolved feed policy; the engine's own
314 admission remains authoritative. No artifact/feed I/O or library load occurs.
316 Callers must pass the verified SHA-256 of the plugin file that will be loaded
317 as ``plugin_sha256`` and of its generated C++ as ``generated_cpp_sha256``
318 (manifest values verified against the files by the artifact loader).
319 Admission performs no I/O and compares inventory hashes only with these
320 supplied values, so its identity check is only as good as those arguments.
324 from pineforge_codegen.request_feed_inventory
import canonical_wire_timeframe
325 except ImportError
as error:
327 "producer_unavailable",
328 "cannot import pineforge_codegen.request_feed_inventory; "
329 "admission needs pineforge-codegen with request_feed_inventory",
332 chart = canonical_wire_timeframe(chart_timeframe)
333 if chart
not in SUPPORT_POLICY[
"primary_chart_timeframes"]:
335 "selected_window_chart_timeframe",
336 f
"selected-window chart timeframe {chart_timeframe!r} "
337 f
"(canonical {chart!r}) is unsupported",
339 if inventory
is None:
341 "missing",
"selected execution requires request_feed_inventory"
343 checked = _validate_shape(inventory, canonical_wire_timeframe)
344 if checked[
"artifact_sha256"]
is None:
346 "unbound",
"request_feed_inventory.artifact_sha256 is null"
348 for name, expected
in (
349 (
"source_sha256", generated_cpp_sha256),
350 (
"artifact_sha256", plugin_sha256),
352 if checked[name] != expected:
355 f
"request_feed_inventory.{name} {checked[name]!r} differs "
356 f
"from the supplied artifact identity {expected!r}",
358 primary = checked[
"primary_chart_timeframe"]
359 if primary
is not None and primary != chart:
362 f
"request_feed_inventory.primary_chart_timeframe {primary!r} "
363 f
"differs from chart timeframe {chart!r}",
365 tokens: set[str] = set()
366 for position, entry
in enumerate(checked[
"entries"]):
367 if entry[
"kind"] ==
"unknown":
369 "unknown", f
"request_feed_inventory.entries[{position}] is unknown"
371 if entry[
"kind"] ==
"token":
372 tokens.add(entry[
"timeframe"])
374 for value
in _input_values(entry, fixed_inputs, input_domains):
375 token = canonical_wire_timeframe(value)
379 f
"request_feed_inventory.entries[{position}] input "
380 f
"{entry['key']!r} cannot resolve timeframe value {value!r}",
383 unsupported = sorted(tokens - set(SUPPORT_POLICY[
"request_feed_timeframes"]))
386 "selected_window_request_feed_timeframe",
387 f
"selected-window request-feed timeframes {unsupported!r} are unsupported",
389 return tuple(sorted(tokens))
with_failure(self, str|None code, dict[str, Any]|None args, str origin="hpo")
None __init__(self, RequestInventoryReason reason, str message)
None __init__(self, RequestTimeframeCapability capability, str message)
Mapping[str, Any] read_request_feed_inventory(str|os.PathLike[str]|Mapping[str, Any] artifact_dir_or_manifest)
tuple[str,...] admit_request_feed_inventory(Mapping[str, Any]|None inventory, *str plugin_sha256, str generated_cpp_sha256, str chart_timeframe, Mapping[str, Any] fixed_inputs, Mapping[str, ParameterSpec] input_domains)