Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ This repository is the central manifest source for QSL compatibility checks.

- `qsl.toml`: current repo QSL metadata (`tier`, `upgrade_ring`, `bundle`).
- `scripts/check_qsl_compat.py`: validate a repo's QSL compliance in `pyproject.toml`/`uv.lock` against central bundle.
- `python/scripts/qslctl.py`: unified QSL version-control CLI for repo checks, workspace checks, ring reports/plans, and matrix generation.
- `scripts/render_qsl_dependency_graph.py`: render dependency graph for review.
- `compat/bundles/2026.07.0.toml`: first compatibility bundle (CalVer).
- `compat/repo-tiers.toml`: repo tier and upgrade ring policy notes.
Expand Down
37 changes: 37 additions & 0 deletions docs/qsl_version_control.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# QSL version control plane

QuantRuntimeSettings is the control plane for QuantStrategyLab internal version
management.

## Source of truth

- `compat/bundles/*.toml` is the source of truth for internal repository commit pins.
- Each consumer repository declares its bundle in `qsl.toml`.
- Consumer files (`pyproject.toml`, `uv.lock`, `requirements.txt`, `constraints.txt`) must match the declared bundle.
- `internal_dependency_matrix.json` is generated from local consumer dependency files; do not hand-edit it except for emergency repair.

## CLI

Use `qslctl` for repository and workspace checks:

```bash
python3 python/scripts/qslctl.py check --repo-root ../UsEquityStrategies
python3 python/scripts/qslctl.py check-all --projects-root /Users/lisiyi/Projects
python3 python/scripts/qslctl.py report --projects-root /Users/lisiyi/Projects
python3 python/scripts/qslctl.py plan --projects-root /Users/lisiyi/Projects
python3 python/scripts/qslctl.py generate-matrix --projects-root /Users/lisiyi/Projects --check
python3 python/scripts/qslctl.py generate-matrix --projects-root /Users/lisiyi/Projects --sync
```

## Rollout policy

1. Create or update a bundle in `compat/bundles/`.
2. Apply that bundle by ring: core → strategy libraries → pipelines/research → runtime platforms → ops/tooling.
3. Regenerate `internal_dependency_matrix.json` after consumer pins are updated.
4. Keep strict checks enabled only after a ring is converged.

## Workspace report / plan

- `qslctl report` is read-only. It groups the current workspace by ring, status, and bundle hotspot.
- `qslctl plan` is read-only. It renders the ring-by-ring convergence order and highlights which repos should be fixed before the next ring starts.
- Use `report` to answer “what is broken right now?” and `plan` to answer “what should we fix first?”
2 changes: 1 addition & 1 deletion internal_dependency_matrix.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@
"path": "requirements.txt",
"package": "us-equity-strategies",
"source_repo": "UsEquityStrategies",
"ref": "cd959e30e91e57697ad878d98799c1afb43bf5fb"
"ref": "1643f37d723c4d0fdc475ce1030b3d911c07aacb"
},
{
"consumer_repo": "InteractiveBrokersPlatform",
Expand Down
31 changes: 20 additions & 11 deletions python/scripts/check_internal_dependency_matrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,18 @@
ROOT = Path(__file__).resolve().parents[2]
DEFAULT_MATRIX_PATH = ROOT / "internal_dependency_matrix.json"
DEFAULT_PROJECTS_ROOT = ROOT.parent
DEPENDENCY_PATTERN = re.compile(
PYPROJECT_DEPENDENCY_PATTERN = re.compile(
r"(?P<package>[A-Za-z0-9_.-]+)\s*@\s*"
r"git\+https://github\.com/QuantStrategyLab/"
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")
UV_DEPENDENCY_PATTERN = re.compile(
r"name\s*=\s*\"(?P<package>[A-Za-z0-9_.-]+)\"[^\n]*"
r"git\s*=\s*\"https://github\.com/QuantStrategyLab/"
r"(?P<source_repo>[A-Za-z0-9_.-]+)\.git\?rev=(?P<ref>[A-Za-z0-9_.-]+)"
Comment on lines +23 to +25

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Parse uv.lock package source stanzas too

When an internal dependency appears only in its [[package]] stanza in uv.lock (for example a transitive locked package has name = "..." followed by source = { git = "https://github.com/QuantStrategyLab/..." } on the next line), this pattern never matches because it requires name and git on the same line. That means generate-matrix and matrix checks ignore those locked QSL refs unless they are also repeated in [package.metadata].requires-dist, allowing transitive internal pins in uv.lock to drift without being reported.

Useful? React with 👍 / 👎.

)
DEPENDENCY_PATTERNS = (PYPROJECT_DEPENDENCY_PATTERN, UV_DEPENDENCY_PATTERN)
TRACKED_DEPENDENCY_PATHS = ("requirements.txt", "requirements-lock.txt", "pyproject.toml", "uv.lock")
LEGACY_DEPENDENCY_PATHS = ("requirements.txt", "requirements-lock.txt")
PYPROJECT_FALLBACK_PATH = "pyproject.toml"

Expand Down Expand Up @@ -116,16 +122,19 @@ def _required_string(item: dict[str, Any], key: str, index: int) -> str:


def parse_dependency_pins(consumer_repo: str, path: str, text: str) -> list[DependencyPin]:
return [
DependencyPin(
consumer_repo=consumer_repo,
path=path,
package=match.group("package"),
source_repo=match.group("source_repo"),
ref=match.group("ref"),
pins: list[DependencyPin] = []
for pattern in DEPENDENCY_PATTERNS:
pins.extend(
DependencyPin(
consumer_repo=consumer_repo,
path=path,
package=match.group("package"),
source_repo=match.group("source_repo"),
ref=match.group("ref"),
)
for match in pattern.finditer(text)
)
for match in DEPENDENCY_PATTERN.finditer(text)
]
return _sort_dependency_pins(pins)


def _parse_repo_pins(projects_root: Path, consumer_repo: str, relative_path: str) -> list[DependencyPin]:
Expand Down
Loading
Loading