fix: propagate consultation evidence before audit
Staging Backend Quality Gate / validate (push) Successful in 12m37s
Staging Backend Quality Gate / publish (push) Successful in 8m25s

This commit is contained in:
Jesse_Chen
2026-08-10 19:39:52 +08:00
parent 2697d16ef1
commit b637528f14
5 changed files with 91 additions and 13 deletions
+16
View File
@@ -2696,3 +2696,19 @@
- 相关记录:ERR-011、ERR-014、ERR-078
- 复发自:无
- 修复版本:本次同步提交
## BUG-159 | consultation 报告漏挂 Ashtakavarga/KP 且 VedAstro raw response 晚于证据包生成
- 状态:resolvedlocal candidate,待 staging gate/deployment
- 首次发现:2026-08-10
- 最近更新:2026-08-10
- 影响面:个人报告 consultation workflow、Technique Audit、VedAstro official evidence、`modules.ashtakavarga``modules.kp_cusps`
- 用户现象:Technique Audit 将 Ashtakavarga、KP cusp 和官方 VedAstro 标成 blocked;即使已配置 API key,当前报告仍提示官方外部引擎未验证或不可用。
- 触发条件:报告通过 consultation workflow 生成 machine evidence packet;本地排盘已有 planets/ascendant,或 VedAstro gateway 本次返回 official raw response。
- 根因:consultation 本地补充层只挂入 Varga、Arudha 与 Narayana,未调用仓库已有的 Ashtakavarga/KP 计算;同时 VedAstro gateway 在 machine evidence packet 冻结后才执行,导致本次 raw response 无法进入报告审计。API key 只满足凭据配置,不证明本次调用、响应与证据传播成功。
- 修复:复用现有 `_compute_ashtakavarga``_normalized_planets_from_body``_compute_kp`,把结果挂入既有 modules;将 gateway 调用提前到 evidence packet 生成前,仅在 `official_verified` 且存在本次 raw response 时合并 official evidence,缺 raw 时继续失败闭合。
- 验证:consultation consumer/API 聚焦回归锁定 Ashtakavarga SAV、12 个 KP houses、两项 audit 为 used,以及 VedAstro raw response 传播至 `official_verified`;相关聚焦测试 34 passed,开工预检通过。
- 防复发:本地计算器存在不等于已进入报告 modulesmachine evidence packet 不得在 official gateway 完成本次调用前冻结;`official_verified` 必须同时具备 closure state 与 raw response。
- 相关记录:ERR-020、ERR-021、ERR-022、ERR-024、ERR-025、ERR-026、ERR-104
- 复发自:无
- 修复版本:待提交
+6
View File
@@ -263,3 +263,9 @@ Prevention: retry fetch and `git ls-remote` before any push or release action, c
手工 Release Gate Run `1638``xiaoxin` 上通过 Docker Engine 检查后进入完整测试,但该 runner 不支持 `docker compose``--project-name``--env-file` 被 Docker 顶层 CLI 判为 unknown flag,导致 17 个 PostgreSQL、备份、身份和权限集成测试级联失败。候选 staging SHA、旧生产、Supabase 与 DNS 未被改变。
Prevention: 将该门禁运行在已验证 Docker Compose v2 的 `manman-linux`,并在安装依赖前以 `docker compose version --short` 强制 v2;不得把 `docker version` 当作 Compose 能力证明。新 SHA 必须重新完成 staging gate、公网 staging 身份和手工 Release Gate,失败 run 不得授权生产 migration、deploy 或维护停写。
## ERR-104 | Consultation evidence packet can freeze before gateway evidence and omit existing local modules | mitigated 2026-08-10
The consultation workflow built its machine evidence packet before invoking the VedAstro gateway, so a configured credential and even a successful official raw response could not affect the current report. The same workflow also failed to attach the repository's existing Ashtakavarga and KP calculators to `chart.modules`, leaving both Technique Audit rows blocked despite valid local chart inputs.
Prevention: invoke the official gateway before freezing the evidence packet; require `official_verified` plus a non-empty raw response before promoting official evidence; attach existing local calculator outputs through the shared consultation layer and keep KP values `observation_only` until an external worked-example parity gate closes. Regression tests must lock both raw-response propagation and module presence.
+62 -13
View File
@@ -653,12 +653,33 @@ def _attach_local_consultation_layers(handler, chart: dict, birth_payload: dict,
except Exception as exc:
diagnostics.append({'layer': 'narayana_dasha', 'status': 'unavailable', 'reason': exc.__class__.__name__})
if planets and ascendant and not isinstance(modules.get('ashtakavarga'), dict):
try:
response = handler._compute_ashtakavarga({'planets': planets, 'ascendant': ascendant})
result = response.get('result') if isinstance(response, dict) else None
if isinstance(result, dict):
modules['ashtakavarga'] = result
except Exception as exc:
diagnostics.append({'layer': 'ashtakavarga', 'status': 'unavailable', 'reason': exc.__class__.__name__})
if planets and ascendant and not isinstance(modules.get('kp_cusps'), dict):
try:
normalized, _, asc_sign_idx = handler._normalized_planets_from_body({
'planets': planets,
'ascendant': ascendant,
})
kp_result = handler._compute_kp({'planets': normalized, 'asc_sign_idx': asc_sign_idx})
if isinstance(kp_result, dict):
modules['kp_cusps'] = kp_result
except Exception as exc:
diagnostics.append({'layer': 'kp_cusps', 'status': 'unavailable', 'reason': exc.__class__.__name__})
chart['local_consultation_layers'] = {
'status': 'ready' if not diagnostics else 'partial',
'source': 'repository_local_engines',
'available': [
name
for name in ('varga_full', 'arudha_padas', 'narayana_dasha')
for name in ('varga_full', 'arudha_padas', 'narayana_dasha', 'ashtakavarga', 'kp_cusps')
if isinstance(modules.get(name), dict) and modules.get(name)
],
'diagnostics': diagnostics,
@@ -970,7 +991,47 @@ def execute_consultation_workflow(
})
executed_steps.append('run_thematic_report')
vedastro_gateway = rectification.get('vedastro_gateway') if isinstance(rectification, dict) else None
if not isinstance(vedastro_gateway, dict):
try:
vedastro_gateway = handler._compute_vedastro_gateway_run(body)
except Exception as exc: # Gateway evidence must not block the local chart result.
vedastro_gateway = {
'scope': 'vedastro_gateway_run',
'status': 'official_blocked',
'official_closure_reason': 'gateway_invocation_error',
'error_type': type(exc).__name__,
}
vedastro_official = handler._high_rigor_vedastro_official_summary(chart)
gateway_raw = (
vedastro_gateway.get('official_raw_response')
or vedastro_gateway.get('raw_response')
or vedastro_gateway.get('vedastro_official_raw_response')
) if isinstance(vedastro_gateway, dict) else None
gateway_closure = (
vedastro_gateway.get('official_closure_state') or vedastro_gateway.get('status')
) if isinstance(vedastro_gateway, dict) else None
if gateway_closure == 'official_verified' and gateway_raw:
prior_truth = vedastro_official.get('runtime_truth') if isinstance(vedastro_official.get('runtime_truth'), dict) else {}
prior_layers = prior_truth.get('official_execution_layers') if isinstance(prior_truth.get('official_execution_layers'), dict) else {}
vedastro_official = {
**vedastro_official,
'status': 'ok',
'official_closure_state': 'official_verified',
'raw_response': gateway_raw,
'runtime_truth': {
**prior_truth,
'status': 'ok',
'official_closure_state': 'official_verified',
'official_execution_layers': {**prior_layers, 'chart_core': 'ok'},
'fallback_active': False,
},
}
elif isinstance(vedastro_gateway, dict):
vedastro_official.setdefault('official_closure_state', gateway_closure or 'official_blocked')
vedastro_official.setdefault('official_closure_reason', vedastro_gateway.get('official_closure_reason'))
vedastro_archive_manifest = handler._compute_vedastro_gateway_archives()
runtime_truth = vedastro_official.get('runtime_truth') if isinstance(vedastro_official.get('runtime_truth'), dict) else {}
interpretation_source_runtime_coverage = handler._interpretation_source_runtime_coverage(chart)
@@ -1007,18 +1068,6 @@ def execute_consultation_workflow(
blind=bool(body.get('blind') or body.get('blind_technical_mode')),
)
vedastro_gateway = rectification.get('vedastro_gateway') if isinstance(rectification, dict) else None
if not isinstance(vedastro_gateway, dict):
try:
vedastro_gateway = handler._compute_vedastro_gateway_run(body)
except Exception as exc: # Gateway evidence must not block the local chart result.
vedastro_gateway = {
'scope': 'vedastro_gateway_run',
'status': 'official_blocked',
'official_closure_reason': 'gateway_invocation_error',
'error_type': type(exc).__name__,
}
result = {
'success': True,
'endpoint': 'consultation_workflow',
+3
View File
@@ -2350,6 +2350,9 @@ def test_consultation_workflow_uses_unified_orchestrator_contract(monkeypatch) -
assert result['runtime_planner']['planner_name'] == 'UnifiedConsultationRuntimePlanner'
assert result['runtime_planner']['entry_mode'] == 'direct_chart'
assert result['runtime_planner']['route']['question_type'] == 'career'
assert result['vedastro_official']['raw_response']['request_id'] == 'test-live-call'
assert result['machine_evidence_packet']['sections']['external_oracle_status']['status'] == 'official_verified'
assert result['machine_evidence_packet']['sections']['vedastro_official_raw_response']['status'] == 'used'
assert result['runtime_planner']['sync_steps'][0] == 'compute_chart'
assert result['runtime_planner']['executed_steps'] == [
'compute_chart',
@@ -68,6 +68,8 @@ def test_local_consultation_layers_supply_d10_a10_and_narayana_without_vedastro(
assert 'D10_Dasamsa' in modules['varga_full']
assert modules['arudha_padas']['padas']['A10']['name'] == 'Karma Pada (A10)'
assert modules['narayana_dasha']['current_dasha']['md']
assert modules['ashtakavarga']['sav']['total'] > 0
assert len(modules['kp_cusps']['houses']) == 12
assert chart['local_consultation_layers']['status'] == 'ready'
packet = UnifiedConsultationOrchestrator().machine_evidence_packet(
@@ -78,6 +80,8 @@ def test_local_consultation_layers_supply_d10_a10_and_narayana_without_vedastro(
assert packet['sections']['D10']['status'] == 'used'
assert packet['sections']['A10']['status'] == 'used'
assert packet['sections']['narayana_dasha']['status'] == 'used'
assert packet['sections']['ashtakavarga']['status'] == 'used'
assert packet['sections']['KP_cusp']['status'] == 'used'
def test_consumer_context_treats_unconfigured_vedastro_as_optional_cross_check() -> None: