把星盘接口的计算和 HTTP 拆成两个类,离线入口不再伪造请求对象。
方法体未改。定向测试 383 通过;1 条校正回归在基线上同样失败,断言未改。快速门和全量 pytest 未跑。 Co-Authored-By: Grok (agent loop)
This commit is contained in:
co-authored by
Grok (agent loop)
parent
fbf04f0b5f
commit
c7350bcafb
@@ -2,11 +2,12 @@
|
||||
|
||||
New endpoints and features must live in new modules and be thinly registered
|
||||
from the main file. Line count is only a coarse guardrail; the live gates are
|
||||
JyotishAPIHandler method count and JyotishAPIHandler.__new__ forgery sites.
|
||||
handler method count and JyotishAPIHandler.__new__ forgery sites.
|
||||
|
||||
Baselines re-measured 2026-09-16 after TASK-api-server-backdoor-close-20260916
|
||||
moved seven compute-only methods into scripts/offline_compute_mixins.py and
|
||||
closed two of the four production forgeries.
|
||||
Baselines re-measured 2026-10-16 after TASK-api-server-service-extract-20261013
|
||||
moved HTTP methods onto JyotishAPIHandler and left calculation on
|
||||
JyotishComputeService. Production forgeries went from 2 to 0. The two classes
|
||||
stay in this file so existing module-attribute patches still land.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -23,6 +24,9 @@ AGENTS = ROOT / "AGENTS.md"
|
||||
# Live `wc -l scripts/jyotish_api_server.py` equivalent:
|
||||
# Path.read_bytes().count(b"\n"), measured 2026-09-16 on origin/staging @ 51a65d92.
|
||||
# New features must not consume this budget; open a module instead.
|
||||
# 原值:10924
|
||||
# 新值:10924
|
||||
# 原因:行数基线按任务书保持不动。拆类只加了类声明,实测行数仍不超过这条基线。
|
||||
JYOTISH_API_SERVER_LINE_COUNT_BASELINE = 10924
|
||||
JYOTISH_API_SERVER_LINE_COUNT_CAP = JYOTISH_API_SERVER_LINE_COUNT_BASELINE + 300
|
||||
|
||||
@@ -30,22 +34,21 @@ JYOTISH_API_SERVER_LINE_COUNT_CAP = JYOTISH_API_SERVER_LINE_COUNT_BASELINE + 300
|
||||
# TASK-freeze-metric-change-20260915 §1. Re-measured 2026-09-16 after
|
||||
# TASK-api-server-backdoor-close-20260916 lifted 7 compute-only methods into
|
||||
# scripts/offline_compute_mixins.py (225 -> 218).
|
||||
# 原值:218(删占位回退之前)
|
||||
# 原值:217
|
||||
# 新值:217
|
||||
# 原因:BUG-1341 删除 JyotishAPIHandler._fallback_dasha_periods,类方法少 1 个,没有新增方法
|
||||
# 原因:BUG-1341 已把基线降到 217。本单只把 HTTP 方法换到子类,不增删方法。
|
||||
JYOTISH_API_HANDLER_METHOD_COUNT_BASELINE = 217
|
||||
|
||||
# `JyotishAPIHandler.__new__` in scripts/ and tests/ `*.py`, excluding this file.
|
||||
# Re-measured 2026-09-16 after TASK-api-server-backdoor-close-20260916:
|
||||
# scripts/ production forgeries 4 -> 2, tests/ unchanged at 29.
|
||||
JYOTISH_API_HANDLER_NEW_COUNT_BASELINE = 31
|
||||
# 原值:31
|
||||
# 新值:5
|
||||
# 原因:生产 2 处和只用计算方法的测试改为 JyotishComputeService()。留下的 5 处要碰 HTTP,本单不改。
|
||||
JYOTISH_API_HANDLER_NEW_COUNT_BASELINE = 5
|
||||
|
||||
# Production forgeries only. The two survivors both sit on the consultation
|
||||
# workflow chain (`consultation_workflow_service.execute_consultation_workflow`
|
||||
# and `capture_report_blocked_repairs_golden`); closing them needs the 4,086-line
|
||||
# mixin extraction that the 2026-09-16 spike showed breaks cross-module
|
||||
# monkeypatching in tests/test_api_server_security.py. Target stays 0.
|
||||
JYOTISH_API_HANDLER_NEW_SCRIPTS_BASELINE = 2
|
||||
# Production forgeries only. Offline callers instantiate JyotishComputeService.
|
||||
# 原值:2
|
||||
# 新值:0
|
||||
# 原因:consultation_workflow_service 与 capture_report_blocked_repairs_golden 改为正常实例化计算类。
|
||||
JYOTISH_API_HANDLER_NEW_SCRIPTS_BASELINE = 0
|
||||
|
||||
# Compute-only mixins lifted out of the handler must never depend back on the
|
||||
# HTTP monolith, or the backdoor simply moves house.
|
||||
@@ -141,3 +144,88 @@ def test_quality_gate_runs_api_server_growth_contract() -> None:
|
||||
assert "tests/test_api_server_growth_contract.py" in CORE_PYTEST_TARGETS
|
||||
quality_gate = (ROOT / "scripts" / "run_quality_gate.py").read_text(encoding="utf-8")
|
||||
assert '"tests/test_api_server_growth_contract.py"' in quality_gate
|
||||
|
||||
|
||||
_HTTP_ATTRS = frozenset({
|
||||
"headers",
|
||||
"wfile",
|
||||
"rfile",
|
||||
"client_address",
|
||||
"send_response",
|
||||
"send_header",
|
||||
"end_headers",
|
||||
"path",
|
||||
"command",
|
||||
})
|
||||
_HTTP_METHOD_NAMES = frozenset({
|
||||
"_json",
|
||||
"_html",
|
||||
"_send_cors_headers",
|
||||
"_read_json_body",
|
||||
"_enforce_request_security",
|
||||
"_job_access_token",
|
||||
"do_GET",
|
||||
"do_POST",
|
||||
"do_OPTIONS",
|
||||
"_error_json",
|
||||
"_get_chart_job",
|
||||
"_get_high_rigor_job",
|
||||
"_require_dynamic_rectification_token",
|
||||
"_compute_dynamic_rectification_score",
|
||||
"_compute_dynamic_rectification_opportunities",
|
||||
})
|
||||
|
||||
|
||||
def test_compute_service_is_not_an_http_handler() -> None:
|
||||
"""JyotishComputeService can be constructed. HTTP stays on the subclass."""
|
||||
from http.server import BaseHTTPRequestHandler
|
||||
|
||||
from scripts.jyotish_api_server import JyotishAPIHandler, JyotishComputeService
|
||||
|
||||
assert not issubclass(JyotishComputeService, BaseHTTPRequestHandler)
|
||||
assert issubclass(JyotishAPIHandler, JyotishComputeService)
|
||||
assert issubclass(JyotishAPIHandler, BaseHTTPRequestHandler)
|
||||
mro_names = [item.__name__ for item in JyotishAPIHandler.__mro__]
|
||||
assert mro_names.index("BaseHTTPRequestHandler") < mro_names.index("JyotishComputeService")
|
||||
assert JyotishAPIHandler.server_version == "JyotishAPI/6.9.14"
|
||||
assert "server_version" not in JyotishComputeService.__dict__
|
||||
for name in _HTTP_METHOD_NAMES:
|
||||
assert name in JyotishAPIHandler.__dict__, name
|
||||
assert name not in JyotishComputeService.__dict__, name
|
||||
|
||||
|
||||
def _http_touches(fn: ast.AST) -> list[str]:
|
||||
hits: list[str] = []
|
||||
for node in ast.walk(fn):
|
||||
if (
|
||||
isinstance(node, ast.Attribute)
|
||||
and isinstance(node.value, ast.Name)
|
||||
and node.value.id == "self"
|
||||
and node.attr in _HTTP_ATTRS | _HTTP_METHOD_NAMES
|
||||
):
|
||||
hits.append(f"{node.lineno}:{node.attr}")
|
||||
if (
|
||||
isinstance(node, ast.Call)
|
||||
and isinstance(node.func, ast.Name)
|
||||
and node.func.id == "getattr"
|
||||
and len(node.args) >= 2
|
||||
and isinstance(node.args[0], ast.Name)
|
||||
and node.args[0].id == "self"
|
||||
and isinstance(node.args[1], ast.Constant)
|
||||
and node.args[1].value in _HTTP_ATTRS | _HTTP_METHOD_NAMES
|
||||
):
|
||||
hits.append(f"{node.lineno}:getattr:{node.args[1].value}")
|
||||
return hits
|
||||
|
||||
|
||||
def test_compute_service_methods_do_not_touch_http() -> None:
|
||||
tree = ast.parse(API_SERVER.read_text(encoding="utf-8"))
|
||||
compute = next(
|
||||
node for node in tree.body
|
||||
if isinstance(node, ast.ClassDef) and node.name == "JyotishComputeService"
|
||||
)
|
||||
offenders: list[str] = []
|
||||
for node in compute.body:
|
||||
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
||||
offenders.extend(f"{node.name}:{hit}" for hit in _http_touches(node))
|
||||
assert offenders == []
|
||||
|
||||
Reference in New Issue
Block a user