-
Notifications
You must be signed in to change notification settings - Fork 0
Add phase-1 QSL compatibility manifest and checks #151
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b6f7fcd
a2f5938
a692929
4c3a64e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| name = "2026.07.0" | ||
| description = "Phase-1 QSL compatibility bundle" | ||
| created_at = "2026-07-03" | ||
|
|
||
| [repos] | ||
| QuantPlatformKit = "0063af3b4a974650ea58a7d3f26dd1b94f65d3e8" | ||
| UsEquityStrategies = "46887bc3f5454d5b59623b1f5efb7c65912c6b8b" | ||
| HkEquityStrategies = "61993bf261aeccf64b5a75428b9405f4e1d1d682" | ||
| CnEquityStrategies = "ffbdf7303179ba6e7f9d3e28c21202f77e04762c" | ||
| CryptoStrategies = "8039ddddde7634ad3615496c9b79d2918996938c" | ||
| QuantStrategyPlugins = "6c10aef8989f52d82571e676092b14d315401989" | ||
| MarketSignalSources = "ea8771b6b65f356e7edc4a2b08cd621186726646" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| [tiers] | ||
| core = { name = "core", description = "平台最小闭环、运行核心与共享基础设施" } | ||
| strategy_lib = { name = "strategy-lib", description = "策略库、插件与市场信号源" } | ||
| pipeline = { name = "pipeline", description = "策略研发与快照流水线" } | ||
| runtime = { name = "runtime", description = "平台运行时与执行服务" } | ||
| ops = { name = "ops/tooling", description = "运维/发布与中央配置仓库" } | ||
|
|
||
| [upgrade_rings] | ||
| ring_a = "core" | ||
| ring_b = "strategy-lib" | ||
| ring_c = "pipeline" | ||
| ring_d = "runtime" | ||
| ring_e = "ops/tooling" | ||
|
|
||
| [upgrade_rules] | ||
| # 允许上升方向:稳定性从低到高(ops/tooling -> runtime -> pipeline -> strategy-lib -> core) | ||
| # 建议原则:较外层可回退引用内层,内层通常不直接引用同级以上的低稳定性 ring。 | ||
| allow_drift = [ | ||
| "ops/tooling:runtime", | ||
| "ops/tooling:pipeline", | ||
| "ops/tooling:strategy-lib", | ||
| "ops/tooling:core", | ||
| "runtime:pipeline", | ||
| "runtime:strategy-lib", | ||
| "runtime:core", | ||
| "pipeline:strategy-lib", | ||
| "pipeline:core", | ||
| "strategy-lib:core", | ||
| ] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| # QSL Compatibility & Upgrade Checklist (Central Manifest Phase-1) | ||
|
|
||
| This repository carries the first version of the QSL central compatibility manifest. | ||
|
|
||
| ## 文件结构 | ||
|
|
||
| - `compat/bundles/*.toml` | ||
| - 每个 bundle 用 Calendar Version 命名(当前:`2026.07.0`)。 | ||
| - 记录 QSL 管控的固定内部仓库提交。 | ||
| - `compat/repo-tiers.toml` | ||
| - 记录仓库层级(`core/strategy-lib/pipeline/runtime/ops`)与升级 ring 规则。 | ||
| - `qsl.toml` | ||
| - 本仓库自身的 QSL 元信息:`tier`、`compat`/`bundle`、`upgrade_ring`。 | ||
| - `scripts/check_qsl_compat.py` | ||
| - 在任意仓库根目录运行,校验: | ||
| - 禁止 `@main` | ||
| - 禁止短 SHA | ||
| - 禁止 `requirements.txt` / `constraints.txt`(未设置 `allow_legacy=true` 时) | ||
| - 内部依赖 Ref 是否与 `compat/bundles/<bundle>.toml` 一致 | ||
| - `scripts/render_qsl_dependency_graph.py` | ||
| - 输出当前仓库的 QSL 依赖图(Markdown / Text)。 | ||
|
|
||
| ## 如何接入一个新仓库 | ||
|
|
||
| 1. 在仓库根目录新建 `qsl.toml`: | ||
|
|
||
| ```toml | ||
| [qsl] | ||
| bundle = "2026.07.0" # 选择要对齐的 central bundle | ||
| compat = "2026.07.0" # 兼容检查入口(与 bundle 相同) | ||
| tier = "ops/tooling" | ||
| upgrade_ring = "ring_e" | ||
| allow_legacy = false # 需要临时兼容时可先放开 | ||
| enforce_bundle = true # 过渡仓库可设 false;ref drift 会降级为 warning | ||
| ``` | ||
|
|
||
| 2. 在 `pyproject.toml`/`uv.lock` 中用完整 SHA 固定 QuantStrategyLab 的内部 git 依赖。 | ||
|
|
||
| 3. 运行自检: | ||
|
|
||
| ```bash | ||
| python scripts/check_qsl_compat.py --repo-root . --non-strict | ||
| python scripts/render_qsl_dependency_graph.py --repo-root . --format md | ||
| ``` | ||
|
|
||
| 说明:默认脚本会输出错误并以非零退出码返回;`--non-strict` 仅用于本地快速预览。 | ||
|
|
||
| ## 当前中心兼容基线 | ||
|
|
||
| - Bundle: `2026.07.0` | ||
| - QPK: `0063af3b4a974650ea58a7d3f26dd1b94f65d3e8` | ||
| - UsEquityStrategies: `46887bc3f5454d5b59623b1f5efb7c65912c6b8b` | ||
| - HkEquityStrategies: `61993bf261aeccf64b5a75428b9405f4e1d1d682` | ||
| - CnEquityStrategies: `ffbdf7303179ba6e7f9d3e28c21202f77e04762c` | ||
| - CryptoStrategies: `8039ddddde7634ad3615496c9b79d2918996938c` | ||
| - QuantStrategyPlugins: `6c10aef8989f52d82571e676092b14d315401989` | ||
| - MarketSignalSources: `ea8771b6b65f356e7edc4a2b08cd621186726646` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,6 +20,8 @@ | |
| r"(?P<source_repo>[A-Za-z0-9_.-]+)\.git@(?P<ref>[A-Za-z0-9_.-]+)" | ||
| ) | ||
| TRACKED_DEPENDENCY_PATHS = ("requirements.txt", "requirements-lock.txt", "pyproject.toml") | ||
| LEGACY_DEPENDENCY_PATHS = ("requirements.txt", "requirements-lock.txt") | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Including Useful? React with 👍 / 👎. |
||
| PYPROJECT_FALLBACK_PATH = "pyproject.toml" | ||
|
|
||
|
|
||
| def _sort_dependency_pins(pins: list[DependencyPin]) -> list[DependencyPin]: | ||
|
|
@@ -126,6 +128,40 @@ def parse_dependency_pins(consumer_repo: str, path: str, text: str) -> list[Depe | |
| ] | ||
|
|
||
|
|
||
| def _parse_repo_pins(projects_root: Path, consumer_repo: str, relative_path: str) -> list[DependencyPin]: | ||
| path = projects_root / consumer_repo / relative_path | ||
| if not path.is_file(): | ||
| return [] | ||
| return parse_dependency_pins(consumer_repo, relative_path, path.read_text(encoding="utf-8")) | ||
|
|
||
|
|
||
| def _fallback_path_for_legacy_requirements(projects_root: Path, consumer_repo: str, expected_path: str) -> str | None: | ||
| if expected_path not in LEGACY_DEPENDENCY_PATHS: | ||
| return None | ||
| pyproject_path = projects_root / consumer_repo / PYPROJECT_FALLBACK_PATH | ||
| if pyproject_path.is_file(): | ||
| return PYPROJECT_FALLBACK_PATH | ||
| return None | ||
|
|
||
|
|
||
| def _compare_dependency_pins( | ||
| *, expected_pins: list[DependencyPin], actual_pins: list[DependencyPin], issues: list[str] | ||
| ) -> None: | ||
| actual_by_key = {pin.key: pin for pin in actual_pins} | ||
| expected_by_key = {pin.key: pin for pin in expected_pins} | ||
|
|
||
| for key, expected in sorted(expected_by_key.items()): | ||
| actual = actual_by_key.get(key) | ||
| if actual is None: | ||
| issues.append(f"missing {expected.label()} expected @{expected.ref}") | ||
| elif actual.ref != expected.ref: | ||
| issues.append(f"ref mismatch {expected.label()}: expected @{expected.ref}, found @{actual.ref}") | ||
|
|
||
| for key, actual in sorted(actual_by_key.items()): | ||
| if key not in expected_by_key: | ||
| issues.append(f"untracked internal dependency {actual.label()} @{actual.ref}") | ||
|
|
||
|
|
||
| def check_matrix(*, matrix_pins: list[DependencyPin], projects_root: Path) -> MatrixReport: | ||
| expected_by_file: dict[tuple[str, str], list[DependencyPin]] = {} | ||
| for pin in matrix_pins: | ||
|
|
@@ -137,23 +173,30 @@ def check_matrix(*, matrix_pins: list[DependencyPin], projects_root: Path) -> Ma | |
| for (consumer_repo, relative_path), expected_pins in sorted(expected_by_file.items()): | ||
| path = projects_root / consumer_repo / relative_path | ||
| if not path.exists(): | ||
| fallback_path = _fallback_path_for_legacy_requirements(projects_root, consumer_repo, relative_path) | ||
| if fallback_path is not None: | ||
| fallback_pins = _parse_repo_pins(projects_root, consumer_repo, fallback_path) | ||
| if fallback_pins: | ||
| _compare_dependency_pins( | ||
| expected_pins=[ | ||
| DependencyPin( | ||
| consumer_repo=pin.consumer_repo, | ||
| path=fallback_path, | ||
| package=pin.package, | ||
| source_repo=pin.source_repo, | ||
| ref=pin.ref, | ||
| ) | ||
| for pin in expected_pins | ||
| ], | ||
| actual_pins=fallback_pins, | ||
| issues=issues, | ||
|
Comment on lines
+189
to
+192
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a repo already has matrix rows for Useful? React with 👍 / 👎. |
||
| ) | ||
| continue | ||
| missing_files.append(f"{consumer_repo}/{relative_path}") | ||
| continue | ||
| checked_files += 1 | ||
| actual_pins = parse_dependency_pins(consumer_repo, relative_path, path.read_text(encoding="utf-8")) | ||
| actual_by_key = {pin.key: pin for pin in actual_pins} | ||
| expected_by_key = {pin.key: pin for pin in expected_pins} | ||
|
|
||
| for key, expected in sorted(expected_by_key.items()): | ||
| actual = actual_by_key.get(key) | ||
| if actual is None: | ||
| issues.append(f"missing {expected.label()} expected @{expected.ref}") | ||
| elif actual.ref != expected.ref: | ||
| issues.append(f"ref mismatch {expected.label()}: expected @{expected.ref}, found @{actual.ref}") | ||
|
|
||
| for key, actual in sorted(actual_by_key.items()): | ||
| if key not in expected_by_key: | ||
| issues.append(f"untracked internal dependency {actual.label()} @{actual.ref}") | ||
| _compare_dependency_pins(expected_pins=expected_pins, actual_pins=actual_pins, issues=issues) | ||
|
|
||
| return MatrixReport(checked_files=checked_files, missing_files=missing_files, issues=issues) | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| [qsl] | ||
| # QuantRuntimeSettings 作为 QSL 中央仓库 | ||
| repo = "QuantRuntimeSettings" | ||
| bundle = "2026.07.0" | ||
| compat = "2026.07.0" | ||
| tier = "ops/tooling" | ||
| upgrade_ring = "ring_e" | ||
| allow_legacy = false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When this new bundle is used against the current consumers tracked by this repo, it rejects the existing baseline rather than representing it: for example, this sets
QuantPlatformKitto0063af..., whileinternal_dependency_matrix.jsonstill records active consumers at6d367...,b0eacd...,d786..., ande86554b. Because this commit does not update those consumers or the existing matrix in the same wave, addingqsl.tomlwithbundle = "2026.07.0"to any current consumer will immediately produce bundle pin mismatches.Useful? React with 👍 / 👎.