Skip to content

Commit a692929

Browse files
Pigbibicodex
andcommitted
fix: support transitional qsl compatibility metadata
Co-Authored-By: Codex <noreply@openai.com>
1 parent a2f5938 commit a692929

3 files changed

Lines changed: 61 additions & 18 deletions

File tree

docs/qsl_compat_upgrade.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ compat = "2026.07.0" # 兼容检查入口(与 bundle 相同)
3131
tier = "ops/tooling"
3232
upgrade_ring = "ring_e"
3333
allow_legacy = false # 需要临时兼容时可先放开
34+
enforce_bundle = true # 过渡仓库可设 false;ref drift 会降级为 warning
3435
```
3536

3637
2.`pyproject.toml`/`uv.lock` 中用完整 SHA 固定 QuantStrategyLab 的内部 git 依赖。

scripts/check_qsl_compat.py

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,29 @@ def _read_toml(path: Path) -> dict[str, Any]:
4242
return tomllib.load(handle)
4343

4444

45+
def _get_bundle(config: dict[str, Any]) -> str:
46+
bundle = config.get("bundle") or config.get("compat")
47+
if isinstance(bundle, dict):
48+
bundle = bundle.get("bundle")
49+
if not isinstance(bundle, str) or not bundle.strip():
50+
raise ValueError("qsl.bundle (or qsl.compat.bundle) is required")
51+
return bundle.strip()
52+
53+
54+
def _get_upgrade_ring(config: dict[str, Any]) -> str:
55+
upgrade_ring = config.get("upgrade_ring", config.get("ring"))
56+
if upgrade_ring is None or str(upgrade_ring).strip() == "":
57+
raise ValueError("qsl.upgrade_ring (or qsl.ring) is required")
58+
return str(upgrade_ring).strip()
59+
60+
61+
def _get_enforce_bundle(config: dict[str, Any]) -> bool:
62+
compat = config.get("compat")
63+
if isinstance(compat, dict) and "enforce_bundle" in compat:
64+
return bool(compat["enforce_bundle"])
65+
return bool(config.get("enforce_bundle", True))
66+
67+
4568
def _load_qsl_config(repo_root: Path) -> dict[str, str | bool]:
4669
qsl_path = repo_root / "qsl.toml"
4770
if not qsl_path.exists():
@@ -52,25 +75,23 @@ def _load_qsl_config(repo_root: Path) -> dict[str, str | bool]:
5275
if not isinstance(config, dict):
5376
raise TypeError("qsl section must be a table")
5477

55-
bundle = config.get("bundle") or config.get("compat")
56-
if not isinstance(bundle, str) or not bundle.strip():
57-
raise ValueError("qsl.bundle (or qsl.compat) is required")
78+
bundle = _get_bundle(config)
5879

5980
tier = config.get("tier")
6081
if not isinstance(tier, str) or not tier.strip():
6182
raise ValueError("qsl.tier is required")
6283

63-
upgrade_ring = config.get("upgrade_ring")
64-
if not isinstance(upgrade_ring, str) or not upgrade_ring.strip():
65-
raise ValueError("qsl.upgrade_ring is required")
84+
upgrade_ring = _get_upgrade_ring(config)
6685

6786
allow_legacy = bool(config.get("allow_legacy", False))
87+
enforce_bundle = _get_enforce_bundle(config)
6888

6989
return {
70-
"bundle": bundle.strip(),
90+
"bundle": bundle,
7191
"tier": tier.strip(),
72-
"upgrade_ring": str(upgrade_ring).strip(),
92+
"upgrade_ring": upgrade_ring,
7393
"allow_legacy": allow_legacy,
94+
"enforce_bundle": enforce_bundle,
7495
"qsl_path": qsl_path.as_posix(),
7596
}
7697

@@ -148,12 +169,14 @@ def _check(repo_root: Path, compat_root: Path) -> tuple[bool, list[str], list[st
148169
tier = str(qsl_cfg["tier"])
149170
upgrade_ring = str(qsl_cfg["upgrade_ring"])
150171
allow_legacy = bool(qsl_cfg["allow_legacy"])
172+
enforce_bundle = bool(qsl_cfg["enforce_bundle"])
151173
qsl_path = str(qsl_cfg["qsl_path"])
152174

153175
notes.append(f"qsl={qsl_path}")
154176
notes.append(f"bundle={bundle}")
155177
notes.append(f"tier={tier}")
156178
notes.append(f"upgrade_ring={upgrade_ring}")
179+
notes.append(f"enforce_bundle={enforce_bundle}")
157180

158181
bundle_refs = _load_bundle(compat_root, bundle)
159182

@@ -172,31 +195,39 @@ def _check(repo_root: Path, compat_root: Path) -> tuple[bool, list[str], list[st
172195
f"{legacy_ref.repo}@{legacy_ref.ref}"
173196
)
174197
continue
175-
_validate_ref(legacy_ref, bundle_refs[legacy_ref.repo], issues)
198+
_validate_ref(legacy_ref, bundle_refs[legacy_ref.repo], issues, warnings, enforce_bundle)
176199

177200
discovered = _gather_repo_refs(repo_root)
178201
for pin in discovered:
179202
if pin.repo not in bundle_refs:
180203
issues.append(f"unmanaged qsl dependency in {pin.source}:{pin.line_no}: {pin.repo}@{pin.ref}")
181204
continue
182205
expected_ref = bundle_refs[pin.repo]
183-
_validate_ref(pin, expected_ref, issues)
206+
_validate_ref(pin, expected_ref, issues, warnings, enforce_bundle)
184207

185208
return (len(issues) == 0, issues, warnings, notes)
186209

187210

188-
def _validate_ref(pin: GitRef, expected_ref: str, issues: list[str]) -> None:
211+
def _validate_ref(pin: GitRef, expected_ref: str, issues: list[str], warnings: list[str], enforce_bundle: bool) -> None:
189212
if _is_main_ref(pin.ref):
190213
issues.append(f"forbidden ref 'main' in {pin.source}:{pin.line_no}: {pin.repo}")
191214
return
192215
if not _is_full_sha(pin.ref):
193-
issues.append(f"forbidden short/invalid ref '{pin.ref}' in {pin.source}:{pin.line_no}: {pin.repo}")
216+
message = f"forbidden short/invalid ref '{pin.ref}' in {pin.source}:{pin.line_no}: {pin.repo}"
217+
if enforce_bundle:
218+
issues.append(message)
219+
else:
220+
warnings.append(message)
194221
return
195222
if pin.ref != expected_ref:
196-
issues.append(
223+
message = (
197224
f"bundle pin mismatch for {pin.repo} in {pin.source}:{pin.line_no}: "
198225
f"expected {expected_ref}, found {pin.ref}"
199226
)
227+
if enforce_bundle:
228+
issues.append(message)
229+
else:
230+
warnings.append(message)
200231

201232

202233
def build_parser() -> argparse.ArgumentParser:

scripts/render_qsl_dependency_graph.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,20 +31,31 @@ def _read_toml(path: Path) -> dict:
3131
return tomllib.load(handle)
3232

3333

34+
def _get_bundle(config: dict) -> str:
35+
bundle = config.get("bundle") or config.get("compat")
36+
if isinstance(bundle, dict):
37+
bundle = bundle.get("bundle")
38+
if not isinstance(bundle, str) or not bundle.strip():
39+
raise ValueError("qsl.bundle (or qsl.compat.bundle) is required")
40+
return bundle.strip()
41+
42+
43+
def _get_upgrade_ring(config: dict) -> str:
44+
upgrade_ring = config.get("upgrade_ring", config.get("ring"))
45+
return "" if upgrade_ring is None else str(upgrade_ring).strip()
46+
47+
3448
def _load_qsl_config(repo_root: Path) -> dict[str, str | bool]:
3549
qsl_path = repo_root / "qsl.toml"
3650
payload = _read_toml(qsl_path)
3751
config = payload.get("qsl", payload)
3852
if not isinstance(config, dict):
3953
raise TypeError("qsl section must be a table")
4054

41-
bundle = config.get("bundle") or config.get("compat")
42-
if not isinstance(bundle, str) or not bundle.strip():
43-
raise ValueError("qsl.bundle (or qsl.compat) is required")
4455
return {
45-
"bundle": bundle.strip(),
56+
"bundle": _get_bundle(config),
4657
"tier": str(config.get("tier", "")),
47-
"upgrade_ring": str(config.get("upgrade_ring", "")),
58+
"upgrade_ring": _get_upgrade_ring(config),
4859
}
4960

5061

0 commit comments

Comments
 (0)