diff --git a/docs/research/ACTIVE_FRONTS.md b/docs/research/ACTIVE_FRONTS.md index 74b4a4f6..9ec22d72 100644 --- a/docs/research/ACTIVE_FRONTS.md +++ b/docs/research/ACTIVE_FRONTS.md @@ -52,8 +52,9 @@ This file is the small index for the current engineering fronts that still drive - `/Users/wuyongnaren/Documents/印度占星/docs/superpowers/specs/2026-06-28-dignity-guardrail-v1-design.md` - `/Users/wuyongnaren/Documents/印度占星/docs/research/dignity_guardrail_v1_boundary_audit_2026_06_28.md` - `/Users/wuyongnaren/Documents/印度占星/docs/research/divisional_dignity_context_repair_audit_2026_06_28.md` +- `/Users/wuyongnaren/Documents/印度占星/docs/research/functional_benefic_malefic_strict_layer_audit_2026_06_28.md` - D1-only dignity guardrail is landed. - Divisional dignity context repair for D9/DK/Vimsopaka Navamsa is also landed. - Still open: - Vimsopaka semantic mapping for `NEECHA_BHANGA / GREAT_FRIEND / GREAT_ENEMY` - - a separate functional-role guardrail + - functional role now enters strict evidence; follow-up is Technique Audit Table rendering. diff --git a/docs/research/functional_benefic_malefic_strict_layer_audit_2026_06_28.md b/docs/research/functional_benefic_malefic_strict_layer_audit_2026_06_28.md new file mode 100644 index 00000000..66f547b6 --- /dev/null +++ b/docs/research/functional_benefic_malefic_strict_layer_audit_2026_06_28.md @@ -0,0 +1,42 @@ +# Functional Benefic/Malefic Strict Layer Audit - 2026-06-28 + +## Scope + +This audit records the first strict-workflow integration of the Functional Benefic/Malefic hard constraint from `AGENTS.md`. + +## Implementation + +Strict workflow evidence now exposes: + +```json +{ + "functional_benefic_malefic": { + "status": "used | blocked", + "ascendant": "Leo", + "functional_benefics": ["Sun", "Mars", "Jupiter"], + "functional_malefics": ["Mercury", "Moon", "Saturn", "Venus"], + "functional_neutrals": [], + "yogakarakas": ["Mars"], + "owned_houses": {"Sun": [1]}, + "source": "strict_functional_benefic_malefic_v1" + } +} +``` + +## Boundary + +- This is a functional house-lord role layer, not a natural benefic/malefic table. +- It is exposed as evidence and secondary context. +- It does not directly lift dominant labels. +- If chart ascendant or planets are unavailable, the layer remains `blocked` in evidence but does not pollute legacy secondary-context tests. + +## Why It Matters + +The project hard constraint says high-rigor timing or outcome readings must not judge benefic/malefic effects from natural planetary quality alone. This bridge makes the functional role layer visible to strict workflows so later adjudicators and Technique Audit Tables can use it. + +## Verification + +- `tests/test_mcp_strict_workflow_functional_layer.py` +- `tests/test_mcp_strict_workflow_relationship.py` +- `tests/test_mcp_strict_workflow_career.py` +- `tests/test_mcp_strict_workflow_finance.py` diff --git a/mcp_server.py b/mcp_server.py index a83f5588..dd588c79 100644 --- a/mcp_server.py +++ b/mcp_server.py @@ -449,6 +449,91 @@ def _derive_external_activation_support(modules: Dict[str, Any], domain: str) -> } +def _derive_functional_benefic_malefic(modules: Dict[str, Any]) -> Dict[str, Any]: + chart = _safe_get(modules, "chart") + ascendant = _safe_get(chart, "ascendant") if isinstance(chart, dict) else None + planets = _safe_get(chart, "planets") if isinstance(chart, dict) else None + if not isinstance(ascendant, dict) or not isinstance(planets, dict): + return { + "status": "blocked", + "ascendant": ascendant.get("sign") if isinstance(ascendant, dict) else None, + "functional_benefics": [], + "functional_malefics": [], + "effect_on_confidence": "Missing chart.ascendant or chart.planets; functional layer blocked.", + "source": "strict_functional_benefic_malefic_v1", + } + asc_sign = ascendant.get("sign") + asc_idx = _SIGN_TO_INDEX.get(asc_sign) + if asc_idx is None: + return { + "status": "blocked", + "ascendant": asc_sign, + "functional_benefics": [], + "functional_malefics": [], + "effect_on_confidence": "Functional layer blocked: unknown ascendant sign.", + "source": "strict_functional_benefic_malefic_v1", + } + + owned_houses: Dict[str, List[int]] = {} + for house_num in range(1, 13): + sign = _SIGNS[(asc_idx + house_num - 1) % 12] + lord = _SIGN_LORDS.get(sign) + if lord: + owned_houses.setdefault(lord, []).append(house_num) + + trines = {1, 5, 9} + kendras = {1, 4, 7, 10} + challenging = {3, 6, 8, 11, 12} + benefics: set[str] = set() + malefics: set[str] = set() + yogakarakas: set[str] = set() + neutrals: set[str] = set() + + for planet in ("Sun", "Moon", "Mars", "Mercury", "Jupiter", "Venus", "Saturn"): + houses = owned_houses.get(planet, []) + if not houses: + continue + owns_trine = any(house in trines for house in houses) + owns_kendra = any(house in kendras for house in houses) + owns_challenge = any(house in challenging for house in houses) + + if owns_trine and owns_kendra and planet not in {"Sun", "Moon"}: + yogakarakas.add(planet) + benefics.add(planet) + elif owns_trine: + benefics.add(planet) + elif owns_challenge and 1 not in houses: + malefics.add(planet) + elif owns_kendra and planet in {"Jupiter", "Venus", "Mercury", "Moon"}: + neutrals.add(planet) + else: + neutrals.add(planet) + + # Classical softening: Sun/Moon as 8L are not treated as harshly as other 8L. + eighth_lord = owned_houses and next( + (planet for planet, houses in owned_houses.items() if 8 in houses), + None, + ) + if eighth_lord in {"Sun", "Moon"} and eighth_lord in malefics: + malefics.remove(eighth_lord) + neutrals.add(eighth_lord) + + return { + "status": "used", + "ascendant": asc_sign, + "functional_benefics": sorted(benefics), + "functional_malefics": sorted(malefics), + "functional_neutrals": sorted(neutrals - benefics - malefics), + "yogakarakas": sorted(yogakarakas), + "owned_houses": {planet: houses for planet, houses in sorted(owned_houses.items())}, + "effect_on_confidence": ( + "High-rigor outputs must combine functional house-lord roles with natural roles; " + "conflicts should cap confidence or be explicitly noted." + ), + "source": "strict_functional_benefic_malefic_v1", + } + + def _derive_synastry_relationship_support(modules: Dict[str, Any]) -> Dict[str, Any]: synastry = _safe_get(modules, "synastry") base = { @@ -902,6 +987,9 @@ def _derive_event_judgement(route: str, present: Dict[str, Any], missing: List[s shadbala_component_audit = present.get("shadbala_component_audit") or {} if shadbala_component_audit.get("status") in {"blocked", "incomplete"}: secondary_context.append("shadbala_component_gap") + functional_layer = present.get("functional_benefic_malefic") or {} + if functional_layer.get("status") == "used": + secondary_context.append("functional_benefic_malefic_used") if kakshya_career_support.get("level") == "supportive": secondary_context.append("kakshya_career_support") elif kakshya_career_support.get("level") == "obstructive": @@ -990,6 +1078,9 @@ def _derive_event_judgement(route: str, present: Dict[str, Any], missing: List[s shadbala_component_audit = present.get("shadbala_component_audit") or {} if shadbala_component_audit.get("status") in {"blocked", "incomplete"}: secondary_context.append("shadbala_component_gap") + functional_layer = present.get("functional_benefic_malefic") or {} + if functional_layer.get("status") == "used": + secondary_context.append("functional_benefic_malefic_used") if argala_support.get("level") == "supportive": secondary_context.append("argala_support") elif argala_support.get("level") == "obstructive": @@ -1125,6 +1216,9 @@ def _derive_event_judgement(route: str, present: Dict[str, Any], missing: List[s shadbala_component_audit = present.get("shadbala_component_audit") or {} if shadbala_component_audit.get("status") in {"blocked", "incomplete"}: secondary_context.append("shadbala_component_gap") + functional_layer = present.get("functional_benefic_malefic") or {} + if functional_layer.get("status") == "used": + secondary_context.append("functional_benefic_malefic_used") if pav_finance_support.get("level") == "supportive": secondary_context.append("pav_finance_support") if sodhita_finance_support.get("level") == "obstructive": @@ -1304,8 +1398,9 @@ def _collect_strict_evidence(route: str, result: Dict[str, Any]) -> Dict[str, An present["kakshya_career_support"] = _derive_kakshya_career_support(_safe_get(modules, "kakshya")) present["argala_support"] = _derive_argala_support(modules, 10) present["external_activation"] = _derive_external_activation_support(modules, "career") + present["functional_benefic_malefic"] = _derive_functional_benefic_malefic(modules) missing = [key for key, value in present.items() if key not in { - "external_activation", "argala_support", "shadbala", "shadbala_component_audit", "kakshya_career_support" + "external_activation", "argala_support", "shadbala", "shadbala_component_audit", "kakshya_career_support", "functional_benefic_malefic" } and value in (None, {}, [], "")] convergence = present["career_convergence"] or {} confidence_cap = "medium" @@ -1362,9 +1457,10 @@ def _collect_strict_evidence(route: str, result: Dict[str, Any]) -> Dict[str, An present["argala_support"] = _derive_argala_support(modules, 7) present["external_activation"] = _derive_external_activation_support(modules, "marriage") present["dignity_guardrail"] = _derive_dignity_guardrail(route, present) + present["functional_benefic_malefic"] = _derive_functional_benefic_malefic(modules) missing = [ key for key, value in present.items() - if key not in {"chart", "external_activation", "dignity_guardrail", "jaimini_marriage_support", "jaimini_timing_support", "synastry_relationship_support", "argala_support", "shadbala", "shadbala_component_audit"} + if key not in {"chart", "external_activation", "dignity_guardrail", "jaimini_marriage_support", "jaimini_timing_support", "synastry_relationship_support", "argala_support", "shadbala", "shadbala_component_audit", "functional_benefic_malefic"} and value in (None, {}, [], "") ] convergence = present["marriage_convergence"] or {} @@ -1434,8 +1530,9 @@ def _collect_strict_evidence(route: str, result: Dict[str, Any]) -> Dict[str, An present["kakshya_finance_support"] = _derive_kakshya_finance_support(_safe_get(modules, "kakshya")) present["external_activation"] = _derive_external_activation_support(modules, "wealth") present["dignity_guardrail"] = _derive_dignity_guardrail(route, present) + present["functional_benefic_malefic"] = _derive_functional_benefic_malefic(modules) missing = [key for key, value in present.items() if key not in { - "chart", "external_activation", "dignity_guardrail", "gains_convergence", "career_convergence", "avayogi_risk", "ashtakavarga_finance_support", "shadbala_component_audit", "asc_sign", "pav_finance_support", "sodhita_finance_support", "kakshya_finance_support" + "chart", "external_activation", "dignity_guardrail", "gains_convergence", "career_convergence", "avayogi_risk", "ashtakavarga_finance_support", "shadbala_component_audit", "asc_sign", "pav_finance_support", "sodhita_finance_support", "kakshya_finance_support", "functional_benefic_malefic" } and value in (None, {}, [], "")] convergence_hits: List[Dict[str, Any]] = [ item for item in [ diff --git a/tests/test_mcp_strict_workflow_functional_layer.py b/tests/test_mcp_strict_workflow_functional_layer.py new file mode 100644 index 00000000..e08472e6 --- /dev/null +++ b/tests/test_mcp_strict_workflow_functional_layer.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +"""Regression tests for functional benefic/malefic strict evidence integration.""" + +from __future__ import annotations + +from mcp_server import _collect_strict_evidence + + +def _base_relationship_result() -> dict: + return { + "modules": { + "chart": { + "ascendant": {"sign": "Leo"}, + "planets": { + "Sun": {"sign": "Aquarius", "house": 7}, + "Moon": {"sign": "Taurus", "house": 10}, + "Mars": {"sign": "Gemini", "house": 11}, + "Mercury": {"sign": "Capricorn", "house": 6}, + "Jupiter": {"sign": "Sagittarius", "house": 5}, + "Venus": {"sign": "Pisces", "house": 8}, + "Saturn": {"sign": "Libra", "house": 3}, + }, + }, + "varga_full": {"D9_Navamsa": {"summary": "ok"}}, + "special_lagnas": {"Upapada_Lagna": {"sign": "Libra", "lord": "Venus"}}, + "jaimini": {"darakaraka": {"planet": "Venus", "house": 7}}, + "vivah_saham": {"sign": "Taurus", "house": 7}, + "dasha": {"current_dasha": {"mahadasha": "Venus", "antardasha": "Moon"}}, + "narayana_dasha": {"current_dasha": {"sign": "Libra", "lord": "Venus"}}, + "dasa_convergence": { + "domain_activations": { + "marriage_partnership": {"convergence_level": "L3", "probability": "50-65%"} + } + }, + } + } + + +def test_relationship_strict_workflow_exposes_functional_benefic_malefic_layer() -> None: + strict = _collect_strict_evidence("relationship", _base_relationship_result()) + + functional = strict["present_evidence"]["functional_benefic_malefic"] + assert functional["status"] == "used" + assert functional["ascendant"] == "Leo" + assert isinstance(functional["functional_benefics"], list) + assert isinstance(functional["functional_malefics"], list) + assert "Sun" in functional["functional_benefics"] + assert "Venus" in functional["functional_malefics"] + assert "functional_benefic_malefic_used" in strict["event_judgement"]["secondary_context"] +