From 87e103b5cf3ee435e0f63916586770c442ddf7f1 Mon Sep 17 00:00:00 2001 From: George Berkshire <41768719+ageorge156@users.noreply.github.com> Date: Thu, 20 Aug 2026 20:21:01 +0800 Subject: [PATCH 1/4] test: add reproducer for VPN rules module metadata --- tests/test_module_spec.py | 146 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 tests/test_module_spec.py diff --git a/tests/test_module_spec.py b/tests/test_module_spec.py new file mode 100644 index 0000000..d82cec2 --- /dev/null +++ b/tests/test_module_spec.py @@ -0,0 +1,146 @@ +from __future__ import annotations + +import json +import sys +import unittest +from pathlib import Path, PurePosixPath + + +ROOT = Path(__file__).resolve().parents[1] +SPEC_PATH = ROOT / "MODULE_SPEC.yaml" +sys.path.insert(0, str(ROOT / "scripts")) + +from gen_clash_verge_script import load_payload, to_rule # noqa: E402 + + +class ModuleSpecTests(unittest.TestCase): + def test_module_spec_declares_rule_authority_and_safe_context(self) -> None: + spec = json.loads(SPEC_PATH.read_text(encoding="utf-8")) + + self.assertEqual( + set(spec), + { + "schema_version", + "module_id", + "authority", + "repository", + "bounded_context", + "capabilities", + "data", + "contracts", + "dependencies", + "agent_context", + "verification", + }, + ) + self.assertEqual(spec["schema_version"], 1) + self.assertEqual(spec["module_id"], "vpn-rules") + self.assertEqual( + spec["authority"], + { + "scope": "module_metadata", + "subject": "vpn-rules", + "owner": "vpn-rules-owner", + "source": "MODULE_SPEC.yaml", + "revision": 1, + "effective_from": "2026-08-20T00:00:00+08:00", + }, + ) + self.assertEqual( + spec["repository"], + { + "name": "vpn-rules", + "workspace_id": None, + "type": "infrastructure", + "maturity": "operational", + }, + ) + + boundary = spec["bounded_context"] + self.assertEqual(boundary["domain"], "network-routing-policy") + self.assertIn("single source of truth", boundary["responsibility"].lower()) + prohibited = " ".join(boundary["prohibited_responsibilities"]).lower() + self.assertIn("production", prohibited) + self.assertIn("credential", prohibited) + self.assertIn("proxy subscription", prohibited) + + self.assertEqual( + {capability["id"] for capability in spec["capabilities"]}, + { + "direct-routing-rule-authoring", + "clash-verge-script-generation", + "router-rule-projection", + }, + ) + self.assertEqual(spec["contracts"]["consumes"], []) + self.assertEqual(spec["dependencies"], []) + provided = { + contract["contract_id"]: contract + for contract in spec["contracts"]["provides"] + } + self.assertEqual( + set(provided), + { + "mihomo-direct-rule-provider", + "router-direct-rules", + "router-direct-plain-list", + }, + ) + self.assertEqual( + {contract["version"] for contract in provided.values()}, {"1.0.0"} + ) + for contract in provided.values(): + self.assertTrue((ROOT / contract["path"]).is_file()) + + context = spec["agent_context"] + self.assertLessEqual(context["max_default_tokens"], 6000) + self.assertEqual( + {entry["path"] for entry in context["default_entrypoints"]}, + {"README.md", "direct.yaml"}, + ) + for entry in context["default_entrypoints"]: + self.assertTrue((ROOT / entry["path"]).is_file()) + self.assertIn("router", context["excluded_paths"]) + for value in context["excluded_paths"]: + path = PurePosixPath(value) + self.assertFalse(path.is_absolute()) + self.assertNotIn("..", path.parts) + + self.assertEqual( + spec["verification"], + { + "commands": [ + { + "id": "rule-contract-tests", + "argv": [ + "python3", + "-m", + "unittest", + "discover", + "-s", + "tests", + "-v", + ], + "cwd": ".", + "network": False, + "required": True, + } + ] + }, + ) + + def test_checked_in_router_projections_match_the_rule_source(self) -> None: + payload = load_payload(ROOT / "direct.yaml") + plain = (ROOT / "router/direct-plain.txt").read_text(encoding="utf-8") + router = (ROOT / "router/direct-rules.yaml").read_text(encoding="utf-8") + + self.assertEqual(plain, "\n".join(payload) + "\n") + self.assertEqual( + router.split("rules:\n", 1)[1], + "\n".join(f" - {to_rule(entry)}" for entry in payload) + "\n", + ) + self.assertTrue(all(",DIRECT" not in entry for entry in payload)) + + +if __name__ == "__main__": + unittest.main() From ac93ea1b031f39b5d534a0ee884266588b9790b7 Mon Sep 17 00:00:00 2001 From: George Berkshire <41768719+ageorge156@users.noreply.github.com> Date: Thu, 20 Aug 2026 20:21:41 +0800 Subject: [PATCH 2/4] feat(architecture): publish VPN rules module metadata --- MODULE_SPEC.yaml | 120 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 MODULE_SPEC.yaml diff --git a/MODULE_SPEC.yaml b/MODULE_SPEC.yaml new file mode 100644 index 0000000..4d9dad0 --- /dev/null +++ b/MODULE_SPEC.yaml @@ -0,0 +1,120 @@ +{ + "schema_version": 1, + "module_id": "vpn-rules", + "authority": { + "scope": "module_metadata", + "subject": "vpn-rules", + "owner": "vpn-rules-owner", + "source": "MODULE_SPEC.yaml", + "revision": 1, + "effective_from": "2026-08-20T00:00:00+08:00" + }, + "repository": { + "name": "vpn-rules", + "workspace_id": null, + "type": "infrastructure", + "maturity": "operational" + }, + "bounded_context": { + "domain": "network-routing-policy", + "responsibility": "Own the single source of truth for direct-routing rules and deterministic projections for supported clients.", + "prohibited_responsibilities": [ + "Applying rules to production clients, routers, VPN services, or proxy runtimes", + "Owning or exposing proxy subscription credentials, provider tokens, or device secrets", + "Claiming generated or checked-in artifacts are active production configuration" + ] + }, + "capabilities": [ + { + "id": "direct-routing-rule-authoring", + "summary": "Maintain provider-neutral direct-routing entries in the authoritative mihomo rule-provider source.", + "status": "operational" + }, + { + "id": "clash-verge-script-generation", + "summary": "Render source entries as idempotent DIRECT rules for Clash Verge Script.js.", + "status": "operational" + }, + { + "id": "router-rule-projection", + "summary": "Generate policy-bearing YAML and policy-free plain-text projections for router consumers.", + "status": "operational" + } + ], + "data": { + "owns": [ + { + "asset_id": "direct-routing-rule-source", + "kind": "reference", + "classification": "restricted" + }, + { + "asset_id": "generated-router-rule-projections", + "kind": "artifact", + "classification": "restricted" + } + ] + }, + "contracts": { + "provides": [ + { + "contract_id": "mihomo-direct-rule-provider", + "kind": "artifact", + "version": "1.0.0", + "path": "direct.yaml", + "compatibility": "backward" + }, + { + "contract_id": "router-direct-rules", + "kind": "artifact", + "version": "1.0.0", + "path": "router/direct-rules.yaml", + "compatibility": "exact" + }, + { + "contract_id": "router-direct-plain-list", + "kind": "artifact", + "version": "1.0.0", + "path": "router/direct-plain.txt", + "compatibility": "exact" + } + ], + "consumes": [] + }, + "dependencies": [], + "agent_context": { + "default_entrypoints": [ + { + "path": "README.md", + "purpose": "Repository purpose, supported consumers, generation commands, and maintenance flow." + }, + { + "path": "direct.yaml", + "purpose": "Authoritative direct-routing rule source and provider format." + } + ], + "excluded_paths": [ + "router" + ], + "max_default_tokens": 6000 + }, + "verification": { + "commands": [ + { + "id": "rule-contract-tests", + "argv": [ + "python3", + "-m", + "unittest", + "discover", + "-s", + "tests", + "-v" + ], + "cwd": ".", + "network": false, + "required": true + } + ] + } +} From 80a5199150ca1303f2ae487f4f083e697451e0f4 Mon Sep 17 00:00:00 2001 From: George Berkshire <41768719+ageorge156@users.noreply.github.com> Date: Thu, 20 Aug 2026 20:22:15 +0800 Subject: [PATCH 3/4] test: add reproducer for missing lite CI contract --- tests/test_ci_contract.py | 41 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 tests/test_ci_contract.py diff --git a/tests/test_ci_contract.py b/tests/test_ci_contract.py new file mode 100644 index 0000000..c76458e --- /dev/null +++ b/tests/test_ci_contract.py @@ -0,0 +1,41 @@ +from __future__ import annotations + +import re +import unittest +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] + + +class CiContractTests(unittest.TestCase): + def test_ci_publishes_one_offline_lite_gate(self) -> None: + workflow = (ROOT / ".gitea/workflows/ci.yml").read_text(encoding="utf-8") + job_block = workflow.split("jobs:", 1)[1] + + self.assertEqual( + re.findall(r"(?m)^ ([a-z][a-z0-9_-]*):\s*$", job_block), + ["lite"], + ) + self.assertIn( + "actions/checkout@524e936cd9e579adf00e308bfdf971aebc7de09e", + workflow, + ) + self.assertIn("persist-credentials: false", workflow) + self.assertIn( + "python3 -m unittest discover -s tests -v", + workflow, + ) + for forbidden in ( + "actions/checkout@v", + "apt ", + "pip ", + "curl ", + "wget ", + "docker pull", + ): + self.assertNotIn(forbidden, workflow) + + +if __name__ == "__main__": + unittest.main() From b1885cace81a1fa7f7d0d52740cac23093372bff Mon Sep 17 00:00:00 2001 From: George Berkshire <41768719+ageorge156@users.noreply.github.com> Date: Thu, 20 Aug 2026 20:22:52 +0800 Subject: [PATCH 4/4] fix(ci): publish offline lite verification --- .gitea/workflows/ci.yml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index fc5fecb..ed33e18 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -1,7 +1,5 @@ -# Gitea Actions CI 模板(统一极简合规门禁) -# 复制为 /.gitea/workflows/ci.yml 后即可用;无需调整。 -# 说明:runner 容器无外网且无 pip(Gitea 实例限制),CI 只做本地可完成的合规检查; -# 依赖安装与测试一律在本地运行(开发机上完成)。 +# Gitea Actions CI(统一离线 lite 门禁) +# Runner 无外网且无 pip;这里只运行标准库测试和本地合规检查。 name: CI on: @@ -18,13 +16,18 @@ permissions: contents: read jobs: - check: + lite: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - name: Checkout + 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 -m unittest discover -s tests -v