From f2a741d36bb68b05ac001f14abdce3e796d5a642 Mon Sep 17 00:00:00 2001 From: 732642856 <732642856@qq.com> Date: Thu, 2 Jul 2026 14:17:18 +0800 Subject: [PATCH] Add interpretation source inventory gate --- mcp_server.py | 177 +++++++++++++++++- .../interpretation_source_inventory_gate.py | 81 ++++++++ scripts/run_quality_gate.py | 2 + ...st_interpretation_source_inventory_gate.py | 57 ++++++ tests/test_mcp_strict_workflow_career.py | 30 ++- tests/test_mcp_strict_workflow_finance.py | 4 + .../test_mcp_strict_workflow_relationship.py | 4 + 7 files changed, 353 insertions(+), 2 deletions(-) create mode 100644 scripts/interpretation_source_inventory_gate.py create mode 100644 tests/test_interpretation_source_inventory_gate.py diff --git a/mcp_server.py b/mcp_server.py index fd46eb91..4773d624 100644 --- a/mcp_server.py +++ b/mcp_server.py @@ -116,21 +116,158 @@ def _load_json_file(path: str) -> Dict[str, Any]: return data if isinstance(data, dict) else {} +def _existing_paths(paths: List[str]) -> List[str]: + return [path for path in paths if _repo_relative_exists(path)] + + +def _build_interpretation_source_inventory(source_refs: List[str]) -> Dict[str, Any]: + primary_truth = [ + "references/interpretation_template_registry.json", + "references/raman-house-judgment-methodology.md", + "references/bphs-ch48-narayana-dasha.md", + "references/mandatory-verification-gate-protocol.md", + "references/real-reading-quality-checklist.md", + ] + frontend_interpretation = [ + "jyotish-app/interpretation.js", + "jyotish-app/analysis-deep.js", + "jyotish-app/planet-house-details-a.js", + "jyotish-app/planet-house-details-b.js", + "jyotish-app/planet-house-details-c.js", + ] + qa_governance = [ + "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-core/resources/qa_rules.md", + ] + reader_validation = [ + "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-reader/resources/chart_reading_rules.md", + "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-reader/resources/validation_rules.md", + ] + yoga_rules = [ + "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-core/resources/yogas.md", + "references/yoga_rules.json", + "jyotish-app/yoga-details-a.js", + "jyotish-app/yoga-details-b.js", + "jyotish-app/yoga-extended.js", + "jyotish-app/yoga-extended-b.js", + ] + saham_rules = [ + "references/saham_rules.json", + ] + reference_layer = [ + "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-core/resources/p1_p12.md", + "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-core/resources/house_framework.md", + *frontend_interpretation, + *qa_governance, + *reader_validation, + *yoga_rules, + *saham_rules, + ] + quarantined_drafts = [ + "docs/research/local_drafts/2026-06/skill_fragment_map_and_source_of_truth_2026_06_26.md", + "docs/research/local_drafts/2026-06/dasha_accuracy_closure_status_2026_06_26.md", + "docs/research/local_drafts/2026-06/dasha_code_only_priority_rerank_2026_06_26.md", + ] + + layers = { + "primary_truth": { + "status": "available", + "promotion_status": "primary_truth", + "source_refs": _existing_paths(primary_truth), + "missing_refs": [path for path in primary_truth if not _repo_relative_exists(path)], + }, + "frontend_interpretation": { + "status": "available", + "promotion_status": "reference_layer", + "source_refs": _existing_paths(frontend_interpretation), + "missing_refs": [path for path in frontend_interpretation if not _repo_relative_exists(path)], + }, + "qa_governance": { + "status": "available", + "promotion_status": "reference_layer", + "source_refs": _existing_paths(qa_governance), + "missing_refs": [path for path in qa_governance if not _repo_relative_exists(path)], + }, + "reader_validation": { + "status": "available", + "promotion_status": "reference_layer", + "source_refs": _existing_paths(reader_validation), + "missing_refs": [path for path in reader_validation if not _repo_relative_exists(path)], + }, + "yoga_rules": { + "status": "available", + "promotion_status": "reference_layer", + "source_refs": _existing_paths(yoga_rules), + "missing_refs": [path for path in yoga_rules if not _repo_relative_exists(path)], + }, + "saham_rules": { + "status": "available", + "promotion_status": "reference_layer", + "source_refs": _existing_paths(saham_rules), + "missing_refs": [path for path in saham_rules if not _repo_relative_exists(path)], + }, + "quarantined_drafts": { + "status": "quarantined", + "promotion_status": "not_truth_source", + "source_refs": _existing_paths(quarantined_drafts), + "missing_refs": [path for path in quarantined_drafts if not _repo_relative_exists(path)], + "boundary": "Listed for awareness only; drafts are not promoted into runtime source_refs.", + }, + } + missing_refs = [path for layer in layers.values() for path in layer["missing_refs"]] + quarantined_refs = set(layers["quarantined_drafts"]["source_refs"]) + promoted_quarantined = sorted(quarantined_refs.intersection(source_refs)) + status = "used" if not missing_refs and not promoted_quarantined else "partial" + return { + "status": status, + "source": "repo_interpretation_source_inventory_v1", + "layers": layers, + "summary": { + "primary_truth_count": len(layers["primary_truth"]["source_refs"]), + "reference_layer_count": len(_existing_paths(reference_layer)), + "quarantined_draft_count": len(layers["quarantined_drafts"]["source_refs"]), + "missing_ref_count": len(missing_refs), + "promoted_quarantined_count": len(promoted_quarantined), + }, + "missing_refs": missing_refs, + "promoted_quarantined_refs": promoted_quarantined, + "boundary": "Inventory is explicit and conservative; local drafts are indexed but not treated as truth sources.", + } + + @lru_cache(maxsize=1) def _existing_interpretation_source_pack() -> Dict[str, Any]: """Return the existing repo interpretation/source layers as an explicit evidence pack.""" template_path = "references/interpretation_template_registry.json" p1_p12_path = "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-core/resources/p1_p12.md" house_framework_path = "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-core/resources/house_framework.md" + qa_rules_path = "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-core/resources/qa_rules.md" + core_yogas_path = "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-core/resources/yogas.md" + reader_chart_rules_path = "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-reader/resources/chart_reading_rules.md" + reader_validation_rules_path = "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-reader/resources/validation_rules.md" raman_path = "references/raman-house-judgment-methodology.md" bphs_narayana_path = "references/bphs-ch48-narayana-dasha.md" mevg_path = "references/mandatory-verification-gate-protocol.md" real_case_checklist_path = "references/real-reading-quality-checklist.md" + frontend_interpretation_paths = [ + "jyotish-app/interpretation.js", + "jyotish-app/analysis-deep.js", + ] planet_house_paths = [ "jyotish-app/planet-house-details-a.js", "jyotish-app/planet-house-details-b.js", "jyotish-app/planet-house-details-c.js", ] + yoga_rule_paths = [ + core_yogas_path, + "references/yoga_rules.json", + "jyotish-app/yoga-details-a.js", + "jyotish-app/yoga-details-b.js", + "jyotish-app/yoga-extended.js", + "jyotish-app/yoga-extended-b.js", + ] + saham_rule_paths = [ + "references/saham_rules.json", + ] template_ids: List[str] = [] template_count = 0 @@ -151,14 +288,22 @@ def _existing_interpretation_source_pack() -> Dict[str, Any]: bphs_narayana_path, mevg_path, real_case_checklist_path, + *frontend_interpretation_paths, *planet_house_paths, + qa_rules_path, + reader_chart_rules_path, + reader_validation_rules_path, + *yoga_rule_paths, + *saham_rule_paths, ] missing_refs = [path for path in source_refs if not _repo_relative_exists(path)] + inventory = _build_interpretation_source_inventory(source_refs) return { - "status": "used" if not missing_refs else "partial", + "status": "used" if not missing_refs and inventory.get("status") == "used" else "partial", "source": "repo_existing_interpretation_sources", "source_refs": source_refs, "missing_refs": missing_refs, + "interpretation_source_inventory": inventory, "template_registry": { "path": template_path, "template_count": template_count, @@ -171,11 +316,21 @@ def _existing_interpretation_source_pack() -> Dict[str, Any]: "bphs_narayana_dasha", "mevg_mandatory_external_verification", "real_case_quality_checklist", + "qa_governance_rules", + "reader_validation_rules", + "frontend_interpretation_layer", + "yoga_rule_layer", + "saham_rule_layer", ], "bphs_raman_layer": { "status": "available" if _repo_relative_exists(raman_path) and _repo_relative_exists(bphs_narayana_path) else "partial", "source_refs": [raman_path, bphs_narayana_path], }, + "frontend_interpretation_layer": { + "status": "available" if all(_repo_relative_exists(path) for path in frontend_interpretation_paths) else "partial", + "source_refs": frontend_interpretation_paths, + "promotion_status": "reference_layer", + }, "frontend_planet_house_details": { "status": "available" if all(_repo_relative_exists(path) for path in planet_house_paths) else "partial", "coverage": "9_planets_x_12_houses", @@ -183,6 +338,26 @@ def _existing_interpretation_source_pack() -> Dict[str, Any]: "house_count": 12, "source_refs": planet_house_paths, }, + "qa_governance_layer": { + "status": "available" if _repo_relative_exists(qa_rules_path) else "partial", + "source_refs": [qa_rules_path], + "promotion_status": "reference_layer", + }, + "reader_validation_layer": { + "status": "available" if all(_repo_relative_exists(path) for path in [reader_chart_rules_path, reader_validation_rules_path]) else "partial", + "source_refs": [reader_chart_rules_path, reader_validation_rules_path], + "promotion_status": "reference_layer", + }, + "yoga_rule_layer": { + "status": "available" if all(_repo_relative_exists(path) for path in yoga_rule_paths) else "partial", + "source_refs": yoga_rule_paths, + "promotion_status": "reference_layer", + }, + "saham_rule_layer": { + "status": "available" if all(_repo_relative_exists(path) for path in saham_rule_paths) else "partial", + "source_refs": saham_rule_paths, + "promotion_status": "reference_layer", + }, "mevg_gate": { "status": "blocked", "required": True, diff --git a/scripts/interpretation_source_inventory_gate.py b/scripts/interpretation_source_inventory_gate.py new file mode 100644 index 00000000..5bf209df --- /dev/null +++ b/scripts/interpretation_source_inventory_gate.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +"""Validate interpretation source inventory wiring and draft quarantine.""" + +from __future__ import annotations + +import json +import sys +from pathlib import Path +from typing import Any + + +ROOT = Path(__file__).resolve().parents[1] +if str(ROOT) not in sys.path: + sys.path.insert(0, str(ROOT)) + +from mcp_server import _existing_interpretation_source_pack # noqa: E402 + + +REQUIRED_LAYERS = [ + "primary_truth", + "frontend_interpretation", + "qa_governance", + "reader_validation", + "yoga_rules", + "saham_rules", + "quarantined_drafts", +] + + +def build_report() -> dict[str, Any]: + source_pack = _existing_interpretation_source_pack() + inventory = source_pack.get("interpretation_source_inventory") if isinstance(source_pack, dict) else {} + if not isinstance(inventory, dict): + inventory = {} + layers = inventory.get("layers") if isinstance(inventory.get("layers"), dict) else {} + summary = inventory.get("summary") if isinstance(inventory.get("summary"), dict) else {} + source_refs = source_pack.get("source_refs") if isinstance(source_pack.get("source_refs"), list) else [] + missing_layers = [name for name in REQUIRED_LAYERS if name not in layers] + missing_refs = inventory.get("missing_refs") if isinstance(inventory.get("missing_refs"), list) else [] + promoted_quarantined = ( + inventory.get("promoted_quarantined_refs") + if isinstance(inventory.get("promoted_quarantined_refs"), list) + else [] + ) + failures: list[dict[str, Any]] = [] + if source_pack.get("status") != "used": + failures.append({"id": "source_pack_not_used", "status": source_pack.get("status")}) + if inventory.get("status") != "used": + failures.append({"id": "inventory_not_used", "status": inventory.get("status")}) + if missing_layers: + failures.append({"id": "missing_inventory_layers", "layers": missing_layers}) + if missing_refs: + failures.append({"id": "missing_source_refs", "refs": missing_refs}) + if promoted_quarantined: + failures.append({"id": "quarantined_drafts_promoted", "refs": promoted_quarantined}) + + status = "pass" if not failures else "fail" + return { + "scope": "interpretation_source_inventory_gate", + "status": status, + "source_pack_status": source_pack.get("status"), + "inventory_status": inventory.get("status"), + "summary": summary, + "layers": layers, + "runtime_source_refs": source_refs, + "failures": failures, + "boundary": ( + "This gate validates explicit source inventory wiring. It does not promote drafts " + "or replace MEVG/global-web/real-case verification for interpretive claims." + ), + } + + +def main() -> int: + report = build_report() + print(json.dumps(report, ensure_ascii=False, indent=2)) + return 0 if report["status"] == "pass" else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/run_quality_gate.py b/scripts/run_quality_gate.py index fc6e6de6..c279662e 100644 --- a/scripts/run_quality_gate.py +++ b/scripts/run_quality_gate.py @@ -30,6 +30,7 @@ EXTRA_COMPILE_TARGETS = [ ROOT / "scripts" / "audit_fragments.py", ROOT / "scripts" / "dasha_reference_audit.py", ROOT / "scripts" / "external_oracle_sanity_closure.py", + ROOT / "scripts" / "interpretation_source_inventory_gate.py", ROOT / "scripts" / "oracle_boundary_audit.py", ROOT / "scripts" / "oracle_collection_queue.py", ROOT / "scripts" / "oracle_evidence_validator.py", @@ -480,6 +481,7 @@ def main() -> int: validate_json_files() run([PYTHON, "scripts/audit_capabilities.py", "--mode", "validate"]) run([PYTHON, "scripts/audit_fragments.py", "--strict"]) + run([PYTHON, "scripts/interpretation_source_inventory_gate.py"]) run([PYTHON, "scripts/deployment_preflight.py"]) if profile["check_release_hygiene"]: release_hygiene_check() diff --git a/tests/test_interpretation_source_inventory_gate.py b/tests/test_interpretation_source_inventory_gate.py new file mode 100644 index 00000000..44905d9d --- /dev/null +++ b/tests/test_interpretation_source_inventory_gate.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +"""Regression tests for interpretation source inventory governance.""" + +from __future__ import annotations + +import json +import subprocess +import sys +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] + + +def test_interpretation_source_inventory_gate_reports_layered_source_pack() -> None: + completed = subprocess.run( + [ + sys.executable, + "scripts/interpretation_source_inventory_gate.py", + ], + cwd=ROOT, + text=True, + capture_output=True, + timeout=120, + check=False, + ) + + assert completed.returncode == 0, completed.stderr or completed.stdout + report = json.loads(completed.stdout) + + assert report["scope"] == "interpretation_source_inventory_gate" + assert report["status"] == "pass" + assert report["source_pack_status"] == "used" + assert report["summary"]["missing_ref_count"] == 0 + assert report["summary"]["promoted_quarantined_count"] == 0 + assert report["summary"]["primary_truth_count"] >= 5 + assert report["summary"]["reference_layer_count"] >= 10 + assert report["summary"]["quarantined_draft_count"] >= 1 + + layers = report["layers"] + assert "jyotish-app/interpretation.js" in layers["frontend_interpretation"]["source_refs"] + assert "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-core/resources/qa_rules.md" in layers["qa_governance"]["source_refs"] + assert "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-reader/resources/validation_rules.md" in layers["reader_validation"]["source_refs"] + assert "references/yoga_rules.json" in layers["yoga_rules"]["source_refs"] + assert "references/saham_rules.json" in layers["saham_rules"]["source_refs"] + + draft_refs = layers["quarantined_drafts"]["source_refs"] + source_refs = set(report["runtime_source_refs"]) + assert any("docs/research/local_drafts/" in path for path in draft_refs) + assert all(path not in source_refs for path in draft_refs) + + +def test_quality_gate_runs_interpretation_source_inventory_gate() -> None: + quality_gate = (ROOT / "scripts" / "run_quality_gate.py").read_text(encoding="utf-8") + + assert '"scripts/interpretation_source_inventory_gate.py"' in quality_gate + assert '[PYTHON, "scripts/interpretation_source_inventory_gate.py"]' in quality_gate diff --git a/tests/test_mcp_strict_workflow_career.py b/tests/test_mcp_strict_workflow_career.py index ddd7aaf6..4c04edfa 100644 --- a/tests/test_mcp_strict_workflow_career.py +++ b/tests/test_mcp_strict_workflow_career.py @@ -5,7 +5,7 @@ from __future__ import annotations import jyotish_engine -from mcp_server import _collect_strict_evidence +from mcp_server import _collect_strict_evidence, _existing_interpretation_source_pack def _base_career_result() -> dict: @@ -85,6 +85,16 @@ def test_career_strict_contract_attaches_existing_interpretation_source_pack() - assert source_pack["template_registry"]["template_count"] >= 11 assert source_pack["frontend_planet_house_details"]["planet_count"] == 9 assert source_pack["frontend_planet_house_details"]["house_count"] == 12 + assert source_pack["interpretation_source_inventory"]["status"] == "used" + assert source_pack["frontend_interpretation_layer"]["status"] == "available" + assert source_pack["frontend_interpretation_layer"]["source_refs"] == [ + "jyotish-app/interpretation.js", + "jyotish-app/analysis-deep.js", + ] + assert source_pack["yoga_rule_layer"]["status"] == "available" + assert "references/yoga_rules.json" in source_pack["yoga_rule_layer"]["source_refs"] + assert source_pack["reader_validation_layer"]["status"] == "available" + assert "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-reader/resources/validation_rules.md" in source_pack["reader_validation_layer"]["source_refs"] audit = strict["technique_audit_summary"] assert audit["interpretation_source_pack"]["used"] is True @@ -92,6 +102,24 @@ def test_career_strict_contract_attaches_existing_interpretation_source_pack() - assert audit["real_case_calibration"]["status"] == "blocked" +def test_interpretation_source_inventory_classifies_sources_without_promoting_drafts() -> None: + source_pack = _existing_interpretation_source_pack() + inventory = source_pack["interpretation_source_inventory"] + + assert inventory["status"] == "used" + assert inventory["summary"]["primary_truth_count"] >= 4 + assert inventory["summary"]["reference_layer_count"] >= 4 + assert inventory["summary"]["quarantined_draft_count"] >= 1 + assert "jyotish-app/interpretation.js" in inventory["layers"]["frontend_interpretation"]["source_refs"] + assert "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-core/resources/qa_rules.md" in inventory["layers"]["qa_governance"]["source_refs"] + assert "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-reader/resources/validation_rules.md" in inventory["layers"]["reader_validation"]["source_refs"] + assert "references/yoga_rules.json" in inventory["layers"]["yoga_rules"]["source_refs"] + + draft_refs = inventory["layers"]["quarantined_drafts"]["source_refs"] + assert any("docs/research/local_drafts/" in path for path in draft_refs) + assert all(path not in source_pack["source_refs"] for path in draft_refs) + + def test_career_blocks_label_when_d10_is_missing_but_preserves_jaimini_context() -> None: result = _base_career_result() del result["modules"]["varga_full"]["D10_Dasamsa"] diff --git a/tests/test_mcp_strict_workflow_finance.py b/tests/test_mcp_strict_workflow_finance.py index a467bdbe..e6852a19 100644 --- a/tests/test_mcp_strict_workflow_finance.py +++ b/tests/test_mcp_strict_workflow_finance.py @@ -43,6 +43,10 @@ def test_finance_strict_contract_attaches_existing_interpretation_source_pack() assert "yogi_asc_tight_orb_wealth" in source_pack["template_registry"]["template_ids"] assert source_pack["bphs_raman_layer"]["status"] == "available" assert source_pack["frontend_planet_house_details"]["coverage"] == "9_planets_x_12_houses" + assert source_pack["interpretation_source_inventory"]["status"] == "used" + assert source_pack["frontend_interpretation_layer"]["status"] == "available" + assert source_pack["yoga_rule_layer"]["status"] == "available" + assert source_pack["qa_governance_layer"]["status"] == "available" audit = strict["technique_audit_summary"] assert audit["interpretation_source_pack"]["used"] is True diff --git a/tests/test_mcp_strict_workflow_relationship.py b/tests/test_mcp_strict_workflow_relationship.py index bd9ba09e..e496f728 100644 --- a/tests/test_mcp_strict_workflow_relationship.py +++ b/tests/test_mcp_strict_workflow_relationship.py @@ -72,6 +72,10 @@ def test_relationship_strict_contract_attaches_existing_interpretation_source_pa assert "p1_p12" in source_pack["frameworks"] assert "house_framework" in source_pack["frameworks"] assert "raman_functional_house_judgment" in source_pack["frameworks"] + assert source_pack["interpretation_source_inventory"]["status"] == "used" + assert source_pack["qa_governance_layer"]["status"] == "available" + assert source_pack["reader_validation_layer"]["status"] == "available" + assert "references/open_source_sources/vedic-astro-skills/codex/skills/vedic-core/resources/qa_rules.md" in source_pack["qa_governance_layer"]["source_refs"] audit = strict["technique_audit_summary"] assert audit["interpretation_source_pack"]["source"] == "repo_existing_interpretation_sources"