alpha-engine-lib 0.34.1__tar.gz → 0.35.0__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.
Files changed (75) hide show
  1. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/PKG-INFO +35 -6
  2. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/README.md +33 -4
  3. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/pyproject.toml +2 -2
  4. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/src/alpha_engine_lib/__init__.py +1 -1
  5. alpha_engine_lib-0.35.0/src/alpha_engine_lib/ssm_dispatcher.py +463 -0
  6. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/src/alpha_engine_lib.egg-info/PKG-INFO +35 -6
  7. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/src/alpha_engine_lib.egg-info/SOURCES.txt +2 -0
  8. alpha_engine_lib-0.35.0/tests/test_ssm_dispatcher.py +656 -0
  9. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/setup.cfg +0 -0
  10. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/src/alpha_engine_lib/agent_schemas.py +0 -0
  11. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/src/alpha_engine_lib/alerts.py +0 -0
  12. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/src/alpha_engine_lib/arcticdb.py +0 -0
  13. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/src/alpha_engine_lib/collector_results.py +0 -0
  14. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/src/alpha_engine_lib/cost.py +0 -0
  15. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/src/alpha_engine_lib/dates.py +0 -0
  16. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/src/alpha_engine_lib/decision_capture.py +0 -0
  17. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/src/alpha_engine_lib/ec2_spot.py +0 -0
  18. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/src/alpha_engine_lib/email_sender.py +0 -0
  19. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/src/alpha_engine_lib/eval_artifacts.py +0 -0
  20. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/src/alpha_engine_lib/logging.py +0 -0
  21. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/src/alpha_engine_lib/model_pricing.yaml +0 -0
  22. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/src/alpha_engine_lib/pillars.py +0 -0
  23. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/src/alpha_engine_lib/pipeline_status/__init__.py +0 -0
  24. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/src/alpha_engine_lib/pipeline_status/read.py +0 -0
  25. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/src/alpha_engine_lib/pipeline_status/registry.py +0 -0
  26. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/src/alpha_engine_lib/pipeline_status/templates.py +0 -0
  27. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/src/alpha_engine_lib/preflight.py +0 -0
  28. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/src/alpha_engine_lib/rag/__init__.py +0 -0
  29. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/src/alpha_engine_lib/rag/db.py +0 -0
  30. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/src/alpha_engine_lib/rag/embeddings.py +0 -0
  31. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/src/alpha_engine_lib/rag/migrations/0001_content_tsv.sql +0 -0
  32. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/src/alpha_engine_lib/rag/rerank.py +0 -0
  33. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/src/alpha_engine_lib/rag/retrieval.py +0 -0
  34. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/src/alpha_engine_lib/rag/schema.sql +0 -0
  35. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/src/alpha_engine_lib/reconcile.py +0 -0
  36. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/src/alpha_engine_lib/secrets.py +0 -0
  37. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/src/alpha_engine_lib/sources/__init__.py +0 -0
  38. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/src/alpha_engine_lib/sources/protocols.py +0 -0
  39. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/src/alpha_engine_lib/ssm_log_capture.py +0 -0
  40. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/src/alpha_engine_lib/telegram.py +0 -0
  41. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/src/alpha_engine_lib/trading_calendar.py +0 -0
  42. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/src/alpha_engine_lib/transparency.py +0 -0
  43. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/src/alpha_engine_lib/transparency_inventory.yaml +0 -0
  44. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/src/alpha_engine_lib/universe.py +0 -0
  45. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/src/alpha_engine_lib.egg-info/dependency_links.txt +0 -0
  46. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/src/alpha_engine_lib.egg-info/requires.txt +0 -0
  47. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/src/alpha_engine_lib.egg-info/top_level.txt +0 -0
  48. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/tests/test_agent_schemas.py +0 -0
  49. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/tests/test_alerts.py +0 -0
  50. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/tests/test_arcticdb.py +0 -0
  51. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/tests/test_collector_results.py +0 -0
  52. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/tests/test_cost.py +0 -0
  53. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/tests/test_dates.py +0 -0
  54. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/tests/test_decision_capture.py +0 -0
  55. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/tests/test_ec2_spot.py +0 -0
  56. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/tests/test_email_sender.py +0 -0
  57. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/tests/test_eval_artifacts.py +0 -0
  58. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/tests/test_logging.py +0 -0
  59. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/tests/test_pillars.py +0 -0
  60. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/tests/test_pipeline_status_read.py +0 -0
  61. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/tests/test_pipeline_status_registry.py +0 -0
  62. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/tests/test_pipeline_status_templates.py +0 -0
  63. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/tests/test_preflight.py +0 -0
  64. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/tests/test_rag.py +0 -0
  65. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/tests/test_rag_rerank.py +0 -0
  66. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/tests/test_rag_retrieval_hybrid.py +0 -0
  67. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/tests/test_reconcile.py +0 -0
  68. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/tests/test_secrets.py +0 -0
  69. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/tests/test_sources_protocols.py +0 -0
  70. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/tests/test_ssm_log_capture.py +0 -0
  71. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/tests/test_telegram.py +0 -0
  72. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/tests/test_trading_calendar.py +0 -0
  73. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/tests/test_transparency.py +0 -0
  74. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/tests/test_universe.py +0 -0
  75. {alpha_engine_lib-0.34.1 → alpha_engine_lib-0.35.0}/tests/test_version_pin.py +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: alpha-engine-lib
3
- Version: 0.34.1
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, and Step-Functions execution-state projection. Full surface documented in README.
3
+ Version: 0.35.0
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
7
7
  Requires-Python: >=3.9
@@ -181,18 +181,47 @@ results = retrieve(
181
181
 
182
182
  Requires the `[rag]` extra. Embeddings are Voyage `voyage-3-lite` (512d); the database backend is Neon Postgres with pgvector + HNSW indexes.
183
183
 
184
+ ### `ssm_dispatcher` — SSM send-command + poll chokepoint
185
+
186
+ Canonical Python primitive for the `run_ssm` bash helper that previously appeared as a ~54-line mirror across each dispatcher script that drives a spot instance over the SSM transport. The pre-lift shape — base64-wrap the script body, `aws ssm send-command --document-name AWS-RunShellScript`, loop on `get-command-invocation`, stream the `StandardOutputContent` delta, propagate the inner exit — now lives in one place where the polling cadence, error-class handling, and S3 output-key layout match across every consumer.
187
+
188
+ ```bash
189
+ python -m alpha_engine_lib.ssm_dispatcher run \
190
+ --instance-id "$INSTANCE_ID" \
191
+ --description "bootstrap" \
192
+ --timeout 3600 \
193
+ --output-bucket "$S3_BUCKET" \
194
+ --output-key-prefix "${S3_STAGING_PREFIX}/ssm-output" \
195
+ --region "$AWS_REGION" \
196
+ --script-stdin <<'BOOTSTRAP'
197
+ set -eo pipefail
198
+ export HOME=/home/ec2-user AWS_REGION=us-east-1
199
+ # ...the script body the SSM target will execute...
200
+ BOOTSTRAP
201
+ ```
202
+
203
+ Exit 0 on `Success`; exit 1 on any terminal non-Success status, send-command failure, or unrecoverable poll failure; exit 2 on bad CLI input. `InvocationDoesNotExist` during the first 60s after SendCommand counts as a registration race and keeps polling — closes the 2026-05-23 Saturday SF substrate weakness at the chokepoint rather than per-SF-JSON Retry block.
204
+
205
+ ### `ssm_log_capture` — SSM-step log capture + S3 ship-on-exit chokepoint
206
+
207
+ Pairs with `ssm_dispatcher` on the SSM target side. The dispatcher script tells the target instance to invoke `python -m alpha_engine_lib.ssm_log_capture run --slug X --log /var/log/X.log -- bash <launcher>`; the target wrapper tees the launcher's stdout/stderr to a local log file and to its own stdout (so the SSM `StandardOutputContent` channel still surfaces output to the dispatcher), then on exit ships the full local log to `s3://alpha-engine-research/_ssm_logs/{slug}/{date}/{host}-{time}.log` regardless of the inner exit code. Replaces the inline `trap 'aws s3 cp ...' EXIT` pattern that broke under ASL `States.Array` escape semantics (2026-05-22 Friday-PM dry-pass catch).
208
+
209
+ ### `ec2_spot` — capacity-resilient spot-launch chokepoint
210
+
211
+ Rotates across `(instance_type × subnet)` combinations on `InsufficientInstanceCapacity` / `InsufficientHostCapacity` / `Unsupported` / `InvalidAvailabilityZone` / `SpotMaxPriceTooLow`; non-capacity errors raise immediately. CLI exit 64 distinguishes capacity exhaustion from generic failure. Replaces the hardcoded single-subnet + single-instance-type launch pattern that mirrored across each dispatcher; landed 2026-05-22 after the third-recurrence-in-a-month spot-launch fragility.
212
+
184
213
  ## How it's used
185
214
 
186
215
  All six Nous Ergon module repos depend on this lib:
187
216
 
188
217
  | Module | Repo | What it imports from here |
189
218
  |---|---|---|
190
- | Data | [`alpha-engine-data`](https://github.com/cipher813/alpha-engine-data) | `logging`, `preflight`, `arcticdb`, `dates`, `trading_calendar`, `rag` (ingestion) |
219
+ | Data | [`alpha-engine-data`](https://github.com/cipher813/alpha-engine-data) | `logging`, `preflight`, `arcticdb`, `dates`, `trading_calendar`, `rag` (ingestion), `ec2_spot` + `ssm_log_capture` + `ssm_dispatcher` (spot launchers) |
191
220
  | Research | [`alpha-engine-research`](https://github.com/cipher813/alpha-engine-research) | `logging`, `decision_capture`, `cost`, `dates`, `rag` (retrieval), `agent_schemas` (canonical LLM-output contracts) |
192
- | Predictor | [`alpha-engine-predictor`](https://github.com/cipher813/alpha-engine-predictor) | `logging`, `preflight`, `arcticdb`, `dates` |
221
+ | Predictor | [`alpha-engine-predictor`](https://github.com/cipher813/alpha-engine-predictor) | `logging`, `preflight`, `arcticdb`, `dates`, `ec2_spot` + `ssm_log_capture` + `ssm_dispatcher` (spot launcher) |
193
222
  | Executor | [`alpha-engine`](https://github.com/cipher813/alpha-engine) | `logging`, `preflight`, `arcticdb`, `dates`, `trading_calendar` |
194
- | Backtester | [`alpha-engine-backtester`](https://github.com/cipher813/alpha-engine-backtester) | `logging`, `preflight`, `arcticdb`, `dates`, `agent_schemas` (replay-harness Pydantic validation) |
195
- | Dashboard | [`alpha-engine-dashboard`](https://github.com/cipher813/alpha-engine-dashboard) | `logging`, `arcticdb`, `dates` |
223
+ | Backtester | [`alpha-engine-backtester`](https://github.com/cipher813/alpha-engine-backtester) | `logging`, `preflight`, `arcticdb`, `dates`, `agent_schemas` (replay-harness Pydantic validation), `ec2_spot` + `ssm_log_capture` + `ssm_dispatcher` (spot launcher) |
224
+ | Dashboard | [`alpha-engine-dashboard`](https://github.com/cipher813/alpha-engine-dashboard) | `logging`, `arcticdb`, `dates`, hosts the SSM-target `.venv` that `ssm_dispatcher` invokes via `python -m` |
196
225
 
197
226
  ## Development
198
227
 
@@ -152,18 +152,47 @@ results = retrieve(
152
152
 
153
153
  Requires the `[rag]` extra. Embeddings are Voyage `voyage-3-lite` (512d); the database backend is Neon Postgres with pgvector + HNSW indexes.
154
154
 
155
+ ### `ssm_dispatcher` — SSM send-command + poll chokepoint
156
+
157
+ Canonical Python primitive for the `run_ssm` bash helper that previously appeared as a ~54-line mirror across each dispatcher script that drives a spot instance over the SSM transport. The pre-lift shape — base64-wrap the script body, `aws ssm send-command --document-name AWS-RunShellScript`, loop on `get-command-invocation`, stream the `StandardOutputContent` delta, propagate the inner exit — now lives in one place where the polling cadence, error-class handling, and S3 output-key layout match across every consumer.
158
+
159
+ ```bash
160
+ python -m alpha_engine_lib.ssm_dispatcher run \
161
+ --instance-id "$INSTANCE_ID" \
162
+ --description "bootstrap" \
163
+ --timeout 3600 \
164
+ --output-bucket "$S3_BUCKET" \
165
+ --output-key-prefix "${S3_STAGING_PREFIX}/ssm-output" \
166
+ --region "$AWS_REGION" \
167
+ --script-stdin <<'BOOTSTRAP'
168
+ set -eo pipefail
169
+ export HOME=/home/ec2-user AWS_REGION=us-east-1
170
+ # ...the script body the SSM target will execute...
171
+ BOOTSTRAP
172
+ ```
173
+
174
+ Exit 0 on `Success`; exit 1 on any terminal non-Success status, send-command failure, or unrecoverable poll failure; exit 2 on bad CLI input. `InvocationDoesNotExist` during the first 60s after SendCommand counts as a registration race and keeps polling — closes the 2026-05-23 Saturday SF substrate weakness at the chokepoint rather than per-SF-JSON Retry block.
175
+
176
+ ### `ssm_log_capture` — SSM-step log capture + S3 ship-on-exit chokepoint
177
+
178
+ Pairs with `ssm_dispatcher` on the SSM target side. The dispatcher script tells the target instance to invoke `python -m alpha_engine_lib.ssm_log_capture run --slug X --log /var/log/X.log -- bash <launcher>`; the target wrapper tees the launcher's stdout/stderr to a local log file and to its own stdout (so the SSM `StandardOutputContent` channel still surfaces output to the dispatcher), then on exit ships the full local log to `s3://alpha-engine-research/_ssm_logs/{slug}/{date}/{host}-{time}.log` regardless of the inner exit code. Replaces the inline `trap 'aws s3 cp ...' EXIT` pattern that broke under ASL `States.Array` escape semantics (2026-05-22 Friday-PM dry-pass catch).
179
+
180
+ ### `ec2_spot` — capacity-resilient spot-launch chokepoint
181
+
182
+ Rotates across `(instance_type × subnet)` combinations on `InsufficientInstanceCapacity` / `InsufficientHostCapacity` / `Unsupported` / `InvalidAvailabilityZone` / `SpotMaxPriceTooLow`; non-capacity errors raise immediately. CLI exit 64 distinguishes capacity exhaustion from generic failure. Replaces the hardcoded single-subnet + single-instance-type launch pattern that mirrored across each dispatcher; landed 2026-05-22 after the third-recurrence-in-a-month spot-launch fragility.
183
+
155
184
  ## How it's used
156
185
 
157
186
  All six Nous Ergon module repos depend on this lib:
158
187
 
159
188
  | Module | Repo | What it imports from here |
160
189
  |---|---|---|
161
- | Data | [`alpha-engine-data`](https://github.com/cipher813/alpha-engine-data) | `logging`, `preflight`, `arcticdb`, `dates`, `trading_calendar`, `rag` (ingestion) |
190
+ | Data | [`alpha-engine-data`](https://github.com/cipher813/alpha-engine-data) | `logging`, `preflight`, `arcticdb`, `dates`, `trading_calendar`, `rag` (ingestion), `ec2_spot` + `ssm_log_capture` + `ssm_dispatcher` (spot launchers) |
162
191
  | Research | [`alpha-engine-research`](https://github.com/cipher813/alpha-engine-research) | `logging`, `decision_capture`, `cost`, `dates`, `rag` (retrieval), `agent_schemas` (canonical LLM-output contracts) |
163
- | Predictor | [`alpha-engine-predictor`](https://github.com/cipher813/alpha-engine-predictor) | `logging`, `preflight`, `arcticdb`, `dates` |
192
+ | Predictor | [`alpha-engine-predictor`](https://github.com/cipher813/alpha-engine-predictor) | `logging`, `preflight`, `arcticdb`, `dates`, `ec2_spot` + `ssm_log_capture` + `ssm_dispatcher` (spot launcher) |
164
193
  | Executor | [`alpha-engine`](https://github.com/cipher813/alpha-engine) | `logging`, `preflight`, `arcticdb`, `dates`, `trading_calendar` |
165
- | Backtester | [`alpha-engine-backtester`](https://github.com/cipher813/alpha-engine-backtester) | `logging`, `preflight`, `arcticdb`, `dates`, `agent_schemas` (replay-harness Pydantic validation) |
166
- | Dashboard | [`alpha-engine-dashboard`](https://github.com/cipher813/alpha-engine-dashboard) | `logging`, `arcticdb`, `dates` |
194
+ | Backtester | [`alpha-engine-backtester`](https://github.com/cipher813/alpha-engine-backtester) | `logging`, `preflight`, `arcticdb`, `dates`, `agent_schemas` (replay-harness Pydantic validation), `ec2_spot` + `ssm_log_capture` + `ssm_dispatcher` (spot launcher) |
195
+ | Dashboard | [`alpha-engine-dashboard`](https://github.com/cipher813/alpha-engine-dashboard) | `logging`, `arcticdb`, `dates`, hosts the SSM-target `.venv` that `ssm_dispatcher` invokes via `python -m` |
167
196
 
168
197
  ## Development
169
198
 
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "alpha-engine-lib"
7
- version = "0.34.1"
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, and Step-Functions execution-state projection. Full surface documented in README."
7
+ version = "0.35.0"
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
11
11
  # 3.9 support 2026-04-29, so upgrade is on the near-term roadmap). All
@@ -1,3 +1,3 @@
1
1
  """alpha-engine-lib — shared utilities for Alpha Engine modules."""
2
2
 
3
- __version__ = "0.34.1"
3
+ __version__ = "0.35.0"
@@ -0,0 +1,463 @@
1
+ """
2
+ SSM send-command + poll-for-completion chokepoint.
3
+
4
+ Consolidation substrate for the ``run_ssm`` bash helper that previously
5
+ appeared as a ~54-line mirror in every dispatcher script that drives a
6
+ spot instance over the SSM transport. The first occurrence shipped in
7
+ alpha-engine-predictor #168 (2026-05-15) as part of the SSH/SCP→SSM
8
+ migration; the second and third occurrences land when alpha-engine-data's
9
+ ``spot_data_weekly.sh`` and alpha-engine-backtester's ``spot_backtest.sh``
10
+ migrate off SSH+SCP onto the same SSM transport. Per
11
+ ``~/Development/CLAUDE.md`` SOTA sub-sub-rule + the
12
+ ``[[feedback_lift_invariants_to_chokepoint_after_second_recurrence]]``
13
+ discipline, the pattern lifts to lib at the second recurrence.
14
+
15
+ The pre-lift bash shape was::
16
+
17
+ run_ssm "<description>" "<bash script>" [timeout_seconds]
18
+ # 1. base64-encode the script body (transport-safe wrapping of inner
19
+ # heredocs / quoting)
20
+ # 2. aws ssm send-command --document-name AWS-RunShellScript \
21
+ # --instance-ids "$INSTANCE_ID" \
22
+ # --output-s3-bucket-name "$S3_BUCKET" \
23
+ # --output-s3-key-prefix "${S3_STAGING_PREFIX}/ssm-output" \
24
+ # --timeout-seconds "$timeout_s" \
25
+ # --parameters file://$pfile
26
+ # 3. while :; do
27
+ # aws ssm get-command-invocation --command-id $cmd_id
28
+ # stream stdout delta; check Status; break on terminal
29
+ # done
30
+ # 4. on Success → return 0
31
+ # 5. on Failed/TimedOut/Cancelled → fetch stderr, print, return 1
32
+
33
+ The Python primitive in this module exposes the same contract — base64
34
+ wrap, send, poll, stream, propagate exit — but lives in one place so
35
+ the polling cadence, error-class handling, and S3 output-key layout
36
+ match across every consumer.
37
+
38
+ **Why a CLI, not a bash function:**
39
+
40
+ Per the SOTA / institutional-approach sub-sub-rule ("when mirroring a
41
+ pattern across repos, consider lifting it into ``alpha-engine-lib``...
42
+ Pure-Bash primitives can stay mirrored unless re-expressible as a
43
+ Python CLI entry callable from Bash, in which case the CLI re-expression
44
+ is the institutional path"). The dispatcher script invokes::
45
+
46
+ python -m alpha_engine_lib.ssm_dispatcher run \\
47
+ --instance-id "$INSTANCE_ID" \\
48
+ --description "bootstrap" \\
49
+ --timeout 3600 \\
50
+ --output-bucket "$S3_BUCKET" \\
51
+ --output-key-prefix "${S3_STAGING_PREFIX}/ssm-output" \\
52
+ --region "$AWS_REGION" \\
53
+ --script-stdin <<'BOOTSTRAP'
54
+ set -eo pipefail
55
+ ...
56
+ BOOTSTRAP
57
+
58
+ Exit code 0 on Success; 1 on terminal non-Success; 2 on bad input. The
59
+ inner script's stdout streams to the dispatcher's stdout as it arrives
60
+ (SSM ``StandardOutputContent`` delta); on terminal non-Success the
61
+ ``StandardErrorContent`` is fetched + printed before the dispatcher
62
+ exits.
63
+
64
+ **InvocationDoesNotExist race:**
65
+
66
+ After ``send-command`` returns a ``CommandId``, the first poll of
67
+ ``get-command-invocation`` can race the SSM control plane's registration
68
+ and return ``InvocationDoesNotExist``. The 2026-05-23 Saturday SF showed
69
+ this exact failure mode at event 16 (MorningEnrich first poll), absorbed
70
+ by the SF Catch but representing a substrate weakness. This module
71
+ treats ``InvocationDoesNotExist`` as a transient "Pending" status for
72
+ the first ~60s after SendCommand (the registration window) and as a
73
+ terminal failure thereafter. Mirrors the bash predecessor's
74
+ ``2>/dev/null || echo Pending`` swallow without the all-errors-look-like-Pending
75
+ ambiguity.
76
+
77
+ **Failure behavior — never raises:**
78
+
79
+ - Inner command's terminal status maps to exit code 0 (Success) or 1
80
+ (Failed / TimedOut / Cancelled / TerminalError). The dispatcher
81
+ script's ``set -e`` then propagates that exit upward to the SF Catch.
82
+ - Subprocess setup failure (boto3 missing, IAM denied at SendCommand
83
+ time, instance not registered) is logged + returns 1. The caller
84
+ reads the failure from CloudWatch / SSM history; this module's job
85
+ is to be a thin transport, not a recovery layer.
86
+ """
87
+
88
+ from __future__ import annotations
89
+
90
+ import argparse
91
+ import base64
92
+ import logging
93
+ import os
94
+ import sys
95
+ import time
96
+ from typing import Final, Optional
97
+
98
+ logger = logging.getLogger(__name__)
99
+
100
+ # Status taxonomy from SSM's get-command-invocation. Terminal non-Success
101
+ # statuses all map to exit 1.
102
+ TERMINAL_NON_SUCCESS: Final[frozenset[str]] = frozenset(
103
+ {"Cancelled", "Failed", "TimedOut", "Cancelling", "TerminalError"}
104
+ )
105
+ PENDING_STATUSES: Final[frozenset[str]] = frozenset(
106
+ {"Pending", "InProgress", "Delayed"}
107
+ )
108
+ SUCCESS_STATUS: Final[str] = "Success"
109
+
110
+ # Window during which InvocationDoesNotExist counts as a registration race
111
+ # rather than a true failure. Mirrors the empirical observation that the
112
+ # SSM control plane has settled by ~30s post-SendCommand under normal
113
+ # conditions; 60s is a defensive ceiling.
114
+ REGISTRATION_GRACE_SECONDS: Final[int] = 60
115
+
116
+ # Poll cadence — matches the bash predecessor's `sleep 5`.
117
+ DEFAULT_POLL_INTERVAL_SECONDS: Final[float] = 5.0
118
+
119
+ # StandardOutputContent / StandardErrorContent fields are capped at 24KB
120
+ # in get-command-invocation responses. Beyond the cap the buffer rotates
121
+ # (we detect by a length decrease) and the full log lives in the
122
+ # configured S3 output prefix.
123
+ SSM_INLINE_OUTPUT_CAP_BYTES: Final[int] = 24 * 1024
124
+
125
+
126
+ class SsmDispatchError(Exception):
127
+ """Non-recoverable SSM send-command / poll failure."""
128
+
129
+
130
+ def _encode_command_payload(script: str) -> str:
131
+ """Wrap ``script`` for AWS-RunShellScript transport.
132
+
133
+ The pre-lift bash helper base64-encoded the script body and emitted
134
+ a single command ``echo <b64> | base64 -d | bash``. This is the
135
+ transport-safe wrapping that lets the script contain heredocs,
136
+ embedded Python, single quotes, etc. without ASL/SSM escaping
137
+ surface.
138
+ """
139
+ b64 = base64.b64encode(script.encode("utf-8")).decode("ascii")
140
+ return f"echo {b64} | base64 -d | bash"
141
+
142
+
143
+ def run(
144
+ instance_id: str,
145
+ description: str,
146
+ script: str,
147
+ *,
148
+ timeout_seconds: int = 3600,
149
+ output_bucket: Optional[str] = None,
150
+ output_key_prefix: Optional[str] = None,
151
+ region: str = "us-east-1",
152
+ poll_interval_seconds: float = DEFAULT_POLL_INTERVAL_SECONDS,
153
+ stdout_stream=None,
154
+ stderr_stream=None,
155
+ sleep=time.sleep,
156
+ monotonic=time.monotonic,
157
+ boto3_client=None,
158
+ ) -> int:
159
+ """Send ``script`` to ``instance_id`` via SSM, poll until terminal, stream stdout.
160
+
161
+ Args:
162
+ instance_id: target EC2 instance ID (must be SSM-registered).
163
+ description: short label for SSM history + dispatcher logs.
164
+ script: bash script body. Will be base64-wrapped + executed as
165
+ a single AWS-RunShellScript command.
166
+ timeout_seconds: SSM command timeout (handed to SendCommand).
167
+ output_bucket: S3 bucket for SSM to write the full stdout/stderr
168
+ (past the 24KB inline cap). Optional; if unset, only inline
169
+ output is available.
170
+ output_key_prefix: S3 key prefix for the SSM output bucket.
171
+ region: AWS region.
172
+ poll_interval_seconds: gap between get-command-invocation polls.
173
+ stdout_stream: destination for streamed inner stdout (default:
174
+ ``sys.stdout``).
175
+ stderr_stream: destination for the terminal-failure stderr dump
176
+ (default: ``sys.stderr``).
177
+ sleep / monotonic: time hooks (overridable for tests).
178
+ boto3_client: optional boto3 ``ssm`` client (for tests). When
179
+ ``None``, constructed via ``boto3.client('ssm', region_name=region)``.
180
+
181
+ Returns:
182
+ ``0`` on terminal Success.
183
+ ``1`` on any terminal non-Success status, send-command failure,
184
+ or unrecoverable poll failure.
185
+
186
+ Never raises.
187
+ """
188
+ out = stdout_stream if stdout_stream is not None else sys.stdout
189
+ err = stderr_stream if stderr_stream is not None else sys.stderr
190
+
191
+ try:
192
+ if boto3_client is None:
193
+ import boto3
194
+
195
+ ssm = boto3.client("ssm", region_name=region)
196
+ else:
197
+ ssm = boto3_client
198
+ except Exception as exc:
199
+ print(
200
+ f"ssm_dispatcher: boto3 client construction failed: "
201
+ f"{type(exc).__name__}: {exc}",
202
+ file=err,
203
+ )
204
+ return 1
205
+
206
+ payload = _encode_command_payload(script)
207
+ send_kwargs: dict = {
208
+ "InstanceIds": [instance_id],
209
+ "DocumentName": "AWS-RunShellScript",
210
+ "Comment": description[:100], # SSM Comment cap is 100 chars
211
+ "TimeoutSeconds": int(timeout_seconds),
212
+ "Parameters": {"commands": [payload]},
213
+ }
214
+ if output_bucket:
215
+ send_kwargs["OutputS3BucketName"] = output_bucket
216
+ if output_key_prefix:
217
+ send_kwargs["OutputS3KeyPrefix"] = output_key_prefix
218
+
219
+ try:
220
+ resp = ssm.send_command(**send_kwargs)
221
+ except Exception as exc:
222
+ print(
223
+ f"ssm_dispatcher: send_command failed for {description!r}: "
224
+ f"{type(exc).__name__}: {exc}",
225
+ file=err,
226
+ )
227
+ return 1
228
+
229
+ command_id = resp.get("Command", {}).get("CommandId")
230
+ if not command_id:
231
+ print(
232
+ f"ssm_dispatcher: send_command returned no CommandId for {description!r}",
233
+ file=err,
234
+ )
235
+ return 1
236
+
237
+ print(f" [ssm {description}] command-id={command_id}", file=err)
238
+
239
+ start_monotonic = monotonic()
240
+ last_out_len = 0
241
+
242
+ while True:
243
+ sleep(poll_interval_seconds)
244
+
245
+ try:
246
+ inv = ssm.get_command_invocation(
247
+ CommandId=command_id,
248
+ InstanceId=instance_id,
249
+ )
250
+ except Exception as exc:
251
+ code = _classify_boto_exception(exc)
252
+ if code == "InvocationDoesNotExist":
253
+ elapsed = monotonic() - start_monotonic
254
+ if elapsed <= REGISTRATION_GRACE_SECONDS:
255
+ # Registration race per the 2026-05-23 Saturday SF
256
+ # event-16 substrate weakness; keep polling.
257
+ continue
258
+ print(
259
+ f"ssm_dispatcher: {description!r} command {command_id} "
260
+ f"never registered (InvocationDoesNotExist after "
261
+ f"{elapsed:.0f}s)",
262
+ file=err,
263
+ )
264
+ return 1
265
+ # Other transient classes that the bash predecessor swallowed
266
+ # via `2>/dev/null || echo Pending`. Be explicit: only the
267
+ # listed set is treated as transient; anything else is a hard
268
+ # failure.
269
+ if code in {"ThrottlingException", "RequestLimitExceeded"}:
270
+ continue
271
+ print(
272
+ f"ssm_dispatcher: get_command_invocation for {description!r} "
273
+ f"raised {code}: {exc}",
274
+ file=err,
275
+ )
276
+ return 1
277
+
278
+ status = inv.get("Status", "Pending")
279
+ std_out = inv.get("StandardOutputContent", "") or ""
280
+
281
+ if len(std_out) > last_out_len:
282
+ out.write(std_out[last_out_len:])
283
+ out.flush()
284
+ last_out_len = len(std_out)
285
+ elif len(std_out) < last_out_len:
286
+ # 24KB cap rotated the buffer; the full log is in S3 (if
287
+ # output_bucket was configured).
288
+ cap_note = (
289
+ f" [ssm {description}] (stdout exceeded "
290
+ f"{SSM_INLINE_OUTPUT_CAP_BYTES // 1024}KB cap — full log: "
291
+ f"s3://{output_bucket}/{output_key_prefix}/)\n"
292
+ if output_bucket
293
+ else (
294
+ f" [ssm {description}] (stdout exceeded "
295
+ f"{SSM_INLINE_OUTPUT_CAP_BYTES // 1024}KB cap — "
296
+ "configure --output-bucket for full log)\n"
297
+ )
298
+ )
299
+ err.write(cap_note)
300
+ err.flush()
301
+ last_out_len = len(std_out)
302
+
303
+ if status == SUCCESS_STATUS:
304
+ return 0
305
+ if status in TERMINAL_NON_SUCCESS:
306
+ std_err = inv.get("StandardErrorContent", "") or ""
307
+ err.write(
308
+ f"ERROR: SSM step {description!r} terminal status={status}\n"
309
+ )
310
+ if std_err:
311
+ err.write(
312
+ f"--- stderr ({SSM_INLINE_OUTPUT_CAP_BYTES // 1024}KB cap; "
313
+ )
314
+ if output_bucket:
315
+ err.write(
316
+ f"full: s3://{output_bucket}/{output_key_prefix}/) ---\n"
317
+ )
318
+ else:
319
+ err.write("configure --output-bucket for full log) ---\n")
320
+ err.write(std_err)
321
+ if not std_err.endswith("\n"):
322
+ err.write("\n")
323
+ err.flush()
324
+ return 1
325
+ if status not in PENDING_STATUSES:
326
+ # Unknown status — treat as a hard failure, log it.
327
+ err.write(
328
+ f"ssm_dispatcher: {description!r} returned unknown status "
329
+ f"{status!r}; treating as failure\n"
330
+ )
331
+ err.flush()
332
+ return 1
333
+ # Pending / InProgress / Delayed — keep polling.
334
+
335
+
336
+ def _classify_boto_exception(exc: BaseException) -> str:
337
+ """Extract the ``Error.Code`` from a botocore ClientError.
338
+
339
+ Returns the exception class name when no ``response.Error.Code`` is
340
+ available (e.g., on non-botocore exceptions). Tests patch this for
341
+ deterministic InvocationDoesNotExist surfacing.
342
+ """
343
+ response = getattr(exc, "response", None)
344
+ if isinstance(response, dict):
345
+ code = response.get("Error", {}).get("Code")
346
+ if code:
347
+ return str(code)
348
+ return type(exc).__name__
349
+
350
+
351
+ def _read_script(args: argparse.Namespace) -> str:
352
+ if args.script_file:
353
+ with open(args.script_file, "r", encoding="utf-8") as fh:
354
+ return fh.read()
355
+ if args.script_stdin:
356
+ return sys.stdin.read()
357
+ raise SystemExit(
358
+ "ssm_dispatcher: must pass either --script-file PATH or --script-stdin "
359
+ "(with the script body on stdin)"
360
+ )
361
+
362
+
363
+ def main(argv: list[str] | None = None) -> int:
364
+ parser = argparse.ArgumentParser(
365
+ prog="python -m alpha_engine_lib.ssm_dispatcher",
366
+ description=(
367
+ "Send a bash script to an SSM-registered EC2 instance via "
368
+ "AWS-RunShellScript, poll until terminal, stream stdout to "
369
+ "this process, and propagate the inner exit status. The "
370
+ "institutional replacement for the ~54-line run_ssm bash "
371
+ "helper mirrored across alpha-engine-* dispatcher scripts."
372
+ ),
373
+ )
374
+ subparsers = parser.add_subparsers(dest="cmd", required=True)
375
+
376
+ run_p = subparsers.add_parser(
377
+ "run",
378
+ help="Dispatch a script to an instance and stream its output.",
379
+ )
380
+ run_p.add_argument(
381
+ "--instance-id",
382
+ required=True,
383
+ help="Target EC2 instance ID (must be SSM-registered).",
384
+ )
385
+ run_p.add_argument(
386
+ "--description",
387
+ required=True,
388
+ help=(
389
+ "Short label for the SSM command Comment + dispatcher log "
390
+ "lines (e.g., 'bootstrap', 'full-training')."
391
+ ),
392
+ )
393
+ run_p.add_argument(
394
+ "--timeout",
395
+ type=int,
396
+ default=3600,
397
+ help="SSM command timeout in seconds (default: 3600).",
398
+ )
399
+ run_p.add_argument(
400
+ "--output-bucket",
401
+ default=None,
402
+ help=(
403
+ "S3 bucket where SSM writes the full stdout/stderr beyond "
404
+ "the inline 24KB cap. Optional; without it, only the inline "
405
+ "delta is available."
406
+ ),
407
+ )
408
+ run_p.add_argument(
409
+ "--output-key-prefix",
410
+ default=None,
411
+ help="S3 key prefix under --output-bucket for the SSM output.",
412
+ )
413
+ run_p.add_argument(
414
+ "--region",
415
+ default=os.environ.get("AWS_REGION", "us-east-1"),
416
+ help="AWS region (default: $AWS_REGION or us-east-1).",
417
+ )
418
+ run_p.add_argument(
419
+ "--poll-interval",
420
+ type=float,
421
+ default=DEFAULT_POLL_INTERVAL_SECONDS,
422
+ help=(
423
+ "Seconds between get-command-invocation polls (default: "
424
+ f"{DEFAULT_POLL_INTERVAL_SECONDS:g})."
425
+ ),
426
+ )
427
+ script_grp = run_p.add_mutually_exclusive_group(required=True)
428
+ script_grp.add_argument(
429
+ "--script-file",
430
+ default=None,
431
+ help="Path to a local file containing the bash script body.",
432
+ )
433
+ script_grp.add_argument(
434
+ "--script-stdin",
435
+ action="store_true",
436
+ help="Read the bash script body from stdin (heredoc-friendly).",
437
+ )
438
+
439
+ args = parser.parse_args(argv)
440
+ logging.basicConfig(level=logging.WARNING)
441
+
442
+ script = _read_script(args)
443
+ if not script.strip():
444
+ print(
445
+ "ssm_dispatcher: empty script body (refusing to dispatch a no-op)",
446
+ file=sys.stderr,
447
+ )
448
+ return 2
449
+
450
+ return run(
451
+ instance_id=args.instance_id,
452
+ description=args.description,
453
+ script=script,
454
+ timeout_seconds=args.timeout,
455
+ output_bucket=args.output_bucket,
456
+ output_key_prefix=args.output_key_prefix,
457
+ region=args.region,
458
+ poll_interval_seconds=args.poll_interval,
459
+ )
460
+
461
+
462
+ if __name__ == "__main__":
463
+ sys.exit(main())
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: alpha-engine-lib
3
- Version: 0.34.1
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, and Step-Functions execution-state projection. Full surface documented in README.
3
+ Version: 0.35.0
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
7
7
  Requires-Python: >=3.9
@@ -181,18 +181,47 @@ results = retrieve(
181
181
 
182
182
  Requires the `[rag]` extra. Embeddings are Voyage `voyage-3-lite` (512d); the database backend is Neon Postgres with pgvector + HNSW indexes.
183
183
 
184
+ ### `ssm_dispatcher` — SSM send-command + poll chokepoint
185
+
186
+ Canonical Python primitive for the `run_ssm` bash helper that previously appeared as a ~54-line mirror across each dispatcher script that drives a spot instance over the SSM transport. The pre-lift shape — base64-wrap the script body, `aws ssm send-command --document-name AWS-RunShellScript`, loop on `get-command-invocation`, stream the `StandardOutputContent` delta, propagate the inner exit — now lives in one place where the polling cadence, error-class handling, and S3 output-key layout match across every consumer.
187
+
188
+ ```bash
189
+ python -m alpha_engine_lib.ssm_dispatcher run \
190
+ --instance-id "$INSTANCE_ID" \
191
+ --description "bootstrap" \
192
+ --timeout 3600 \
193
+ --output-bucket "$S3_BUCKET" \
194
+ --output-key-prefix "${S3_STAGING_PREFIX}/ssm-output" \
195
+ --region "$AWS_REGION" \
196
+ --script-stdin <<'BOOTSTRAP'
197
+ set -eo pipefail
198
+ export HOME=/home/ec2-user AWS_REGION=us-east-1
199
+ # ...the script body the SSM target will execute...
200
+ BOOTSTRAP
201
+ ```
202
+
203
+ Exit 0 on `Success`; exit 1 on any terminal non-Success status, send-command failure, or unrecoverable poll failure; exit 2 on bad CLI input. `InvocationDoesNotExist` during the first 60s after SendCommand counts as a registration race and keeps polling — closes the 2026-05-23 Saturday SF substrate weakness at the chokepoint rather than per-SF-JSON Retry block.
204
+
205
+ ### `ssm_log_capture` — SSM-step log capture + S3 ship-on-exit chokepoint
206
+
207
+ Pairs with `ssm_dispatcher` on the SSM target side. The dispatcher script tells the target instance to invoke `python -m alpha_engine_lib.ssm_log_capture run --slug X --log /var/log/X.log -- bash <launcher>`; the target wrapper tees the launcher's stdout/stderr to a local log file and to its own stdout (so the SSM `StandardOutputContent` channel still surfaces output to the dispatcher), then on exit ships the full local log to `s3://alpha-engine-research/_ssm_logs/{slug}/{date}/{host}-{time}.log` regardless of the inner exit code. Replaces the inline `trap 'aws s3 cp ...' EXIT` pattern that broke under ASL `States.Array` escape semantics (2026-05-22 Friday-PM dry-pass catch).
208
+
209
+ ### `ec2_spot` — capacity-resilient spot-launch chokepoint
210
+
211
+ Rotates across `(instance_type × subnet)` combinations on `InsufficientInstanceCapacity` / `InsufficientHostCapacity` / `Unsupported` / `InvalidAvailabilityZone` / `SpotMaxPriceTooLow`; non-capacity errors raise immediately. CLI exit 64 distinguishes capacity exhaustion from generic failure. Replaces the hardcoded single-subnet + single-instance-type launch pattern that mirrored across each dispatcher; landed 2026-05-22 after the third-recurrence-in-a-month spot-launch fragility.
212
+
184
213
  ## How it's used
185
214
 
186
215
  All six Nous Ergon module repos depend on this lib:
187
216
 
188
217
  | Module | Repo | What it imports from here |
189
218
  |---|---|---|
190
- | Data | [`alpha-engine-data`](https://github.com/cipher813/alpha-engine-data) | `logging`, `preflight`, `arcticdb`, `dates`, `trading_calendar`, `rag` (ingestion) |
219
+ | Data | [`alpha-engine-data`](https://github.com/cipher813/alpha-engine-data) | `logging`, `preflight`, `arcticdb`, `dates`, `trading_calendar`, `rag` (ingestion), `ec2_spot` + `ssm_log_capture` + `ssm_dispatcher` (spot launchers) |
191
220
  | Research | [`alpha-engine-research`](https://github.com/cipher813/alpha-engine-research) | `logging`, `decision_capture`, `cost`, `dates`, `rag` (retrieval), `agent_schemas` (canonical LLM-output contracts) |
192
- | Predictor | [`alpha-engine-predictor`](https://github.com/cipher813/alpha-engine-predictor) | `logging`, `preflight`, `arcticdb`, `dates` |
221
+ | Predictor | [`alpha-engine-predictor`](https://github.com/cipher813/alpha-engine-predictor) | `logging`, `preflight`, `arcticdb`, `dates`, `ec2_spot` + `ssm_log_capture` + `ssm_dispatcher` (spot launcher) |
193
222
  | Executor | [`alpha-engine`](https://github.com/cipher813/alpha-engine) | `logging`, `preflight`, `arcticdb`, `dates`, `trading_calendar` |
194
- | Backtester | [`alpha-engine-backtester`](https://github.com/cipher813/alpha-engine-backtester) | `logging`, `preflight`, `arcticdb`, `dates`, `agent_schemas` (replay-harness Pydantic validation) |
195
- | Dashboard | [`alpha-engine-dashboard`](https://github.com/cipher813/alpha-engine-dashboard) | `logging`, `arcticdb`, `dates` |
223
+ | Backtester | [`alpha-engine-backtester`](https://github.com/cipher813/alpha-engine-backtester) | `logging`, `preflight`, `arcticdb`, `dates`, `agent_schemas` (replay-harness Pydantic validation), `ec2_spot` + `ssm_log_capture` + `ssm_dispatcher` (spot launcher) |
224
+ | Dashboard | [`alpha-engine-dashboard`](https://github.com/cipher813/alpha-engine-dashboard) | `logging`, `arcticdb`, `dates`, hosts the SSM-target `.venv` that `ssm_dispatcher` invokes via `python -m` |
196
225
 
197
226
  ## Development
198
227