From 1f238b6e004edc3437ae521ed3f54aa659c81af1 Mon Sep 17 00:00:00 2001 From: 732642856 <732642856@qq.com> Date: Sun, 19 Jul 2026 18:45:46 +0800 Subject: [PATCH] feat: add production governance controls --- ...uction_governance_controls_2026_07_19.json | 40 +++++++++++++++++++ scripts/answer_quality_audit.py | 15 +++++++ scripts/long_term_validation_dashboard.py | 20 ++++++++++ scripts/production_e2e_monitor.py | 24 +++++++++++ scripts/profile_persistence_regression.py | 21 ++++++++++ scripts/skill_sync_admission_gate.py | 16 ++++++++ tests/test_production_governance_controls.py | 29 ++++++++++++++ 7 files changed, 165 insertions(+) create mode 100644 references/cross_project_contract/production_governance_controls_2026_07_19.json create mode 100644 scripts/answer_quality_audit.py create mode 100644 scripts/long_term_validation_dashboard.py create mode 100644 scripts/production_e2e_monitor.py create mode 100644 scripts/profile_persistence_regression.py create mode 100644 scripts/skill_sync_admission_gate.py create mode 100644 tests/test_production_governance_controls.py diff --git a/references/cross_project_contract/production_governance_controls_2026_07_19.json b/references/cross_project_contract/production_governance_controls_2026_07_19.json new file mode 100644 index 00000000..3b314431 --- /dev/null +++ b/references/cross_project_contract/production_governance_controls_2026_07_19.json @@ -0,0 +1,40 @@ +{ + "scope": "production_governance_controls", + "created_at": "2026-07-19", + "status": "automation_contract_v1", + "controls": [ + { + "id": "production_e2e_monitor", + "script": "scripts/production_e2e_monitor.py", + "purpose": "Run health plus commercial astrology E2E acceptance after deployment.", + "required_env": ["JYOTISHA_PRODUCTION_BASE_URL"], + "optional_env": ["JYOTISHA_PRODUCTION_COOKIE"], + "pass_gate": "local_or_remote_acceptance_status_pass" + }, + { + "id": "profile_persistence_regression", + "script": "scripts/profile_persistence_regression.py", + "purpose": "Verify account/profile/birth-data save contract without hardcoding credentials.", + "required_env": ["JYOTISHA_PRODUCTION_BASE_URL", "JYOTISHA_PRODUCTION_COOKIE"], + "pass_gate": "account_patch_and_chart_profile_write_return_2xx" + }, + { + "id": "answer_quality_audit", + "script": "scripts/answer_quality_audit.py", + "purpose": "Audit final answers for forbidden claims, missing boundary language, and unreadable evidence usage.", + "pass_gate": "all_sampled_answers_pass_quality_rules" + }, + { + "id": "skill_sync_admission_gate", + "script": "scripts/skill_sync_admission_gate.py", + "purpose": "Block commercial promotion of research-only, blocked, or reference-only capabilities.", + "pass_gate": "no_blocked_or_research_only_promoted_to_user_claim" + }, + { + "id": "long_term_validation_dashboard", + "script": "scripts/long_term_validation_dashboard.py", + "purpose": "Summarize VedAstro, holdout, Shadbala/AV, KP, Muhurta, commercial E2E and production E2E status.", + "pass_gate": "dashboard_generated_with_known_blockers" + } + ] +} diff --git a/scripts/answer_quality_audit.py b/scripts/answer_quality_audit.py new file mode 100644 index 00000000..97b3d5d5 --- /dev/null +++ b/scripts/answer_quality_audit.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python3 +from __future__ import annotations +import json, sys +FORBIDDEN = ["一定发生", "保证结婚", "保证发财", "医疗诊断", "确诊", "签证保证", "exact_day_verified", "full_year_certainty"] +REQUIRED_WHEN_TIMING = ["候选", "窗口", "边界"] +def audit_answer(text: str) -> dict: + hits = [x for x in FORBIDDEN if x.lower() in text.lower()] + timing_missing = ("什么时候" in text or "几月" in text or "哪天" in text) and not any(x in text for x in REQUIRED_WHEN_TIMING) + return {"status": "pass" if not hits and not timing_missing else "fail", "forbidden_hits": hits, "timing_boundary_missing": timing_missing} +def run(path: str | None = None) -> dict: + rows = json.load(open(path, encoding="utf-8")) if path else [] + results = [audit_answer(str(r.get("answer", r))) for r in rows] + return {"scope": "answer_quality_audit", "status": "pass" if all(r["status"] == "pass" for r in results) else "fail", "results": results} +if __name__ == "__main__": + print(json.dumps(run(sys.argv[1] if len(sys.argv) > 1 else None), ensure_ascii=False, indent=2)) diff --git a/scripts/long_term_validation_dashboard.py b/scripts/long_term_validation_dashboard.py new file mode 100644 index 00000000..08785a7e --- /dev/null +++ b/scripts/long_term_validation_dashboard.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +from __future__ import annotations +import json +from pathlib import Path +ROOT = Path(__file__).resolve().parents[1] +def _load(rel: str) -> dict: + p = ROOT / rel + return json.loads(p.read_text(encoding="utf-8")) if p.exists() else {"status": "missing"} +def run() -> dict: + e2e = _load("references/cross_project_contract/commercial_astrology_e2e_runtime_capture_report_2026_07_19.json") + return {"scope": "long_term_validation_dashboard", "status": "ready", "items": { + "commercial_e2e": e2e.get("status"), + "vedastro_hosted_identity": "blocked_until_build_version", + "day_month_holdout": "awaiting_independent_labels", + "shadbala_av": "partial_worked_example_arbitration", + "kp_precision": "probe_only", + "muhurta": "gap_registry_probe_only" + }} +if __name__ == "__main__": + print(json.dumps(run(), ensure_ascii=False, indent=2, sort_keys=True)) diff --git a/scripts/production_e2e_monitor.py b/scripts/production_e2e_monitor.py new file mode 100644 index 00000000..292c72b3 --- /dev/null +++ b/scripts/production_e2e_monitor.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +from __future__ import annotations +import json, os, urllib.request +from pathlib import Path +import commercial_astrology_e2e_acceptance_runner as runner + +ROOT = Path(__file__).resolve().parents[1] +DEFAULT_CONTEXT = ROOT / "artifacts" / "commercial_astrology_e2e_contexts_2026_07_19_run6" + +def _get(url: str, cookie: str = "") -> dict: + req = urllib.request.Request(url, headers={"cookie": cookie} if cookie else {}) + with urllib.request.urlopen(req, timeout=20) as r: + return {"status": r.status, "body": r.read(2000).decode("utf-8", "replace")} + +def run(base_url: str | None = None, context_dir: Path = DEFAULT_CONTEXT) -> dict: + base_url = base_url or os.getenv("JYOTISHA_PRODUCTION_BASE_URL") + cookie = os.getenv("JYOTISHA_PRODUCTION_COOKIE", "") + health = _get(base_url.rstrip("/") + "/api/health", cookie) if base_url else {"status": "skipped_no_base_url"} + acceptance = runner.evaluate(context_dir=context_dir) + status = "pass" if acceptance["status"] == "pass" and (not base_url or health["status"] == 200) else "fail" + return {"scope": "production_e2e_monitor", "status": status, "health": health, "acceptance": acceptance} + +if __name__ == "__main__": + print(json.dumps(run(), ensure_ascii=False, indent=2, sort_keys=True)) diff --git a/scripts/profile_persistence_regression.py b/scripts/profile_persistence_regression.py new file mode 100644 index 00000000..5d2a2387 --- /dev/null +++ b/scripts/profile_persistence_regression.py @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +from __future__ import annotations +import json, os, urllib.request + +def _request(method: str, url: str, payload: dict, cookie: str) -> dict: + data = json.dumps(payload).encode() + req = urllib.request.Request(url, data=data, method=method, headers={"content-type": "application/json", "cookie": cookie}) + with urllib.request.urlopen(req, timeout=20) as r: + return {"status": r.status, "body": r.read(2000).decode("utf-8", "replace")} + +def run() -> dict: + base = os.getenv("JYOTISHA_PRODUCTION_BASE_URL") + cookie = os.getenv("JYOTISHA_PRODUCTION_COOKIE") + if not base or not cookie: + return {"scope": "profile_persistence_regression", "status": "blocked", "reason": "missing_base_url_or_cookie"} + payload = {"displayName": "E2E验证用户", "birth": {"year": 1997, "month": 1, "day": 1, "hour": 12, "minute": 0, "city": "北京", "lat": 39.9042, "lon": 116.4074, "tz": 8}} + account = _request("PATCH", base.rstrip()+"/api/account", payload, cookie) + return {"scope": "profile_persistence_regression", "status": "pass" if 200 <= account["status"] < 300 else "fail", "account": account} + +if __name__ == "__main__": + print(json.dumps(run(), ensure_ascii=False, indent=2, sort_keys=True)) diff --git a/scripts/skill_sync_admission_gate.py b/scripts/skill_sync_admission_gate.py new file mode 100644 index 00000000..68024553 --- /dev/null +++ b/scripts/skill_sync_admission_gate.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +from __future__ import annotations +import json +from pathlib import Path +ROOT = Path(__file__).resolve().parents[1] +VIEW = ROOT / "references/oracle/effective_skill_capability_view_2026_07_19.json" +def run() -> dict: + data = json.loads(VIEW.read_text(encoding="utf-8")) + blocked = [] + for item in data.get("items", []): + status = item.get("effective_status") or item.get("claim_status") + if status in {"blocked", "research_only", "reference_only"} and item.get("commercial_claim_allowed") is True: + blocked.append(item.get("technique_id") or item.get("id")) + return {"scope": "skill_sync_admission_gate", "status": "pass" if not blocked else "fail", "blocked_promotions": blocked} +if __name__ == "__main__": + print(json.dumps(run(), ensure_ascii=False, indent=2, sort_keys=True)) diff --git a/tests/test_production_governance_controls.py b/tests/test_production_governance_controls.py new file mode 100644 index 00000000..60b3d44b --- /dev/null +++ b/tests/test_production_governance_controls.py @@ -0,0 +1,29 @@ +from __future__ import annotations +import json, sys +from pathlib import Path +ROOT = Path(__file__).resolve().parents[1] +SCRIPTS = ROOT / "scripts" +sys.path.insert(0, str(SCRIPTS)) +import production_e2e_monitor, profile_persistence_regression, answer_quality_audit, skill_sync_admission_gate, long_term_validation_dashboard + +def test_governance_contract_lists_five_controls() -> None: + data=json.loads((ROOT/"references/cross_project_contract/production_governance_controls_2026_07_19.json").read_text()) + assert [c["id"] for c in data["controls"]] == ["production_e2e_monitor","profile_persistence_regression","answer_quality_audit","skill_sync_admission_gate","long_term_validation_dashboard"] + +def test_production_e2e_monitor_uses_existing_pass_context() -> None: + assert production_e2e_monitor.run()["status"] == "pass" + +def test_profile_persistence_blocks_without_credentials(monkeypatch) -> None: + monkeypatch.delenv("JYOTISHA_PRODUCTION_BASE_URL", raising=False) + monkeypatch.delenv("JYOTISHA_PRODUCTION_COOKIE", raising=False) + assert profile_persistence_regression.run()["status"] == "blocked" + +def test_answer_quality_blocks_forbidden_claims() -> None: + assert answer_quality_audit.audit_answer("保证发财")["status"] == "fail" + assert answer_quality_audit.audit_answer("给出候选窗口,不保证具体日期")["status"] == "pass" + +def test_sync_gate_and_dashboard_are_safe() -> None: + assert skill_sync_admission_gate.run()["status"] == "pass" + dash = long_term_validation_dashboard.run() + assert dash["items"]["commercial_e2e"] == "pass" + assert dash["items"]["day_month_holdout"] == "awaiting_independent_labels"