Enforce external-truth Yogi finance boundary

This commit is contained in:
732642856
2026-06-28 10:32:10 +08:00
parent 0bee529579
commit ba867a4b8c
4 changed files with 259 additions and 23 deletions
@@ -0,0 +1,70 @@
# Git Execution Card - Finance Yogi Boundary Only
日期:2026-06-28
目标:只提交 finance adjudicator 的外部真值 Yogi 边界修复,不混入其他工作树噪音。
## 目标文件
1. `mcp_server.py`
2. `tests/test_mcp_strict_workflow_finance.py`
3. `docs/research/wealth_adjudicator_fifth_pass_external_truth_boundary_2026_06_28.md`
## 执行顺序
### Step 1 - 确认暂存范围
```bash
git -C /Users/wuyongnaren/Documents/印度占星 reset
git -C /Users/wuyongnaren/Documents/印度占星 add -- \
mcp_server.py \
tests/test_mcp_strict_workflow_finance.py \
docs/research/wealth_adjudicator_fifth_pass_external_truth_boundary_2026_06_28.md
```
### Step 2 - 检查暂存区
```bash
git -C /Users/wuyongnaren/Documents/印度占星 diff --cached --name-only
git -C /Users/wuyongnaren/Documents/印度占星 diff --cached --stat
git -C /Users/wuyongnaren/Documents/印度占星 diff --cached --check
```
预期:只看到上述 3 个文件。
### Step 3 - 回归验证
```bash
python3 -m pytest /Users/wuyongnaren/Documents/印度占星/tests/test_mcp_strict_workflow_finance.py -q
```
预期:
- `12 passed`
### Step 4 - 独立提交
建议提交语义:
```bash
git -C /Users/wuyongnaren/Documents/印度占星 commit -m "Enforce external-truth Yogi finance boundary"
```
### Step 5 - 推送当前分支
```bash
git -C /Users/wuyongnaren/Documents/印度占星 push origin codex/release-hygiene-ci
```
## 禁止事项
这次提交不要混入:
- `docs/research/public_benchmark_dashboard_latest.md`
- `references/oracle/artifacts/pyjhora_oracle_artifact_manifest.json`
- 任意 `patch_*.py`
- 任意本地试验脚本
- 未整理的 sidecar 研究包
## 一句话判断
**这是一张“只交 finance Yogi 真实性边界,不交别的”的推送卡。**
@@ -0,0 +1,74 @@
# Wealth Adjudicator Fifth Pass External Truth Boundary (2026-06-28)
## What Was Fixed
This pass closed a structural contradiction in the finance adjudicator:
- one branch of the codebase was already moving toward `external_truth.yogi_planet`
- another branch was still internally deriving Yogi from Sun/Moon degrees and letting that influence finance promise folding
That violated the new high-rigor boundary.
## Boundary Now Enforced
The finance adjudicator now follows this rule:
1. No external `yogi_planet` -> no Yogi contribution
2. External `yogi_planet` present -> lightweight D1 gate only
3. Only then may Yogi enter:
- `wealth_promise_strength.supporting_sources`
- `source_diversity`
- `secondary_context` as `yogi_active`
## Removed Risk
The following behavior is no longer allowed:
- deriving Yogi from internal Sun/Moon degrees
- upgrading `dhana_yogas` to `dhana_yogi_hooks` without upstream truth
- creating `yogi_support_only` from local chart math
## Regression Contract
The finance regression suite now locks these four truths:
1. `dhana` alone must stay `dhana_yogas` without external truth
2. `dhana + lakshmi` must stay `dhana_lakshmi_hooks` without external truth
3. Yogi-only local chart inputs must not produce a finance promise
4. External truth + valid Kendra/Trikona placement may promote to `dhana_yogi_hooks`
## Verification
Command:
```bash
python3 -m pytest tests/test_mcp_strict_workflow_finance.py -q
```
Observed:
- `12 passed`
Manual probe confirmed:
- `NO_EXTERNAL` -> no Yogi source in promise folding
- `WITH_EXTERNAL` -> `supporting_sources = ["dhana", "yogi"]`
- `secondary_context` includes `yogi_active`
## Why This Matters
This is not just a bug fix. It restores honesty at the adjudication layer.
The finance engine can now say:
- "Yogi affected this judgement because an upstream truth source supplied it"
but can no longer silently smuggle Yogi into the result from local chart math.
## Next Step
Keep the same discipline for all future high-order promise hooks:
1. external truth first
2. lightweight downstream gating second
3. no hidden internal recomputation
+43 -22
View File
@@ -112,36 +112,56 @@ def _derive_wealth_promise_strength(modules: Dict[str, Any]) -> Optional[Dict[st
yogas_doshas = modules.get("yogas_doshas") if isinstance(modules, dict) else {}
dhana = yogas_doshas.get("dhana_yogas") if isinstance(yogas_doshas, dict) else {}
yogas = dhana.get("yogas") if isinstance(dhana, dict) else None
if not isinstance(yogas, list) or not yogas:
has_dhana = False
has_lakshmi = False
dhana_level = "weak"
lakshmi_level = "weak"
sources = set()
if isinstance(yogas, list) and yogas:
for row in yogas:
if not isinstance(row, dict):
continue
lvl = str(row.get("strength", "")).lower()
row_type = str(row.get("type", "")).lower()
if "lakshmi" in row_type:
sources.add("lakshmi")
has_lakshmi = True
if lvl == "strong" or (lvl == "moderate" and lakshmi_level == "weak"):
lakshmi_level = lvl
elif "dhana" in row_type:
sources.add("dhana")
has_dhana = True
if lvl == "strong" or (lvl == "moderate" and dhana_level == "weak"):
dhana_level = lvl
if not has_dhana and not has_lakshmi:
return None
strengths = []
sources = set()
for row in yogas:
if not isinstance(row, dict):
continue
strengths.append(str(row.get("strength", "")).lower())
row_type = str(row.get("type", "")).lower()
if "lakshmi" in row_type:
sources.add("lakshmi")
elif "dhana" in row_type:
sources.add("dhana")
supporting_sources = sorted(sources)
if any(level == "strong" for level in strengths):
level = "strong"
elif any(level == "moderate" for level in strengths):
level = "moderate"
if has_dhana and has_lakshmi:
primary_source = "dhana_lakshmi_hooks"
elif has_dhana:
primary_source = "dhana_yogas"
else:
level = "weak"
primary_source = "lakshmi_hooks"
if dhana_level == "strong" or lakshmi_level == "strong":
final_level = "strong"
elif dhana_level == "moderate" or lakshmi_level == "moderate":
final_level = "moderate"
else:
final_level = "weak"
supporting_sources = sorted(sources) or ["dhana"]
primary_source = "dhana_lakshmi_hooks" if len(supporting_sources) > 1 else "dhana_yogas"
return {
"level": level,
"level": final_level,
"primary_source": primary_source,
"supporting_sources": supporting_sources,
"count": len(yogas),
"count": len(yogas) if isinstance(yogas, list) else 0,
"source_diversity": len(supporting_sources),
"yogi_support": None,
}
@@ -405,7 +425,8 @@ def _collect_strict_evidence(route: str, result: Dict[str, Any]) -> Dict[str, An
else:
confidence_cap = "medium-low"
event_judgement = _derive_event_judgement(route, present, missing)
if yogi_promise and event_judgement.get("dominant_label") and "yogi_active" not in event_judgement.get("secondary_context", []):
promise = present.get("wealth_promise_strength") or {}
if yogi_promise and "yogi" in promise.get("supporting_sources", []) and event_judgement.get("dominant_label") and "yogi_active" not in event_judgement.get("secondary_context", []):
event_judgement["secondary_context"] = event_judgement.get("secondary_context", []) + ["yogi_active"]
return {
"question_type": route,
+72 -1
View File
@@ -3,7 +3,7 @@
from __future__ import annotations
from mcp_server import _collect_strict_evidence, _derive_event_judgement
from mcp_server import _collect_strict_evidence, _derive_event_judgement, _derive_wealth_promise_strength
def test_finance_public_wealth_label_requires_at_least_moderate_window() -> None:
@@ -174,6 +174,7 @@ def test_collect_strict_evidence_finance_derives_wealth_promise_from_dhana_yogas
"count": 2,
"source_diversity": 1,
"supporting_sources": ["dhana"],
"yogi_support": None,
}
assert strict["event_judgement"]["score"] == 100
assert strict["event_judgement"]["verdict"] == "moderate_probability_window"
@@ -214,9 +215,71 @@ def test_collect_strict_evidence_finance_combines_dhana_and_lakshmi_hooks() -> N
"count": 2,
"source_diversity": 2,
"supporting_sources": ["dhana", "lakshmi"],
"yogi_support": None,
}
def test_wealth_folding_dhana_does_not_self_infer_yogi_without_external_truth() -> None:
modules = {
"yogas_doshas": {
"dhana_yogas": {
"yogas": [{"type": "dhana", "strength": "strong"}]
}
},
"chart": {
"ascendant": {"degree_raw": 133.0},
"planets": {
"Sun": {"degree_raw": 20.0},
"Moon": {"degree_raw": 20.0},
"Venus": {"house": 11, "sign": "Gemini"},
}
}
}
res = _derive_wealth_promise_strength(modules)
assert res["primary_source"] == "dhana_yogas"
assert res["source_diversity"] == 1
assert res["supporting_sources"] == ["dhana"]
assert res["level"] == "strong"
assert res["yogi_support"] is None
def test_wealth_folding_dhana_lakshmi_does_not_self_infer_yogi_without_external_truth() -> None:
modules = {
"yogas_doshas": {
"dhana_yogas": {
"yogas": [{"type": "dhana", "strength": "moderate"}, {"type": "lakshmi", "strength": "moderate"}]
}
},
"chart": {
"ascendant": {"degree_raw": 133.0},
"planets": {
"Sun": {"degree_raw": 20.0},
"Moon": {"degree_raw": 20.0},
"Venus": {"house": 11, "sign": "Gemini"},
}
}
}
res = _derive_wealth_promise_strength(modules)
assert res["primary_source"] == "dhana_lakshmi_hooks"
assert res["source_diversity"] == 2
assert res["level"] == "moderate"
assert res["yogi_support"] is None
def test_wealth_folding_yogi_only_is_blocked_without_external_truth() -> None:
modules = {
"chart": {
"ascendant": {"degree_raw": 133.0},
"planets": {
"Sun": {"degree_raw": 20.0},
"Moon": {"degree_raw": 20.0},
"Venus": {"house": 11, "sign": "Gemini"},
}
}
}
assert _derive_wealth_promise_strength(modules) is None
def test_collect_strict_evidence_finance_adds_yogi_hook_only_when_external_truth_is_present() -> None:
result = {
"modules": {
@@ -253,12 +316,19 @@ def test_collect_strict_evidence_finance_adds_yogi_hook_only_when_external_truth
}
strict = _collect_strict_evidence("finance", result)
assert strict["present_evidence"]["yogi_promise"] == {
"planet": "Venus",
"house": 10,
"status": "入友(Friendly Sign)",
"source": "external_yogi_planet",
}
assert strict["present_evidence"]["wealth_promise_strength"] == {
"level": "moderate",
"primary_source": "dhana_yogi_hooks",
"count": 2,
"source_diversity": 2,
"supporting_sources": ["dhana", "yogi"],
"yogi_support": None,
}
assert "yogi_active" in strict["event_judgement"]["secondary_context"]
@@ -306,5 +376,6 @@ def test_collect_strict_evidence_finance_does_not_promote_yogi_outside_kendra_tr
"count": 1,
"source_diversity": 1,
"supporting_sources": ["dhana"],
"yogi_support": None,
}
assert "yogi_active" not in strict["event_judgement"]["secondary_context"]