diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..4c9fe42 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,32 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + lite: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@524e936cd9e579adf00e308bfdf971aebc7de09e + with: + persist-credentials: false + - name: Gitea 合规校验 + run: | + if git ls-tree -r HEAD | awk '$1==160000' | grep -q .; then echo "HEAD 含 gitlink"; exit 1; fi + if git ls-files .DS_Store | grep -q .; then echo "跟踪 .DS_Store"; exit 1; fi + if git grep -n -I -E 'sk-[A-Za-z0-9]{20,}|AKIA[0-9A-Z]{16}|ghp_[A-Za-z0-9]{36}|xox[baprs]-[A-Za-z0-9-]{10,}' HEAD | grep -q .; then echo "检出疑似凭证"; exit 1; fi + echo "Gitea 合规校验通过" + - name: 架构模块契约测试 + run: python3 tests/governance/test_module_spec.py + - name: Syntax check + run: git ls-files -z '*.py' | xargs -0 python3 -m py_compile diff --git a/MODULE_SPEC.yaml b/MODULE_SPEC.yaml new file mode 100644 index 0000000..7d85c2a --- /dev/null +++ b/MODULE_SPEC.yaml @@ -0,0 +1,41 @@ +{ + "schema_version": 1, + "module_id": "quant_engine", + "authority": {"scope": "module_metadata", "subject": "quant_engine", "owner": "quant-engine-owner", "source": "MODULE_SPEC.yaml", "revision": 1, "effective_from": "2026-08-20T00:00:00+08:00"}, + "repository": {"name": "quant_engine", "workspace_id": "researchhub", "type": "research_engine", "maturity": "operational"}, + "bounded_context": { + "domain": "quantitative-research-engine", + "responsibility": "Own reusable quantitative research calculations for factors, indicators, execution simulation, backtesting, portfolio decomposition, risk, and performance metrics.", + "prohibited_responsibilities": [ + "Presenting research calculations, simulations, backtests, or metrics as investment advice or guaranteed performance", + "Submitting live orders, routing trades, managing brokerage accounts, or claiming transaction execution", + "Owning market-data source facts, research-result publication, or platform presentation state", + "Loading provider credentials, brokerage credentials, or production secrets", + "Changing financial model semantics through module metadata" + ] + }, + "capabilities": [ + {"id": "factor-and-indicator-calculation", "summary": "Calculate reusable alpha factors and technical indicators from caller-supplied data.", "status": "operational"}, + {"id": "execution-simulation", "summary": "Simulate costs, slippage, market constraints, fills, NAV, and PnL without live order routing.", "status": "operational"}, + {"id": "portfolio-backtesting", "summary": "Run weight-based backtests and benchmark comparisons.", "status": "operational"}, + {"id": "risk-and-performance-analysis", "summary": "Calculate portfolio decomposition, risk contribution, and performance statistics.", "status": "operational"} + ], + "data": {"owns": [ + {"asset_id": "quantitative-model-implementations", "kind": "model", "classification": "internal"}, + {"asset_id": "simulation-and-metric-results", "kind": "artifact", "classification": "confidential"} + ]}, + "contracts": {"provides": [], "consumes": []}, + "dependencies": [], + "agent_context": { + "default_entrypoints": [ + {"path": "README.md", "purpose": "Current engine scope, modules, dependencies, and non-live usage examples."}, + {"path": "pyproject.toml", "purpose": "Supported Python version, package version, dependencies, and quality configuration."} + ], + "excluded_paths": ["graphify-out", ".venv", "build", "dist"], + "max_default_tokens": 8000 + }, + "verification": {"commands": [ + {"id": "module-metadata", "argv": ["python3", "tests/governance/test_module_spec.py"], "cwd": ".", "network": false, "required": true}, + {"id": "unit-tests", "argv": ["python3", "-m", "pytest"], "cwd": ".", "network": false, "required": true} + ]} +} diff --git a/tests/governance/test_ci_contract.py b/tests/governance/test_ci_contract.py new file mode 100644 index 0000000..3d69e18 --- /dev/null +++ b/tests/governance/test_ci_contract.py @@ -0,0 +1,24 @@ +from __future__ import annotations + +import re +import unittest +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[2] + + +class CiContractTests(unittest.TestCase): + def test_ci_is_one_dependency_free_lite_gate(self) -> None: + workflow = (ROOT / ".gitea/workflows/ci.yml").read_text(encoding="utf-8") + jobs = workflow.split("jobs:", 1)[1] + self.assertEqual(re.findall(r"(?m)^ ([a-z][a-z0-9_-]*):\s*$", jobs), ["lite"]) + self.assertIn("actions/checkout@524e936cd9e579adf00e308bfdf971aebc7de09e", workflow) + self.assertIn("persist-credentials: false", workflow) + self.assertIn("python3 tests/governance/test_module_spec.py", workflow) + for forbidden in ("setup-python", "pip ", "curl ", "wget ", "docker pull"): + self.assertNotIn(forbidden, workflow) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/governance/test_module_spec.py b/tests/governance/test_module_spec.py new file mode 100644 index 0000000..e101743 --- /dev/null +++ b/tests/governance/test_module_spec.py @@ -0,0 +1,32 @@ +from __future__ import annotations + +import json +import unittest +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[2] + + +class ModuleSpecTests(unittest.TestCase): + def test_module_spec_declares_pure_research_engine_boundary(self) -> None: + spec = json.loads((ROOT / "MODULE_SPEC.yaml").read_text(encoding="utf-8")) + self.assertEqual(spec["module_id"], "quant_engine") + self.assertEqual(spec["authority"]["subject"], spec["module_id"]) + self.assertEqual(spec["repository"]["type"], "research_engine") + self.assertEqual(spec["bounded_context"]["domain"], "quantitative-research-engine") + prohibited = " ".join(spec["bounded_context"]["prohibited_responsibilities"]).lower() + for term in ("investment advice", "live order", "credentials", "source facts"): + self.assertIn(term, prohibited) + self.assertEqual(spec["contracts"], {"provides": [], "consumes": []}) + self.assertEqual(spec["dependencies"], []) + self.assertTrue( + all( + command["required"] and not command["network"] + for command in spec["verification"]["commands"] + ) + ) + + +if __name__ == "__main__": + unittest.main()