fix(consult): require the evidence the product tells the user it will use

修完 BUG-267 后核对同一张表时发现,前端领域注册表早就为每个领域声明了 requiredLayers,
键名正确,并且这份列表驱动界面上的 evidencePreview——也就是产品明确告诉用户「这次会用
这些证据」。服务端的门是另写的一份,两份用不同词汇描述同一个合同,彼此没有任何链接,
对照下来门少查三处:marriage 声明 A7 而门只要求 UL,wealth 声明 Ashtakavarga 而门只
要求 D2,general 声明 D10 与 D2 而门只要求 D1/D9。三者实测在真实排盘中都是 used,补进
去不会把状态推成 degraded。失败方向仍是静默放宽,因此没有任何人报错。

新增一条测试解析前端注册表,断言其中每个指向真实 section 的条目都出现在服务端的门里。
只比对真实 section:注册表里还有人看的标签(7th house/lord、negative holdout gate)和
引擎压根不产出的 D11,机械全量对齐会把每条路由钉死在 degraded。该测试 fail-closed——
先断言 10 条路由全部解析到且列表非空,否则一次正则失配就会让它无声通过。

另外把 tests/test_consultation_consumer_context.py 加进 CORE_PYTEST_TARGETS:该文件
不在任何 CI 档位里,BUG-267 的 8 条与本条的 1 条此前都只在本地手动跑过,staging 门用的
是 --profile quick,从不运行它们。防漂移的钉子本身没人跑,等于没钉。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-08-18 12:45:11 +08:00
parent a12f57970b
commit 5caf47a413
4 changed files with 81 additions and 5 deletions
+9 -3
View File
@@ -696,17 +696,23 @@ def _attach_local_consultation_layers(handler, chart: dict, birth_payload: dict,
# `ready`. A route that falls through to `general`'s gate is checked against a weaker evidence set
# without anything failing, which is how `marriage` went unchecked for UL and `wealth` for D2; the
# table therefore covers every defined route and a test pins that coverage (BUG-267).
#
# The gate must also not be weaker than what the product tells the user it will use. The frontend
# domain registry declares each domain's layers for its evidence preview, and every entry there that
# names a real section is required here too; a test pins that intersection (BUG-269). The registry
# additionally names human-facing items ('7th house/lord', 'negative holdout gate') and D11, which
# the engine does not build at all — those are deliberately not requirements.
_ROUTE_REQUIRED_LAYERS = {
'career': ('D1', 'D10', 'A10', 'dasha_boundaries', 'narayana_dasha'),
'marriage': ('D1', 'D9', 'UL', 'dasha_boundaries'),
'wealth': ('D1', 'D2', 'dasha_boundaries'),
'marriage': ('D1', 'D9', 'UL', 'A7', 'dasha_boundaries'),
'wealth': ('D1', 'D2', 'ashtakavarga', 'dasha_boundaries'),
'health': ('D1', 'D9', 'dasha_boundaries'),
'education': ('D1', 'D9', 'dasha_boundaries'),
'migration': ('D1', 'D4', 'dasha_boundaries'),
'family': ('D1', 'D9', 'dasha_boundaries'),
'annual': ('D1', 'dasha_boundaries', 'narayana_dasha'),
'timing': ('D1', 'dasha_boundaries', 'narayana_dasha'),
'general': ('D1', 'D9', 'dasha_boundaries'),
'general': ('D1', 'D9', 'D10', 'D2', 'dasha_boundaries'),
}
# The context layers and interpretation boundaries a domain carries. These follow the resolved route
+4
View File
@@ -51,6 +51,10 @@ CORE_PYTEST_TARGETS = [
"tests/test_oracle_collection_queue.py",
"tests/test_oracle_evidence_validator.py",
"tests/test_external_oracle_sanity_closure.py",
# The staging gate runs the quick profile, so a guard absent from this list never runs in CI.
# These pin the answer-truth contract every product consultation is built on, and their failure
# mode is silent widening — nothing errors when they regress (BUG-267, BUG-269).
"tests/test_consultation_consumer_context.py",
]
RUNTIME_TRUTH_PYTEST_TARGETS = [