alpha-engine-lib 0.35.0__tar.gz → 0.35.1__tar.gz
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.
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/PKG-INFO +1 -1
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/pyproject.toml +1 -1
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib/__init__.py +1 -1
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib/pipeline_status/read.py +16 -2
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib/pipeline_status/registry.py +64 -36
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib.egg-info/PKG-INFO +1 -1
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/tests/test_pipeline_status_read.py +73 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/tests/test_pipeline_status_registry.py +15 -5
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/README.md +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/setup.cfg +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib/agent_schemas.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib/alerts.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib/arcticdb.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib/collector_results.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib/cost.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib/dates.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib/decision_capture.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib/ec2_spot.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib/email_sender.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib/eval_artifacts.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib/logging.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib/model_pricing.yaml +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib/pillars.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib/pipeline_status/__init__.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib/pipeline_status/templates.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib/preflight.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib/rag/__init__.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib/rag/db.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib/rag/embeddings.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib/rag/migrations/0001_content_tsv.sql +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib/rag/rerank.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib/rag/retrieval.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib/rag/schema.sql +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib/reconcile.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib/secrets.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib/sources/__init__.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib/sources/protocols.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib/ssm_dispatcher.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib/ssm_log_capture.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib/telegram.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib/trading_calendar.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib/transparency.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib/transparency_inventory.yaml +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib/universe.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib.egg-info/SOURCES.txt +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib.egg-info/dependency_links.txt +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib.egg-info/requires.txt +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib.egg-info/top_level.txt +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/tests/test_agent_schemas.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/tests/test_alerts.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/tests/test_arcticdb.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/tests/test_collector_results.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/tests/test_cost.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/tests/test_dates.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/tests/test_decision_capture.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/tests/test_ec2_spot.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/tests/test_email_sender.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/tests/test_eval_artifacts.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/tests/test_logging.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/tests/test_pillars.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/tests/test_pipeline_status_templates.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/tests/test_preflight.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/tests/test_rag.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/tests/test_rag_rerank.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/tests/test_rag_retrieval_hybrid.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/tests/test_reconcile.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/tests/test_secrets.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/tests/test_sources_protocols.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/tests/test_ssm_dispatcher.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/tests/test_ssm_log_capture.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/tests/test_telegram.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/tests/test_trading_calendar.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/tests/test_transparency.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/tests/test_universe.py +0 -0
- {alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/tests/test_version_pin.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: alpha-engine-lib
|
|
3
|
-
Version: 0.35.
|
|
3
|
+
Version: 0.35.1
|
|
4
4
|
Summary: Shared utilities for the Alpha Engine modules: preflight, structured logging with secret-redaction, ArcticDB universe access, NYSE-calendar dates + freshness predicates, decision capture, cost telemetry, RAG, agent output schemas, SSM-backed secrets, Telegram alerts + SNS fan-out, EC2 spot-launch resilience, SSM log-capture chokepoint, SSM send-command + poll chokepoint, and Step-Functions execution-state projection. Full surface documented in README.
|
|
5
5
|
Author: Brian McMahon
|
|
6
6
|
License: Proprietary
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "alpha-engine-lib"
|
|
7
|
-
version = "0.35.
|
|
7
|
+
version = "0.35.1"
|
|
8
8
|
description = "Shared utilities for the Alpha Engine modules: preflight, structured logging with secret-redaction, ArcticDB universe access, NYSE-calendar dates + freshness predicates, decision capture, cost telemetry, RAG, agent output schemas, SSM-backed secrets, Telegram alerts + SNS fan-out, EC2 spot-launch resilience, SSM log-capture chokepoint, SSM send-command + poll chokepoint, and Step-Functions execution-state projection. Full surface documented in README."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
# EC2 still runs Python 3.9 on the always-on micro instance (boto3 drops
|
{alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib/pipeline_status/read.py
RENAMED
|
@@ -35,7 +35,7 @@ import logging
|
|
|
35
35
|
from dataclasses import dataclass
|
|
36
36
|
from datetime import datetime, timezone
|
|
37
37
|
from enum import Enum
|
|
38
|
-
from typing import TYPE_CHECKING, Any, Optional
|
|
38
|
+
from typing import TYPE_CHECKING, Annotated, Any, Optional, Union
|
|
39
39
|
|
|
40
40
|
from pydantic import BaseModel, ConfigDict, Field
|
|
41
41
|
|
|
@@ -160,7 +160,21 @@ class TaskRow(BaseModel):
|
|
|
160
160
|
# substrate-only reason). ``None`` here means "state name not in the
|
|
161
161
|
# registry" and is a CI-time bug — the consumer should treat it as a
|
|
162
162
|
# registry-drift signal, not a renderable placeholder.
|
|
163
|
-
|
|
163
|
+
#
|
|
164
|
+
# The Annotated[Union[...], Field(discriminator="kind")] form is the
|
|
165
|
+
# SOTA tagged-union pattern for Pydantic V2: ``model_dump(mode="json")``
|
|
166
|
+
# serializes the ``kind`` field on each variant, and ``model_validate``
|
|
167
|
+
# routes dict input to the right class via that tag. Prior to this
|
|
168
|
+
# tagging, ``archive`` was typed ``Optional[Any]``, so a JSON round-trip
|
|
169
|
+
# left it as a plain dict — page-25's ``isinstance`` checks then
|
|
170
|
+
# misfired and rendered "Registry drift" for every state, even those
|
|
171
|
+
# with valid registry entries.
|
|
172
|
+
archive: Optional[
|
|
173
|
+
Annotated[
|
|
174
|
+
Union[ArchivePageRef, ArtifactReason],
|
|
175
|
+
Field(discriminator="kind"),
|
|
176
|
+
]
|
|
177
|
+
] = None
|
|
164
178
|
failure_cause: Optional[str] = None # populated only when status == FAILED
|
|
165
179
|
|
|
166
180
|
|
{alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib/pipeline_status/registry.py
RENAMED
|
@@ -27,8 +27,9 @@ how the two stay in sync without a runtime coupling.
|
|
|
27
27
|
|
|
28
28
|
from __future__ import annotations
|
|
29
29
|
|
|
30
|
-
from
|
|
31
|
-
|
|
30
|
+
from typing import Annotated, Final, Literal, Optional, Union
|
|
31
|
+
|
|
32
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
32
33
|
|
|
33
34
|
|
|
34
35
|
# ── Substantive-state filtering (§3.2 of the plan doc) ────────────────────
|
|
@@ -100,8 +101,7 @@ PIPELINE_LABELS: Final[dict[str, str]] = {
|
|
|
100
101
|
# ── Artifact registry types ───────────────────────────────────────────────
|
|
101
102
|
|
|
102
103
|
|
|
103
|
-
|
|
104
|
-
class ArchivePageRef:
|
|
104
|
+
class ArchivePageRef(BaseModel):
|
|
105
105
|
"""Deep-link target for a substantive Task state that produces an
|
|
106
106
|
operator-readable artifact.
|
|
107
107
|
|
|
@@ -116,14 +116,19 @@ class ArchivePageRef:
|
|
|
116
116
|
|
|
117
117
|
``artifact_label`` is the human-readable label for the deep-link cell
|
|
118
118
|
on page 25 — e.g. "Morning briefing" rather than the bare page slug.
|
|
119
|
+
|
|
120
|
+
``kind`` is the discriminator field for the tagged-union round-trip
|
|
121
|
+
in :class:`alpha_engine_lib.pipeline_status.read.TaskRow.archive`.
|
|
119
122
|
"""
|
|
120
123
|
|
|
124
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
125
|
+
|
|
126
|
+
kind: Literal["archive_page_ref"] = "archive_page_ref"
|
|
121
127
|
page: str
|
|
122
128
|
artifact_label: str
|
|
123
129
|
|
|
124
130
|
|
|
125
|
-
|
|
126
|
-
class ArtifactReason:
|
|
131
|
+
class ArtifactReason(BaseModel):
|
|
127
132
|
"""Explicit non-generic reason a substantive Task state has no archive
|
|
128
133
|
page deep-link.
|
|
129
134
|
|
|
@@ -131,13 +136,26 @@ class ArtifactReason:
|
|
|
131
136
|
a specific reason ("Substrate refresh; no per-run artifact"), never
|
|
132
137
|
a generic "no artifact" placeholder. The reason text is what the
|
|
133
138
|
page 25 cell renders.
|
|
139
|
+
|
|
140
|
+
``kind`` is the discriminator field for the tagged-union round-trip
|
|
141
|
+
in :class:`alpha_engine_lib.pipeline_status.read.TaskRow.archive`.
|
|
134
142
|
"""
|
|
135
143
|
|
|
144
|
+
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
145
|
+
|
|
146
|
+
kind: Literal["artifact_reason"] = "artifact_reason"
|
|
136
147
|
reason: str
|
|
137
148
|
|
|
138
149
|
|
|
139
|
-
#
|
|
140
|
-
|
|
150
|
+
# Discriminated union for :class:`TaskRow.archive` — Pydantic V2 routes
|
|
151
|
+
# dict input to the right variant via the ``kind`` tag, so
|
|
152
|
+
# ``model_dump(mode="json")`` → ``model_validate`` round-trips reconstruct
|
|
153
|
+
# the typed instance (instead of leaving the dict raw, which the page-25
|
|
154
|
+
# ``isinstance`` checks would mis-classify as registry drift).
|
|
155
|
+
RegistryEntry = Annotated[
|
|
156
|
+
Union[ArchivePageRef, ArtifactReason],
|
|
157
|
+
Field(discriminator="kind"),
|
|
158
|
+
]
|
|
141
159
|
|
|
142
160
|
|
|
143
161
|
# ── The registry ──────────────────────────────────────────────────────────
|
|
@@ -151,19 +169,29 @@ RegistryEntry = "ArchivePageRef | ArtifactReason"
|
|
|
151
169
|
# (Saturday 89 / Weekday 36 / EOD 21 total states; nested Parallel branches
|
|
152
170
|
# walked). Reviewed against ROADMAP L3050 + the post-2026-05-15
|
|
153
171
|
# artifact-archive pages (dashboard #86: pages 16-22).
|
|
154
|
-
STATE_TO_ARCHIVE_PAGE: Final[dict[str,
|
|
155
|
-
# ── Saturday SF (
|
|
172
|
+
STATE_TO_ARCHIVE_PAGE: Final[dict[str, Union[ArchivePageRef, ArtifactReason]]] = {
|
|
173
|
+
# ── Saturday SF (24 substantive Task steps) ──────────────────────────
|
|
156
174
|
"MorningEnrich": ArtifactReason(
|
|
157
|
-
"Daily OHLCV write to predictor/daily_closes/{date}.parquet; "
|
|
175
|
+
reason="Daily OHLCV write to predictor/daily_closes/{date}.parquet; "
|
|
158
176
|
"no per-run rendered artifact — substrate for downstream stages."
|
|
159
177
|
),
|
|
160
178
|
"DataPhase1": ArtifactReason(
|
|
161
|
-
"Bulk weekly write to predictor/price_cache/, archive/macro/, "
|
|
179
|
+
reason="Bulk weekly write to predictor/price_cache/, archive/macro/, "
|
|
162
180
|
"ArcticDB universe library; no per-run rendered artifact — "
|
|
163
181
|
"substrate refresh."
|
|
164
182
|
),
|
|
183
|
+
"Scanner": ArtifactReason(
|
|
184
|
+
reason="Standalone scanner Lambda (ROADMAP L1995 Phase 1-2, "
|
|
185
|
+
"alpha-engine-research #235): writes candidates.json for the "
|
|
186
|
+
"run_date as observe-only output, gated by "
|
|
187
|
+
"$.enable_standalone_scanner. No consumer reads it today (Phase "
|
|
188
|
+
"4 will flip RAG to read it; Phase 5 will flip Research). "
|
|
189
|
+
"Failure is non-blocking — the SF Catch routes forward to "
|
|
190
|
+
"CheckSkipRAGIngestion. Once Phase 4/5 lands, swap this entry "
|
|
191
|
+
"for an ArchivePageRef pointing at the scanner-candidates page."
|
|
192
|
+
),
|
|
165
193
|
"RAGIngestion": ArtifactReason(
|
|
166
|
-
"SEC/8-K/earnings/theses corpus refresh in rag/corpus/; "
|
|
194
|
+
reason="SEC/8-K/earnings/theses corpus refresh in rag/corpus/; "
|
|
167
195
|
"substrate-only — consumed at Research time."
|
|
168
196
|
),
|
|
169
197
|
"RegimeSubstrate": ArchivePageRef(
|
|
@@ -179,7 +207,7 @@ STATE_TO_ARCHIVE_PAGE: Final[dict[str, "ArchivePageRef | ArtifactReason"]] = {
|
|
|
179
207
|
artifact_label="Morning research briefing",
|
|
180
208
|
),
|
|
181
209
|
"DataPhase2": ArtifactReason(
|
|
182
|
-
"Alt-data + fundamentals refresh; substrate-only, no per-run "
|
|
210
|
+
reason="Alt-data + fundamentals refresh; substrate-only, no per-run "
|
|
183
211
|
"rendered artifact."
|
|
184
212
|
),
|
|
185
213
|
"EvalJudgeSubmitFirstSaturday": ArchivePageRef(
|
|
@@ -191,7 +219,7 @@ STATE_TO_ARCHIVE_PAGE: Final[dict[str, "ArchivePageRef | ArtifactReason"]] = {
|
|
|
191
219
|
artifact_label="Eval judge (weekly batch)",
|
|
192
220
|
),
|
|
193
221
|
"EvalJudgePoll": ArtifactReason(
|
|
194
|
-
"Polling state for the EvalJudge batch job; no per-run artifact — "
|
|
222
|
+
reason="Polling state for the EvalJudge batch job; no per-run artifact — "
|
|
195
223
|
"see EvalJudgeProcess for the materialized rubric output."
|
|
196
224
|
),
|
|
197
225
|
"EvalJudgeProcess": ArchivePageRef(
|
|
@@ -203,15 +231,15 @@ STATE_TO_ARCHIVE_PAGE: Final[dict[str, "ArchivePageRef | ArtifactReason"]] = {
|
|
|
203
231
|
artifact_label="Eval 4-week rolling mean",
|
|
204
232
|
),
|
|
205
233
|
"RationaleClustering": ArtifactReason(
|
|
206
|
-
"Rationale cluster artifact written to S3; no dedicated page yet "
|
|
234
|
+
reason="Rationale cluster artifact written to S3; no dedicated page yet "
|
|
207
235
|
"(P3 follow-up — backlog)."
|
|
208
236
|
),
|
|
209
237
|
"ReplayConcordance": ArtifactReason(
|
|
210
|
-
"Concordance metric written to backtest/{date}/; surfaced inline "
|
|
238
|
+
reason="Concordance metric written to backtest/{date}/; surfaced inline "
|
|
211
239
|
"in Backtester evaluator report (page 21)."
|
|
212
240
|
),
|
|
213
241
|
"Counterfactual": ArtifactReason(
|
|
214
|
-
"Counterfactual artifact written to backtest/{date}/; surfaced "
|
|
242
|
+
reason="Counterfactual artifact written to backtest/{date}/; surfaced "
|
|
215
243
|
"inline in Backtester evaluator report (page 21)."
|
|
216
244
|
),
|
|
217
245
|
"PredictorTraining": ArchivePageRef(
|
|
@@ -243,19 +271,19 @@ STATE_TO_ARCHIVE_PAGE: Final[dict[str, "ArchivePageRef | ArtifactReason"]] = {
|
|
|
243
271
|
artifact_label="Weekly substrate health check",
|
|
244
272
|
),
|
|
245
273
|
"NotifyComplete": ArtifactReason(
|
|
246
|
-
"Terminal success SNS publish to alpha-engine-alerts; "
|
|
274
|
+
reason="Terminal success SNS publish to alpha-engine-alerts; "
|
|
247
275
|
"no persisted artifact (the email IS the surface)."
|
|
248
276
|
),
|
|
249
277
|
"NotifyShellRunComplete": ArtifactReason(
|
|
250
|
-
"Friday-PM shell-run dry-pass terminal SNS publish; "
|
|
278
|
+
reason="Friday-PM shell-run dry-pass terminal SNS publish; "
|
|
251
279
|
"no persisted artifact (the email IS the surface)."
|
|
252
280
|
),
|
|
253
281
|
"HandleFailure": ArtifactReason(
|
|
254
|
-
"Terminal failure SNS publish to alpha-engine-alerts; "
|
|
282
|
+
reason="Terminal failure SNS publish to alpha-engine-alerts; "
|
|
255
283
|
"no persisted artifact (the email IS the surface)."
|
|
256
284
|
),
|
|
257
285
|
"PublishResearchFailureImmediate": ArtifactReason(
|
|
258
|
-
"Early-signal SNS publish fired the moment the Research branch "
|
|
286
|
+
reason="Early-signal SNS publish fired the moment the Research branch "
|
|
259
287
|
"fails inside ResearchPredictorParallel — BEFORE the sibling "
|
|
260
288
|
"PredictorTraining branch completes its work and the parallel "
|
|
261
289
|
"aggregation joins. No persisted artifact (the email IS the "
|
|
@@ -264,7 +292,7 @@ STATE_TO_ARCHIVE_PAGE: Final[dict[str, "ArchivePageRef | ArtifactReason"]] = {
|
|
|
264
292
|
"CheckBranchOutcomes."
|
|
265
293
|
),
|
|
266
294
|
"PublishPredictorFailureImmediate": ArtifactReason(
|
|
267
|
-
"Early-signal SNS publish fired the moment the PredictorTraining "
|
|
295
|
+
reason="Early-signal SNS publish fired the moment the PredictorTraining "
|
|
268
296
|
"branch fails inside ResearchPredictorParallel — BEFORE the "
|
|
269
297
|
"sibling Research branch's eval-judge / RollingMean / "
|
|
270
298
|
"Counterfactual chain completes. No persisted artifact (the "
|
|
@@ -278,27 +306,27 @@ STATE_TO_ARCHIVE_PAGE: Final[dict[str, "ArchivePageRef | ArtifactReason"]] = {
|
|
|
278
306
|
artifact_label="Deploy-drift assertions",
|
|
279
307
|
),
|
|
280
308
|
"StartExecutorEC2": ArtifactReason(
|
|
281
|
-
"EC2 startInstances on the trading instance; no artifact — "
|
|
309
|
+
reason="EC2 startInstances on the trading instance; no artifact — "
|
|
282
310
|
"operational only."
|
|
283
311
|
),
|
|
284
312
|
"DescribeInstanceInfo": ArtifactReason(
|
|
285
|
-
"Boot diagnostic call against the trading instance; "
|
|
313
|
+
reason="Boot diagnostic call against the trading instance; "
|
|
286
314
|
"no artifact — operational only."
|
|
287
315
|
),
|
|
288
316
|
"CheckTradingDay": ArtifactReason(
|
|
289
|
-
"NYSE-holiday gate via SSM command; no artifact — gate outcome "
|
|
317
|
+
reason="NYSE-holiday gate via SSM command; no artifact — gate outcome "
|
|
290
318
|
"is encoded in the SF branch taken."
|
|
291
319
|
),
|
|
292
320
|
"NotifyHolidaySkip": ArtifactReason(
|
|
293
|
-
"Holiday-skip SNS publish; no persisted artifact (the email IS "
|
|
321
|
+
reason="Holiday-skip SNS publish; no persisted artifact (the email IS "
|
|
294
322
|
"the surface)."
|
|
295
323
|
),
|
|
296
324
|
"StopExecutorOnHoliday": ArtifactReason(
|
|
297
|
-
"EC2 stopInstances on the trading instance after a holiday-skip; "
|
|
325
|
+
reason="EC2 stopInstances on the trading instance after a holiday-skip; "
|
|
298
326
|
"no artifact — operational only."
|
|
299
327
|
),
|
|
300
328
|
"TradingDayCheckFailed": ArtifactReason(
|
|
301
|
-
"SF Pass state recording a holiday-skip outcome; no artifact."
|
|
329
|
+
reason="SF Pass state recording a holiday-skip outcome; no artifact."
|
|
302
330
|
),
|
|
303
331
|
# MorningEnrich (weekday) — same state name as Saturday; same entry above wins.
|
|
304
332
|
"PredictorInference": ArchivePageRef(
|
|
@@ -306,15 +334,15 @@ STATE_TO_ARCHIVE_PAGE: Final[dict[str, "ArchivePageRef | ArtifactReason"]] = {
|
|
|
306
334
|
artifact_label="Predictor morning briefing",
|
|
307
335
|
),
|
|
308
336
|
"CheckPredictorCoverage": ArtifactReason(
|
|
309
|
-
"Coverage-gate Lambda; outcome encoded in the SF branch taken — "
|
|
337
|
+
reason="Coverage-gate Lambda; outcome encoded in the SF branch taken — "
|
|
310
338
|
"see PredictorHealthCheck for any persisted health JSON."
|
|
311
339
|
),
|
|
312
340
|
"ReinvokePredictor": ArtifactReason(
|
|
313
|
-
"Re-invocation Lambda when CheckPredictorCoverage finds a gap; "
|
|
341
|
+
reason="Re-invocation Lambda when CheckPredictorCoverage finds a gap; "
|
|
314
342
|
"no per-run artifact — replaces the PredictorInference output."
|
|
315
343
|
),
|
|
316
344
|
"RecheckCoverage": ArtifactReason(
|
|
317
|
-
"Second coverage-gate Lambda after ReinvokePredictor; outcome "
|
|
345
|
+
reason="Second coverage-gate Lambda after ReinvokePredictor; outcome "
|
|
318
346
|
"encoded in the SF branch taken."
|
|
319
347
|
),
|
|
320
348
|
"PredictorHealthCheck": ArchivePageRef(
|
|
@@ -331,7 +359,7 @@ STATE_TO_ARCHIVE_PAGE: Final[dict[str, "ArchivePageRef | ArtifactReason"]] = {
|
|
|
331
359
|
),
|
|
332
360
|
# ── EOD SF (5 substantive Task steps) ────────────────────────────────
|
|
333
361
|
"PostMarketData": ArtifactReason(
|
|
334
|
-
"Polygon T+1 daily aggregate write to predictor/daily_closes/; "
|
|
362
|
+
reason="Polygon T+1 daily aggregate write to predictor/daily_closes/; "
|
|
335
363
|
"substrate-only — consumed by EODReconcile."
|
|
336
364
|
),
|
|
337
365
|
"CaptureSnapshot": ArchivePageRef(
|
|
@@ -347,17 +375,17 @@ STATE_TO_ARCHIVE_PAGE: Final[dict[str, "ArchivePageRef | ArtifactReason"]] = {
|
|
|
347
375
|
artifact_label="Daily substrate health check",
|
|
348
376
|
),
|
|
349
377
|
"StopTradingInstance": ArtifactReason(
|
|
350
|
-
"EC2 stopInstances on the trading instance; no artifact — "
|
|
378
|
+
reason="EC2 stopInstances on the trading instance; no artifact — "
|
|
351
379
|
"operational only."
|
|
352
380
|
),
|
|
353
381
|
"ForceStopInstance": ArtifactReason(
|
|
354
|
-
"EC2 stopInstances fallback on a non-graceful EOD; no artifact — "
|
|
382
|
+
reason="EC2 stopInstances fallback on a non-graceful EOD; no artifact — "
|
|
355
383
|
"operational only."
|
|
356
384
|
),
|
|
357
385
|
}
|
|
358
386
|
|
|
359
387
|
|
|
360
|
-
def lookup_registry(state_name: str) -> Optional[
|
|
388
|
+
def lookup_registry(state_name: str) -> Optional[Union[ArchivePageRef, ArtifactReason]]:
|
|
361
389
|
"""Return the registry entry for ``state_name`` (None if absent).
|
|
362
390
|
|
|
363
391
|
``None`` here signals "this state is not in the registry" — distinct
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: alpha-engine-lib
|
|
3
|
-
Version: 0.35.
|
|
3
|
+
Version: 0.35.1
|
|
4
4
|
Summary: Shared utilities for the Alpha Engine modules: preflight, structured logging with secret-redaction, ArcticDB universe access, NYSE-calendar dates + freshness predicates, decision capture, cost telemetry, RAG, agent output schemas, SSM-backed secrets, Telegram alerts + SNS fan-out, EC2 spot-launch resilience, SSM log-capture chokepoint, SSM send-command + poll chokepoint, and Step-Functions execution-state projection. Full surface documented in README.
|
|
5
5
|
Author: Brian McMahon
|
|
6
6
|
License: Proprietary
|
|
@@ -462,3 +462,76 @@ def test_region_from_arn_returns_none_for_malformed():
|
|
|
462
462
|
assert _region_from_arn("not-an-arn") is None
|
|
463
463
|
assert _region_from_arn("arn:aws:states") is None
|
|
464
464
|
assert _region_from_arn("arn:aws:states::123:stateMachine:x") is None
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
# ── Archive-union JSON round-trip (regression for "registry drift" false positive) ──
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
def _entered_and_succeeded(name: str, t0: datetime, t1: datetime) -> list[dict]:
|
|
471
|
+
return [
|
|
472
|
+
_entered(name, t0),
|
|
473
|
+
{
|
|
474
|
+
"type": "TaskStateExited",
|
|
475
|
+
"timestamp": t1,
|
|
476
|
+
"stateExitedEventDetails": {"name": name},
|
|
477
|
+
},
|
|
478
|
+
]
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
def test_task_row_archive_round_trips_through_json_for_archive_page_ref():
|
|
482
|
+
"""The dashboard's st.cache_data wraps read_pipeline_state by doing
|
|
483
|
+
``model_dump(mode="json")`` → cache → ``model_validate(dict)``. Before
|
|
484
|
+
this regression-guard, ``TaskRow.archive`` was typed ``Optional[Any]``,
|
|
485
|
+
so the JSON round-trip flattened ArchivePageRef instances to plain
|
|
486
|
+
dicts; page-25's ``isinstance(archive, ArchivePageRef)`` then misfired
|
|
487
|
+
and rendered ``⚠️ Registry drift`` for every state with a valid
|
|
488
|
+
registry entry. The discriminated-union typing
|
|
489
|
+
(``Annotated[Union[...], Field(discriminator='kind')]``) reconstructs
|
|
490
|
+
the typed instance on validate; this test guards the contract."""
|
|
491
|
+
t0 = datetime(2026, 5, 24, 9, 0, tzinfo=timezone.utc)
|
|
492
|
+
t1 = datetime(2026, 5, 24, 9, 5, tzinfo=timezone.utc)
|
|
493
|
+
client = _make_sfn_mock(
|
|
494
|
+
history_response={"events": _entered_and_succeeded("Research", t0, t1)}
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
run = read_pipeline_state(SATURDAY_ARN, client=client)
|
|
498
|
+
research_task = next(t for t in run.tasks if t.state_name == "Research")
|
|
499
|
+
assert isinstance(research_task.archive, ArchivePageRef)
|
|
500
|
+
|
|
501
|
+
# The actual code path that broke production: JSON round-trip.
|
|
502
|
+
round_tripped = PipelineRun.model_validate(run.model_dump(mode="json"))
|
|
503
|
+
round_tripped_task = next(
|
|
504
|
+
t for t in round_tripped.tasks if t.state_name == "Research"
|
|
505
|
+
)
|
|
506
|
+
assert isinstance(round_tripped_task.archive, ArchivePageRef), (
|
|
507
|
+
"TaskRow.archive must reconstruct as ArchivePageRef on JSON "
|
|
508
|
+
"round-trip — otherwise page 25's isinstance check falls through "
|
|
509
|
+
"to the registry-drift sentinel for every state."
|
|
510
|
+
)
|
|
511
|
+
assert round_tripped_task.archive.page == "17_Research_Briefing_Archive"
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
def test_task_row_archive_round_trips_through_json_for_artifact_reason():
|
|
515
|
+
"""Mirrors the ArchivePageRef round-trip but for the ArtifactReason
|
|
516
|
+
variant (substrate-only states like NotifyComplete + Scanner). Both
|
|
517
|
+
variants must reconstruct correctly on JSON round-trip; the
|
|
518
|
+
discriminated union differentiates them via the ``kind`` field."""
|
|
519
|
+
t0 = datetime(2026, 5, 24, 9, 0, tzinfo=timezone.utc)
|
|
520
|
+
t1 = datetime(2026, 5, 24, 9, 0, 1, tzinfo=timezone.utc)
|
|
521
|
+
client = _make_sfn_mock(
|
|
522
|
+
history_response={"events": _entered_and_succeeded("NotifyComplete", t0, t1)}
|
|
523
|
+
)
|
|
524
|
+
|
|
525
|
+
run = read_pipeline_state(SATURDAY_ARN, client=client)
|
|
526
|
+
notify_task = next(t for t in run.tasks if t.state_name == "NotifyComplete")
|
|
527
|
+
assert isinstance(notify_task.archive, ArtifactReason)
|
|
528
|
+
|
|
529
|
+
round_tripped = PipelineRun.model_validate(run.model_dump(mode="json"))
|
|
530
|
+
round_tripped_task = next(
|
|
531
|
+
t for t in round_tripped.tasks if t.state_name == "NotifyComplete"
|
|
532
|
+
)
|
|
533
|
+
assert isinstance(round_tripped_task.archive, ArtifactReason), (
|
|
534
|
+
"TaskRow.archive must reconstruct as ArtifactReason on JSON "
|
|
535
|
+
"round-trip — same regression class as the ArchivePageRef test."
|
|
536
|
+
)
|
|
537
|
+
assert "Terminal success" in round_tripped_task.archive.reason
|
|
@@ -104,6 +104,7 @@ def test_registry_covers_known_saturday_substantive_states():
|
|
|
104
104
|
required = {
|
|
105
105
|
"MorningEnrich",
|
|
106
106
|
"DataPhase1",
|
|
107
|
+
"Scanner",
|
|
107
108
|
"RAGIngestion",
|
|
108
109
|
"RegimeSubstrate",
|
|
109
110
|
"Research",
|
|
@@ -116,6 +117,7 @@ def test_registry_covers_known_saturday_substantive_states():
|
|
|
116
117
|
"Parity",
|
|
117
118
|
"Evaluator",
|
|
118
119
|
"DriftDetection",
|
|
120
|
+
"SaturdayHealthCheck",
|
|
119
121
|
"WeeklySubstrateHealthCheck",
|
|
120
122
|
"NotifyComplete",
|
|
121
123
|
"HandleFailure",
|
|
@@ -166,15 +168,23 @@ def test_lookup_registry_returns_entry_for_known_state():
|
|
|
166
168
|
|
|
167
169
|
|
|
168
170
|
def test_archive_page_ref_is_frozen():
|
|
169
|
-
"""ArchivePageRef + ArtifactReason are frozen
|
|
170
|
-
is forbidden so consumers can rely on registry stability
|
|
171
|
-
a process lifetime.
|
|
171
|
+
"""ArchivePageRef + ArtifactReason are frozen Pydantic models —
|
|
172
|
+
mutation is forbidden so consumers can rely on registry stability
|
|
173
|
+
across a process lifetime. Pydantic V2 raises ValidationError with
|
|
174
|
+
``type='frozen_instance'`` on attribute set; older stdlib-dataclass
|
|
175
|
+
consumers would have seen TypeError / FrozenInstanceError, both
|
|
176
|
+
accepted here so the test survives the dataclass → BaseModel
|
|
177
|
+
transition without spurious churn."""
|
|
178
|
+
from pydantic import ValidationError
|
|
179
|
+
|
|
172
180
|
ref = registry.ArchivePageRef(page="x", artifact_label="y")
|
|
173
|
-
with pytest.raises((TypeError, AttributeError)):
|
|
181
|
+
with pytest.raises((TypeError, AttributeError, ValidationError)):
|
|
174
182
|
ref.page = "z" # type: ignore[misc]
|
|
175
183
|
|
|
176
184
|
|
|
177
185
|
def test_artifact_reason_is_frozen():
|
|
186
|
+
from pydantic import ValidationError
|
|
187
|
+
|
|
178
188
|
reason = registry.ArtifactReason(reason="x")
|
|
179
|
-
with pytest.raises((TypeError, AttributeError)):
|
|
189
|
+
with pytest.raises((TypeError, AttributeError, ValidationError)):
|
|
180
190
|
reason.reason = "y" # type: ignore[misc]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib/collector_results.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib/decision_capture.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib/pipeline_status/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib/sources/__init__.py
RENAMED
|
File without changes
|
{alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib/sources/protocols.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib/trading_calendar.py
RENAMED
|
File without changes
|
|
File without changes
|
{alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib/transparency_inventory.yaml
RENAMED
|
File without changes
|
|
File without changes
|
{alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib.egg-info/requires.txt
RENAMED
|
File without changes
|
{alpha_engine_lib-0.35.0 → alpha_engine_lib-0.35.1}/src/alpha_engine_lib.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|