alloc-context 0.1.0__tar.gz → 0.1.2__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.
- {alloc_context-0.1.0 → alloc_context-0.1.2}/PKG-INFO +3 -1
- {alloc_context-0.1.0 → alloc_context-0.1.2}/README.md +2 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloc_context.egg-info/PKG-INFO +3 -1
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloc_context.egg-info/SOURCES.txt +1 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/__init__.py +1 -1
- {alloc_context-0.1.0 → alloc_context-0.1.2}/pyproject.toml +1 -1
- {alloc_context-0.1.0 → alloc_context-0.1.2}/tests/test_bump_version.py +2 -2
- alloc_context-0.1.2/tests/test_server_json.py +35 -0
- alloc_context-0.1.2/tests/test_workflows.py +150 -0
- alloc_context-0.1.0/tests/test_workflows.py +0 -102
- {alloc_context-0.1.0 → alloc_context-0.1.2}/LICENSE +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloc_context.egg-info/dependency_links.txt +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloc_context.egg-info/entry_points.txt +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloc_context.egg-info/requires.txt +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloc_context.egg-info/top_level.txt +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/__main__.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/config.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/horizon.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/ingest/__init__.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/ingest/cf_benchmarks.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/ingest/cf_history.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/ingest/coinbase_client.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/ingest/coinbase_portfolio.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/ingest/coingecko.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/ingest/coinmarketcap.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/ingest/env_keys.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/ingest/etf_flows.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/ingest/exchange/__init__.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/ingest/exchange/coinbase_adapter.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/ingest/exchange/kraken_adapter.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/ingest/exchange/live.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/ingest/exchange/portfolio.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/ingest/exchange/registry.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/ingest/exchange/types.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/ingest/exchange_http.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/ingest/fear_greed.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/ingest/fred.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/ingest/http_errors.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/ingest/kalshi.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/ingest/kalshi_api.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/ingest/kalshi_client.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/ingest/kalshi_files.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/ingest/kalshi_state.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/ingest/kraken_client.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/ingest/kraken_portfolio.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/ingest/macro_calendar.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/ingest/macro_normalize.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/ingest/market_snapshots.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/ingest/outcome.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/ingest/parse_helpers.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/ingest/runner.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/mcp/__init__.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/mcp/assets.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/mcp/bazaar.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/mcp/contracts.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/mcp/handlers.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/mcp/http.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/mcp/payment_middleware.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/mcp/server.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/mcp/staleness.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/mcp/validation.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/mcp/x402_bazaar_dynamic.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/mcp/x402_config.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/mcp/x402_pricing.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/mcp/x402_stables.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/rollup/__init__.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/rollup/band.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/rollup/breadth.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/rollup/cf_math.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/rollup/cluster.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/rollup/cluster_config.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/rollup/comparison.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/rollup/context.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/rollup/delta.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/rollup/etf.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/rollup/fear_greed.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/rollup/macro.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/rollup/portfolio.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/rollup/rebalance.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/rollup/regime.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/rollup/sentiment.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/rollup/snapshots.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/rollup/tape.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/status_report.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/store/__init__.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/store/db.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/store/jsonutil.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/store/meta.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/store/retention.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/store/status.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/timeutil.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/x402_production_check.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/x402_smoke_redact.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/setup.cfg +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/tests/test_adr005_ingest.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/tests/test_backup_sqlite.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/tests/test_coinbase_portfolio.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/tests/test_context_snapshots.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/tests/test_db_schema.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/tests/test_deploy.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/tests/test_dev_stack.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/tests/test_etf.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/tests/test_exchanges_config.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/tests/test_fear_greed.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/tests/test_fred.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/tests/test_horizon.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/tests/test_ingest_outcome.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/tests/test_ingest_runner.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/tests/test_kalshi_api.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/tests/test_kraken_portfolio.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/tests/test_macro.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/tests/test_market_breadth.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/tests/test_mcp_assets_regime.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/tests/test_mcp_bazaar.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/tests/test_mcp_contracts.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/tests/test_mcp_freshness.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/tests/test_mcp_handlers.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/tests/test_mcp_health.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/tests/test_mcp_http_lifecycle.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/tests/test_mcp_live_portfolio.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/tests/test_mcp_server.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/tests/test_mcp_validation.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/tests/test_mcp_x402.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/tests/test_mcp_x402_http.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/tests/test_mcp_x402_pricing.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/tests/test_mcp_x402_stables.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/tests/test_qa_ingest_store.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/tests/test_rebalance.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/tests/test_rollup.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/tests/test_scaffold.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/tests/test_script_runtime.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/tests/test_security_hardening.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/tests/test_snapshots_and_delta.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/tests/test_status_report.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/tests/test_x402_bazaar_dynamic.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/tests/test_x402_production_check.py +0 -0
- {alloc_context-0.1.0 → alloc_context-0.1.2}/tests/test_x402_smoke_redact.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: alloc-context
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: AllocContext — BTC/ETH allocation context, drift, and rebalance facts
|
|
5
5
|
License: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/negillett/alloc-context
|
|
@@ -42,6 +42,8 @@ Dynamic: license-file
|
|
|
42
42
|
|
|
43
43
|
# AllocContext
|
|
44
44
|
|
|
45
|
+
mcp-name: io.github.negillett/alloc-context
|
|
46
|
+
|
|
45
47
|
**Allocation context for BTC/ETH** — drift, band checks, USD rebalance moves,
|
|
46
48
|
and a fused market backdrop (Fear & Greed, Kalshi, ETF flows, macro) as
|
|
47
49
|
deterministic JSON over MCP.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: alloc-context
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: AllocContext — BTC/ETH allocation context, drift, and rebalance facts
|
|
5
5
|
License: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/negillett/alloc-context
|
|
@@ -42,6 +42,8 @@ Dynamic: license-file
|
|
|
42
42
|
|
|
43
43
|
# AllocContext
|
|
44
44
|
|
|
45
|
+
mcp-name: io.github.negillett/alloc-context
|
|
46
|
+
|
|
45
47
|
**Allocation context for BTC/ETH** — drift, band checks, USD rebalance moves,
|
|
46
48
|
and a fused market backdrop (Fear & Greed, Kalshi, ETF flows, macro) as
|
|
47
49
|
deterministic JSON over MCP.
|
|
@@ -45,7 +45,7 @@ def test_resolve_target_version_rejects_downgrade():
|
|
|
45
45
|
|
|
46
46
|
|
|
47
47
|
def test_resolve_target_version_rejects_unchanged_exact():
|
|
48
|
-
with pytest.raises(ValueError, match="
|
|
48
|
+
with pytest.raises(ValueError, match="already"):
|
|
49
49
|
resolve_target_version(current="0.1.0", bump=None, exact="0.1.0")
|
|
50
50
|
|
|
51
51
|
|
|
@@ -55,7 +55,7 @@ def test_check_version_passes_when_in_sync(tmp_path: Path):
|
|
|
55
55
|
dest = tmp_path / rel
|
|
56
56
|
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
57
57
|
shutil.copy(src, dest)
|
|
58
|
-
check_version(
|
|
58
|
+
check_version(read_current_version(REPO_ROOT), root=tmp_path)
|
|
59
59
|
|
|
60
60
|
|
|
61
61
|
def test_check_version_fails_when_out_of_sync(tmp_path: Path):
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
7
|
+
SERVER_JSON = REPO_ROOT / "server.json"
|
|
8
|
+
|
|
9
|
+
# Official MCP Registry limit (see registry validation errors).
|
|
10
|
+
REGISTRY_DESCRIPTION_MAX_LEN = 100
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_server_json_description_within_registry_limit():
|
|
14
|
+
data = json.loads(SERVER_JSON.read_text(encoding="utf-8"))
|
|
15
|
+
description = data["description"]
|
|
16
|
+
assert len(description) <= REGISTRY_DESCRIPTION_MAX_LEN, (
|
|
17
|
+
f"server.json description is {len(description)} chars; "
|
|
18
|
+
f"registry max is {REGISTRY_DESCRIPTION_MAX_LEN}"
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_server_json_version_matches_pyproject():
|
|
23
|
+
import tomllib
|
|
24
|
+
|
|
25
|
+
py_ver = tomllib.loads(
|
|
26
|
+
(REPO_ROOT / "pyproject.toml").read_text(encoding="utf-8")
|
|
27
|
+
)["project"]["version"]
|
|
28
|
+
data = json.loads(SERVER_JSON.read_text(encoding="utf-8"))
|
|
29
|
+
assert data["version"] == py_ver
|
|
30
|
+
assert data["packages"][0]["version"] == py_ver
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_readme_includes_mcp_registry_name_for_pypi():
|
|
34
|
+
readme = (REPO_ROOT / "README.md").read_text(encoding="utf-8")
|
|
35
|
+
assert "mcp-name: io.github.negillett/alloc-context" in readme
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import yaml
|
|
6
|
+
|
|
7
|
+
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
8
|
+
WORKFLOWS_DIR = REPO_ROOT / ".github" / "workflows"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _load_workflow(name: str) -> dict:
|
|
12
|
+
path = WORKFLOWS_DIR / name
|
|
13
|
+
assert path.exists(), f"missing workflow {name}"
|
|
14
|
+
with path.open() as handle:
|
|
15
|
+
return yaml.safe_load(handle)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _job_steps(workflow: dict, job_name: str | None = None) -> list[dict]:
|
|
19
|
+
jobs = workflow["jobs"]
|
|
20
|
+
if job_name is None:
|
|
21
|
+
job_name = next(iter(jobs))
|
|
22
|
+
return jobs[job_name]["steps"]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _workflow_on(workflow: dict) -> dict:
|
|
26
|
+
# PyYAML may parse bare `on:` as boolean True.
|
|
27
|
+
return workflow.get("on") or workflow[True]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_ci_runs_pytest():
|
|
31
|
+
workflow = _load_workflow("ci.yml")
|
|
32
|
+
steps = _job_steps(workflow, "test")
|
|
33
|
+
pytest_steps = [
|
|
34
|
+
step for step in steps if step.get("run", "").strip().startswith("pytest")
|
|
35
|
+
]
|
|
36
|
+
assert pytest_steps, "ci workflow must run pytest"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def test_ci_runs_actionlint_from_workspace_binary():
|
|
40
|
+
workflow = _load_workflow("ci.yml")
|
|
41
|
+
steps = _job_steps(workflow, "test")
|
|
42
|
+
lint_steps = [step for step in steps if "actionlint" in step.get("run", "")]
|
|
43
|
+
assert lint_steps, "ci workflow must lint workflows"
|
|
44
|
+
run_script = lint_steps[0]["run"]
|
|
45
|
+
assert "./actionlint" in run_script
|
|
46
|
+
assert "1.7.12" in run_script
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_ci_has_no_deploy_job():
|
|
50
|
+
workflow = _load_workflow("ci.yml")
|
|
51
|
+
assert "deploy" not in workflow["jobs"]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_bump_release_workflow_removed():
|
|
55
|
+
assert not (WORKFLOWS_DIR / "bump-release.yml").exists()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_release_pr_workflow_opens_pr():
|
|
59
|
+
workflow = _load_workflow("release-pr.yml")
|
|
60
|
+
on = _workflow_on(workflow)
|
|
61
|
+
assert "workflow_dispatch" in on
|
|
62
|
+
inputs = on["workflow_dispatch"]["inputs"]
|
|
63
|
+
assert "bump" in inputs
|
|
64
|
+
assert "exact_version" in inputs
|
|
65
|
+
# Opening a release PR must not publish or deploy.
|
|
66
|
+
assert "tag_only" not in inputs
|
|
67
|
+
|
|
68
|
+
steps = _job_steps(workflow, "open-release-pr")
|
|
69
|
+
runs = [step.get("run", "") for step in steps]
|
|
70
|
+
assert any("scripts/bump_version.py" in run for run in runs)
|
|
71
|
+
assert any("git push -u origin" in run and "release/v" in run for run in runs)
|
|
72
|
+
assert any("gh pr create" in run for run in runs)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_release_workflow_triggers_on_main_push_only():
|
|
76
|
+
workflow = _load_workflow("release.yml")
|
|
77
|
+
on = _workflow_on(workflow)
|
|
78
|
+
assert on["push"]["branches"] == ["main"]
|
|
79
|
+
# No manual or tag trigger — releases are driven by merges to main.
|
|
80
|
+
assert "workflow_dispatch" not in on
|
|
81
|
+
assert "tags" not in on["push"]
|
|
82
|
+
assert workflow["concurrency"]["group"].startswith("release-")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def test_release_workflow_gates_on_untagged_version():
|
|
86
|
+
workflow = _load_workflow("release.yml")
|
|
87
|
+
check = workflow["jobs"]["check"]
|
|
88
|
+
assert check["outputs"]["release"]
|
|
89
|
+
check_runs = [step.get("run", "") for step in _job_steps(workflow, "check")]
|
|
90
|
+
assert any("--current" in run for run in check_runs)
|
|
91
|
+
assert any("ls-remote --tags" in run for run in check_runs)
|
|
92
|
+
# Every downstream job is conditioned on the release decision.
|
|
93
|
+
for job_name in ("test", "publish-pypi", "publish-mcp-registry", "deploy", "finalize"):
|
|
94
|
+
cond = workflow["jobs"][job_name]["if"]
|
|
95
|
+
assert "needs.check.outputs.release" in cond
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_release_workflow_publishes_then_deploys_then_finalizes():
|
|
99
|
+
workflow = _load_workflow("release.yml")
|
|
100
|
+
jobs = workflow["jobs"]
|
|
101
|
+
|
|
102
|
+
publish_steps = _job_steps(workflow, "publish-pypi")
|
|
103
|
+
publish_runs = [step.get("run", "") for step in publish_steps]
|
|
104
|
+
assert any("python -m build" in run for run in publish_runs)
|
|
105
|
+
pypi_step = next(
|
|
106
|
+
step
|
|
107
|
+
for step in publish_steps
|
|
108
|
+
if step.get("uses", "").startswith("pypa/gh-action-pypi-publish")
|
|
109
|
+
)
|
|
110
|
+
# Idempotent re-runs must not fail on an already-uploaded version.
|
|
111
|
+
assert pypi_step["with"]["skip-existing"] is True
|
|
112
|
+
|
|
113
|
+
registry_runs = [
|
|
114
|
+
step.get("run", "") for step in _job_steps(workflow, "publish-mcp-registry")
|
|
115
|
+
]
|
|
116
|
+
assert any("publish-mcp-registry.sh" in run for run in registry_runs)
|
|
117
|
+
|
|
118
|
+
deploy_steps = _job_steps(workflow, "deploy")
|
|
119
|
+
names = [step.get("name", "") for step in deploy_steps]
|
|
120
|
+
assert "Rsync to VPS" in names
|
|
121
|
+
assert "Install on VPS" in names
|
|
122
|
+
install = next(step for step in deploy_steps if step.get("name") == "Install on VPS")
|
|
123
|
+
assert "deploy/remote-install.sh" in install["run"]
|
|
124
|
+
|
|
125
|
+
finalize = jobs["finalize"]
|
|
126
|
+
assert set(finalize["needs"]) == {
|
|
127
|
+
"check",
|
|
128
|
+
"publish-pypi",
|
|
129
|
+
"publish-mcp-registry",
|
|
130
|
+
"deploy",
|
|
131
|
+
}
|
|
132
|
+
finalize_runs = [step.get("run", "") for step in _job_steps(workflow, "finalize")]
|
|
133
|
+
assert any("git tag" in run for run in finalize_runs)
|
|
134
|
+
assert any("gh release create" in run for run in finalize_runs)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def test_release_workflow_drops_branch_juggling_jobs():
|
|
138
|
+
workflow = _load_workflow("release.yml")
|
|
139
|
+
jobs = workflow["jobs"]
|
|
140
|
+
for removed in ("gate", "prepare", "validate-version", "finalize-tag", "sync-main"):
|
|
141
|
+
assert removed not in jobs
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def test_publish_mcp_registry_workflow_dispatch():
|
|
145
|
+
workflow = _load_workflow("publish-mcp-registry.yml")
|
|
146
|
+
on = _workflow_on(workflow)
|
|
147
|
+
assert "workflow_dispatch" in on
|
|
148
|
+
runs = [step.get("run", "") for step in _job_steps(workflow, "publish")]
|
|
149
|
+
assert any("install-mcp-publisher" in run for run in runs)
|
|
150
|
+
assert any("publish-mcp-registry.sh" in run for run in runs)
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
|
|
5
|
-
import yaml
|
|
6
|
-
|
|
7
|
-
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
8
|
-
WORKFLOWS_DIR = REPO_ROOT / ".github" / "workflows"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def _load_workflow(name: str) -> dict:
|
|
12
|
-
path = WORKFLOWS_DIR / name
|
|
13
|
-
assert path.exists(), f"missing workflow {name}"
|
|
14
|
-
with path.open() as handle:
|
|
15
|
-
return yaml.safe_load(handle)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
def _job_steps(workflow: dict, job_name: str | None = None) -> list[dict]:
|
|
19
|
-
jobs = workflow["jobs"]
|
|
20
|
-
if job_name is None:
|
|
21
|
-
job_name = next(iter(jobs))
|
|
22
|
-
return jobs[job_name]["steps"]
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def _workflow_on(workflow: dict) -> dict:
|
|
26
|
-
# PyYAML may parse bare `on:` as boolean True.
|
|
27
|
-
return workflow.get("on") or workflow[True]
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def test_ci_runs_pytest():
|
|
31
|
-
workflow = _load_workflow("ci.yml")
|
|
32
|
-
steps = _job_steps(workflow, "test")
|
|
33
|
-
pytest_steps = [
|
|
34
|
-
step for step in steps if step.get("run", "").strip().startswith("pytest")
|
|
35
|
-
]
|
|
36
|
-
assert pytest_steps, "ci workflow must run pytest"
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def test_ci_runs_actionlint_from_workspace_binary():
|
|
40
|
-
workflow = _load_workflow("ci.yml")
|
|
41
|
-
steps = _job_steps(workflow, "test")
|
|
42
|
-
lint_steps = [step for step in steps if "actionlint" in step.get("run", "")]
|
|
43
|
-
assert lint_steps, "ci workflow must lint workflows"
|
|
44
|
-
run_script = lint_steps[0]["run"]
|
|
45
|
-
assert "./actionlint" in run_script
|
|
46
|
-
assert "1.7.12" in run_script
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def test_ci_has_no_deploy_job():
|
|
50
|
-
workflow = _load_workflow("ci.yml")
|
|
51
|
-
assert "deploy" not in workflow["jobs"]
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def test_bump_release_workflow_removed():
|
|
55
|
-
assert not (WORKFLOWS_DIR / "bump-release.yml").exists()
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def test_release_workflow_unified_pipeline():
|
|
59
|
-
workflow = _load_workflow("release.yml")
|
|
60
|
-
on = _workflow_on(workflow)
|
|
61
|
-
assert "workflow_dispatch" in on
|
|
62
|
-
inputs = on["workflow_dispatch"]["inputs"]
|
|
63
|
-
assert "bump" in inputs
|
|
64
|
-
assert "exact_version" in inputs
|
|
65
|
-
assert "tag_only" in inputs
|
|
66
|
-
assert on["push"]["tags"] == ["v[0-9]+.[0-9]+.[0-9]+"]
|
|
67
|
-
assert workflow["concurrency"]["group"].startswith("release-")
|
|
68
|
-
|
|
69
|
-
jobs = workflow["jobs"]
|
|
70
|
-
assert jobs["deploy"]["needs"] == ["validate-version", "publish-pypi"]
|
|
71
|
-
assert jobs["finalize-tag"]["needs"] == [
|
|
72
|
-
"prepare",
|
|
73
|
-
"validate-version",
|
|
74
|
-
"publish-pypi",
|
|
75
|
-
"deploy",
|
|
76
|
-
]
|
|
77
|
-
|
|
78
|
-
prepare_runs = [step.get("run", "") for step in _job_steps(workflow, "prepare")]
|
|
79
|
-
assert any("scripts/bump_version.py" in run for run in prepare_runs)
|
|
80
|
-
|
|
81
|
-
validate_runs = [
|
|
82
|
-
step.get("run", "") for step in _job_steps(workflow, "validate-version")
|
|
83
|
-
]
|
|
84
|
-
assert any("scripts/bump_version.py --check" in run for run in validate_runs)
|
|
85
|
-
|
|
86
|
-
deploy_steps = _job_steps(workflow, "deploy")
|
|
87
|
-
names = [step.get("name", "") for step in deploy_steps]
|
|
88
|
-
assert "Rsync to VPS" in names
|
|
89
|
-
assert "Install on VPS" in names
|
|
90
|
-
install = next(step for step in deploy_steps if step.get("name") == "Install on VPS")
|
|
91
|
-
assert "deploy/remote-install.sh" in install["run"]
|
|
92
|
-
|
|
93
|
-
publish_steps = _job_steps(workflow, "publish-pypi")
|
|
94
|
-
publish_runs = [step.get("run", "") for step in publish_steps]
|
|
95
|
-
assert any("python -m build" in run for run in publish_runs)
|
|
96
|
-
assert any(
|
|
97
|
-
step.get("uses", "").startswith("pypa/gh-action-pypi-publish")
|
|
98
|
-
for step in publish_steps
|
|
99
|
-
)
|
|
100
|
-
|
|
101
|
-
finalize_runs = [step.get("run", "") for step in _job_steps(workflow, "finalize-tag")]
|
|
102
|
-
assert any("git push origin" in run and "TAG" in run for run in finalize_runs)
|
|
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
|
{alloc_context-0.1.0 → alloc_context-0.1.2}/alloccontext/ingest/exchange/coinbase_adapter.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
|
|
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
|
|
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
|
|
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
|
|
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
|