diff --git a/docs/research/ACTIVE_FRONTS.md b/docs/research/ACTIVE_FRONTS.md index b1c89744..e30ab2d7 100644 --- a/docs/research/ACTIVE_FRONTS.md +++ b/docs/research/ACTIVE_FRONTS.md @@ -12,6 +12,7 @@ This file is the small index for the current engineering fronts that still drive ## Relationship Adjudication - `/Users/wuyongnaren/Documents/印度占星/docs/research/marriage_adjudicator_first_pass_audit_2026_06_27.md` +- `/Users/wuyongnaren/Documents/印度占星/docs/research/isolated_asset_bridge_audit_2026_06_28.md` - `/Users/wuyongnaren/Documents/印度占星/references/event_judgment_marriage.md` - `/Users/wuyongnaren/Documents/印度占星/docs/superpowers/specs/2026-06-28-jaimini-marriage-bridge-v1-design.md` diff --git a/docs/research/isolated_asset_bridge_audit_2026_06_28.md b/docs/research/isolated_asset_bridge_audit_2026_06_28.md new file mode 100644 index 00000000..f8bfab09 --- /dev/null +++ b/docs/research/isolated_asset_bridge_audit_2026_06_28.md @@ -0,0 +1,66 @@ +# Isolated Asset Bridge Audit - 2026-06-28 + +## Scope + +This audit records the first small bridge pass after the VedAstro parity matrix identified high-value local assets that were present but not fully exploited by strict adjudicators. + +## Bridges + +### Synastry / Ashtakoot -> relationship strict evidence + +- New evidence key: `synastry_relationship_support` +- Source: `synastry_relationship_bridge_v1` +- Inputs accepted: + - `modules.synastry.total_score` + - `modules.synastry.is_approved` or `is_match_approved` + - selected clean additional Kuta signals: `Vedha`, `Rajju`, `BadConstellations` +- Output levels: + - `none` + - `moderate` + - `supportive` + +Boundary: + +- Adds at most `+5` to relationship score. +- Adds `synastry_support` to `secondary_context`. +- Does not lift `dominant_label`. +- Does not bypass D9, UL, Vimshottari, Narayana, Vivah Saham, or marriage convergence gates. + +### Kakshya -> career strict evidence + +- New evidence key: `kakshya_career_support` +- Source: `kakshya_career_bridge_v1` +- Inputs accepted: + - `modules.kakshya.summary.average_strength` +- Output levels: + - `none` + - `supportive` + - `obstructive` + +Boundary: + +- Adds `+2` for supportive career Kakshya. +- Subtracts `-2` for obstructive career Kakshya. +- Adds a secondary context flag. +- Does not lift `dominant_label` without the existing career hard gates. + +### VedAstro range scan adapter summary + +- Adds `event_count`. +- Adds `top_event`. +- Keeps the complete `evidence_ledger` as the source of truth. + +Boundary: + +- The adapter summary is convenience metadata only. +- External range scan evidence remains `oracle_only` until promoted by adjudicator tests. + +## Regression Coverage + +- `tests/test_mcp_strict_workflow_relationship.py` +- `tests/test_mcp_strict_workflow_career.py` +- `tests/test_vedastro_service_adapter_executor.py` + +## Next Follow-Up + +Build a full relationship bridge around Synastry/Ashtakoot only after real couple benchmark cases are added. This pass intentionally keeps matching evidence secondary. diff --git a/mcp_server.py b/mcp_server.py index cbd1edae..4d50bc7b 100644 --- a/mcp_server.py +++ b/mcp_server.py @@ -449,6 +449,59 @@ def _derive_external_activation_support(modules: Dict[str, Any], domain: str) -> } +def _derive_synastry_relationship_support(modules: Dict[str, Any]) -> Dict[str, Any]: + synastry = _safe_get(modules, "synastry") + base = { + "level": "none", + "source": "synastry_relationship_bridge_v1", + "signals": [], + "total_score": None, + "approved": False, + } + if not isinstance(synastry, dict): + return base + + try: + total_score = float(synastry.get("total_score")) + except (TypeError, ValueError): + total_score = None + + approved = bool( + synastry.get("is_approved") + or synastry.get("is_match_approved") + ) + signals: List[str] = [] + if approved: + signals.append("ashtakoot_approved") + if total_score is not None and total_score >= 27: + signals.append("ashtakoot_high_score") + additional_kutas = synastry.get("additional_kutas") + if isinstance(additional_kutas, dict): + vedha_good = additional_kutas.get("Vedha") == "good" + bad_constellations_good = additional_kutas.get("BadConstellations") == "good" + rajju = additional_kutas.get("Rajju") + rajju_good = isinstance(rajju, dict) and rajju.get("result") == "good" + if vedha_good and bad_constellations_good and rajju_good: + signals.append("kuta_exception_clean") + + if approved and total_score is not None and total_score >= 27: + level = "supportive" + elif approved or (total_score is not None and total_score >= 24): + level = "moderate" + else: + level = "none" + + base.update( + { + "level": level, + "signals": signals, + "total_score": total_score, + "approved": approved, + } + ) + return base + + def _derive_argala_support(modules: Dict[str, Any], target_house: int) -> Dict[str, Any]: house_data = _safe_get(modules, "argala", "houses", f"house_{target_house}") if not isinstance(house_data, dict): @@ -656,6 +709,26 @@ def _derive_kakshya_finance_support(kakshya: Any) -> Dict[str, Any]: return base +def _derive_kakshya_career_support(kakshya: Any) -> Dict[str, Any]: + base = { + "level": "none", + "source": "kakshya_career_bridge_v1", + "signals": [], + "average_strength": None, + } + avg = _safe_get(kakshya, "summary", "average_strength") + if not isinstance(avg, (int, float)): + return base + base["average_strength"] = float(avg) + if avg >= 6.5: + base["level"] = "supportive" + base["signals"] = ["kakshya_career_support"] + elif avg <= 4.5: + base["level"] = "obstructive" + base["signals"] = ["kakshya_career_friction"] + return base + + def _sign_to_index(sign: str) -> Optional[int]: try: return _SIGNS.index(sign) @@ -791,6 +864,11 @@ def _derive_event_judgement(route: str, present: Dict[str, Any], missing: List[s score += 10 if present.get("vimshottari_current") else 0 score += 10 if present.get("narayana_current") else 0 score += _convergence_score(present.get("career_convergence")) + kakshya_career_support = present.get("kakshya_career_support") or {} + if kakshya_career_support.get("level") == "supportive": + score += 2 + elif kakshya_career_support.get("level") == "obstructive": + score -= 2 argala_support = present.get("argala_support") or {} if argala_support.get("level") == "supportive": score += 5 @@ -824,6 +902,10 @@ 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") + if kakshya_career_support.get("level") == "supportive": + secondary_context.append("kakshya_career_support") + elif kakshya_career_support.get("level") == "obstructive": + secondary_context.append("kakshya_career_friction") if argala_support.get("level") == "supportive": secondary_context.append("argala_support") elif argala_support.get("level") == "obstructive": @@ -874,6 +956,9 @@ def _derive_event_judgement(route: str, present: Dict[str, Any], missing: List[s score += _convergence_score(present.get("marriage_convergence")) dignity_guardrail = present.get("dignity_guardrail") or {} score += dignity_guardrail.get("score_delta", 0) + synastry_support = present.get("synastry_relationship_support") or {} + if synastry_support.get("level") == "supportive": + score += 5 argala_support = present.get("argala_support") or {} if argala_support.get("level") == "supportive": score += 5 @@ -900,6 +985,8 @@ def _derive_event_judgement(route: str, present: Dict[str, Any], missing: List[s secondary_context.append("jaimini_support") if present.get("upapada_lagna"): secondary_context.append("ul_support") + if synastry_support.get("level") in {"supportive", "moderate"}: + secondary_context.append("synastry_support") shadbala_component_audit = present.get("shadbala_component_audit") or {} if shadbala_component_audit.get("status") in {"blocked", "incomplete"}: secondary_context.append("shadbala_component_gap") @@ -946,6 +1033,7 @@ def _derive_event_judgement(route: str, present: Dict[str, Any], missing: List[s "narayana_current", "darakaraka", "upapada_lagna", + "synastry_relationship_support", "argala_support", ) if present.get(key) @@ -1110,10 +1198,11 @@ def _collect_strict_evidence(route: str, result: Dict[str, Any]) -> Dict[str, An } present["shadbala"] = _safe_get(modules, "shadbala", "planets") present["shadbala_component_audit"] = _derive_shadbala_component_audit(present["shadbala"]) if present["shadbala"] else None + 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") missing = [key for key, value in present.items() if key not in { - "external_activation", "argala_support", "shadbala", "shadbala_component_audit" + "external_activation", "argala_support", "shadbala", "shadbala_component_audit", "kakshya_career_support" } and value in (None, {}, [], "")] convergence = present["career_convergence"] or {} confidence_cap = "medium" @@ -1166,12 +1255,13 @@ def _collect_strict_evidence(route: str, result: Dict[str, Any]) -> Dict[str, An present["shadbala_component_audit"] = _derive_shadbala_component_audit(present["shadbala"]) if present["shadbala"] else None present["jaimini_timing_support"] = _safe_get(modules, "jaimini", "marriage_timing_support") present["jaimini_marriage_support"] = _derive_jaimini_marriage_support(present) + present["synastry_relationship_support"] = _derive_synastry_relationship_support(modules) 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) missing = [ key for key, value in present.items() - if key not in {"chart", "external_activation", "dignity_guardrail", "jaimini_marriage_support", "jaimini_timing_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"} and value in (None, {}, [], "") ] convergence = present["marriage_convergence"] or {} diff --git a/scripts/vedastro_service_adapter.py b/scripts/vedastro_service_adapter.py index 0a3f81dc..261f19f8 100644 --- a/scripts/vedastro_service_adapter.py +++ b/scripts/vedastro_service_adapter.py @@ -240,6 +240,20 @@ def _normalize_range_scan_success( } ) + top_event = None + if evidence_ledger: + top = max( + evidence_ledger, + key=lambda item: item.get("score") if isinstance(item.get("score"), (int, float)) else float("-inf"), + ) + top_event = { + "event_id": top.get("event_id"), + "score": top.get("score"), + "start": top.get("start"), + "end": top.get("end"), + "tags": top.get("tags") or [], + } + return { "backend": "vedastro_service_adapter_candidate", "available": True, @@ -247,6 +261,8 @@ def _normalize_range_scan_success( "operation": "range_scan", "domain": request_preview["domain"], "request_preview": request_preview, + "event_count": len(evidence_ledger), + "top_event": top_event, "evidence_ledger": evidence_ledger, "source_metadata": { "transport": "http_json_service_boundary", diff --git a/tests/test_mcp_strict_workflow_career.py b/tests/test_mcp_strict_workflow_career.py index 718667a4..ee9cbbdf 100644 --- a/tests/test_mcp_strict_workflow_career.py +++ b/tests/test_mcp_strict_workflow_career.py @@ -96,6 +96,36 @@ def test_career_argala_bridge_uses_tenth_house_as_modifier_only() -> None: assert "argala_support" in strict["event_judgement"]["secondary_context"] +def test_career_kakshya_support_adds_small_score_bump_without_label_override() -> None: + base_result = _base_career_result() + base_result["modules"]["dasa_convergence"]["domain_activations"]["career_status"] = { + "convergence_level": "L1", + "probability": "+15-20%", + } + base = _collect_strict_evidence("career", base_result) + result = _base_career_result() + result["modules"]["dasa_convergence"]["domain_activations"]["career_status"] = { + "convergence_level": "L1", + "probability": "+15-20%", + } + result["modules"]["kakshya"] = { + "summary": {"average_strength": 6.7}, + "planets": {"Sun": {"kakshya_strength": 7.0}}, + } + + strict = _collect_strict_evidence("career", result) + + assert strict["present_evidence"]["kakshya_career_support"] == { + "level": "supportive", + "source": "kakshya_career_bridge_v1", + "signals": ["kakshya_career_support"], + "average_strength": 6.7, + } + assert strict["event_judgement"]["dominant_label"] == "career_status" + assert strict["event_judgement"]["score"] >= base["event_judgement"]["score"] + assert "kakshya_career_support" in strict["event_judgement"]["secondary_context"] + + def test_career_caps_confidence_when_provided_shadbala_components_are_incomplete() -> None: result = _base_career_result() result["modules"]["dasa_convergence"]["domain_activations"]["career_status"] = { diff --git a/tests/test_mcp_strict_workflow_relationship.py b/tests/test_mcp_strict_workflow_relationship.py index 79eae684..5665992a 100644 --- a/tests/test_mcp_strict_workflow_relationship.py +++ b/tests/test_mcp_strict_workflow_relationship.py @@ -114,6 +114,59 @@ def test_relationship_collects_vedastro_range_scan_as_external_activation_contex assert "external_activation_support" in strict["event_judgement"]["secondary_context"] +def test_relationship_synastry_bridge_adds_context_and_small_score_bump_without_overriding_label_gate() -> None: + base = _collect_strict_evidence("relationship", _base_relationship_result()) + result = _base_relationship_result() + result["modules"]["synastry"] = { + "total_score": 29.0, + "max_score": 36.0, + "is_approved": True, + "additional_kutas": { + "Mahendra": "good", + "StreeDeergha": "good", + "Vedha": "good", + "Rajju": {"result": "good", "group": None, "effect": ""}, + "BadConstellations": "good", + }, + } + + strict = _collect_strict_evidence("relationship", result) + + assert strict["present_evidence"]["synastry_relationship_support"] == { + "level": "supportive", + "source": "synastry_relationship_bridge_v1", + "signals": ["ashtakoot_approved", "ashtakoot_high_score", "kuta_exception_clean"], + "total_score": 29.0, + "approved": True, + } + assert strict["event_judgement"]["dominant_label"] == "legal_marriage" + assert strict["event_judgement"]["score"] == base["event_judgement"]["score"] + 5 + assert "synastry_support" in strict["event_judgement"]["secondary_context"] + + +def test_relationship_synastry_bridge_cannot_lift_label_when_core_marriage_layers_are_missing() -> None: + result = _base_relationship_result() + del result["modules"]["varga_full"]["D9_Navamsa"] + del result["modules"]["special_lagnas"]["Upapada_Lagna"] + result["modules"]["synastry"] = { + "total_score": 31.0, + "is_approved": True, + "additional_kutas": { + "Vedha": "good", + "Rajju": {"result": "good"}, + "BadConstellations": "good", + }, + } + + strict = _collect_strict_evidence("relationship", result) + + assert strict["present_evidence"]["synastry_relationship_support"]["level"] == "supportive" + assert strict["event_judgement"]["dominant_label"] is None + assert "synastry_support" in strict["event_judgement"]["secondary_context"] + assert "d9_navamsa" in strict["missing_evidence"] + assert "upapada_lagna" in strict["missing_evidence"] + + def test_relationship_dignity_guardrail_ignores_non_relevant_planets() -> None: result = _base_relationship_result() result["modules"]["chart"] = { diff --git a/tests/test_vedastro_service_adapter_executor.py b/tests/test_vedastro_service_adapter_executor.py index 0d3f2f23..c6a09893 100644 --- a/tests/test_vedastro_service_adapter_executor.py +++ b/tests/test_vedastro_service_adapter_executor.py @@ -280,6 +280,14 @@ def test_vedastro_service_adapter_can_normalize_mock_range_scan_response() -> No assert report["status"] == "ok" assert report["operation"] == "range_scan" assert report["domain"] == "marriage" + assert report["event_count"] == 1 + assert report["top_event"] == { + "event_id": "jupiter_7h_window", + "score": 72, + "start": "2026-05-01", + "end": "2026-06-01", + "tags": ["marriage", "transit"], + } assert report["evidence_ledger"][0]["event_id"] == "jupiter_7h_window" assert report["evidence_ledger"][0]["domain"] == "marriage" assert report["evidence_ledger"][0]["score"] == 72