From b637528f14bcadbfdcb3887183bd982ecd3b5bea Mon Sep 17 00:00:00 2001 From: Jesse_Chen Date: Mon, 10 Aug 2026 19:39:52 +0800 Subject: [PATCH] fix: propagate consultation evidence before audit --- docs/BUG_HISTORY.md | 16 +++++ docs/research/pre_work_error_ledger.md | 6 ++ scripts/jyotish_api_server.py | 75 +++++++++++++++++---- tests/test_api_server_security.py | 3 + tests/test_consultation_consumer_context.py | 4 ++ 5 files changed, 91 insertions(+), 13 deletions(-) diff --git a/docs/BUG_HISTORY.md b/docs/BUG_HISTORY.md index d695cf9f..42c5193e 100644 --- a/docs/BUG_HISTORY.md +++ b/docs/BUG_HISTORY.md @@ -2696,3 +2696,19 @@ - 相关记录:ERR-011、ERR-014、ERR-078 - 复发自:无 - 修复版本:本次同步提交 + +## BUG-159 | consultation 报告漏挂 Ashtakavarga/KP 且 VedAstro raw response 晚于证据包生成 + +- 状态:resolved(local 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,开工预检通过。 +- 防复发:本地计算器存在不等于已进入报告 modules;machine evidence packet 不得在 official gateway 完成本次调用前冻结;`official_verified` 必须同时具备 closure state 与 raw response。 +- 相关记录:ERR-020、ERR-021、ERR-022、ERR-024、ERR-025、ERR-026、ERR-104 +- 复发自:无 +- 修复版本:待提交 diff --git a/docs/research/pre_work_error_ledger.md b/docs/research/pre_work_error_ledger.md index c3167163..7bb3dc74 100644 --- a/docs/research/pre_work_error_ledger.md +++ b/docs/research/pre_work_error_ledger.md @@ -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. diff --git a/scripts/jyotish_api_server.py b/scripts/jyotish_api_server.py index 8b85e45c..72927a99 100644 --- a/scripts/jyotish_api_server.py +++ b/scripts/jyotish_api_server.py @@ -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', diff --git a/tests/test_api_server_security.py b/tests/test_api_server_security.py index b2b5434a..e5a20977 100644 --- a/tests/test_api_server_security.py +++ b/tests/test_api_server_security.py @@ -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', diff --git a/tests/test_consultation_consumer_context.py b/tests/test_consultation_consumer_context.py index 4cf8bc61..76c10d04 100644 --- a/tests/test_consultation_consumer_context.py +++ b/tests/test_consultation_consumer_context.py @@ -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: