W3-d 复核 H1:「最近一次封存」按来源白名单认,问数据按口径出具的正式运行不再顶替看板的数

复核发现:问数据的答案按已治理口径正式出具的运行(origin_kind=ledger_issue,W3-b 加的自动出具缺省开)参数是从某人的
提问里绑出来的,而 apps/resolve.sealed_runs 只排除了补算(app_backfill)。于是 from=formal 的 latest_sealed 视图、
趋势、分享链接(latest,以及建 fixed 分享时取的那次)、视图数据接口、补算判断「新版本算过没有」都会换成那组参数的数,
而且没有任何提示。

做法:sealed_runs 的来源从黑名单改成白名单 SEALED_ORIGINS(定时、手动正式运行、应用入口和按需视图、入站钩子、直接调
接口,外加没有来源列之前的老运行);不算的列在 NOT_SEALED_ORIGINS,逐条写明原因。上面这几处都经 sealed_runs,改一处都
跟上。按白名单认是为了以后再加来源时默认不算、要算得先登记,不会再悄悄漏进看板。

测试(tests/test_shares.py):
- 先跑一次正常的正式运行,再跑一次更新的 ledger_issue 正式运行:应用页、趋势、视图数据接口、latest 和 fixed 两种分享
  都还是正常那次,分享的下载报告也不认出具的那次。去掉修复回跑,这一项失败;
- ORIGIN_KINDS 每种来源都要归进 SEALED_ORIGINS 或 NOT_SEALED_ORIGINS 之一,以后新加的来源没归类先失败。
This commit is contained in:
ThearchyHelios
2026-10-16 18:20:52 +08:00
parent fc9b31ecfe
commit 36c8f581cc
2 changed files with 58 additions and 3 deletions
+15 -3
View File
@@ -52,6 +52,18 @@ PAGE_CACHE_SIZE = 256
#: 按新口径补算的运行来源(engine/origin.py)
BACKFILL_ORIGIN = "app_backfill"
#: 「最近一次封存」认哪几种来源的正式运行:latest_sealed 视图、趋势、分享链接(latest,以及建 fixed 时取的那几次)、视图
#: 数据接口、补算判断「新版本算过没有」都经 sealed_runs,这里改一处都跟上。按白名单认,以后加的来源默认不算,要算的在这里
#: 登记(tests/test_shares.py 核对 ORIGIN_KINDS 每种都归了类)。算的:定时、手动的正式运行、应用的入口和按需视图、入站
#: 钩子、直接调接口;加来源这一列之前的老运行(origin_kind 为空)也算。
SEALED_ORIGINS = ("launcher", "schedule", "app_entry", "app_view", "hook", "api")
#: 不算的,各有原因:
#: - app_backfill:按新口径补算的是现在才算的历史期,不是「最近一次」;趋势另经 trend_runs 按它对应的输入期排进去;
#: - ledger_issue:问数据的答案按已治理口径正式出具(docs/design/next-round/02-口径账本.md §5),参数是从某人的提问里
#: 绑出来的。取到它,看板、趋势、分享链接就悄悄换成了那个人问的那组数;
#: - 其余是探索运行(问数据、画布试跑、试运行、个人应用的刷新,评测、口径对账、口径召回),本来就不是正式运行。
NOT_SEALED_ORIGINS = (BACKFILL_ORIGIN, "ledger_issue", "chat", "canvas", "trial", "personal_app", "eval", "reconcile",
"recall")
VIEW_REFUSED = "app_view_refused"
DAILY_LIMIT = "app_view_daily_limit"
@@ -138,8 +150,8 @@ async def sealed_runs(session: AsyncSession, workflow_id: Any, version: Any, gra
"""钉住版本(同一 graph_hash)最近 n 次成功且已封存的正式运行,新的在前。
sealed_now 认的是「这一次结束时封的」:失败后接着跑完、新封存还没补上的那一刻,带着的是旧封存,不算。
按新口径补算的运行(app_backfill)不算:它们是现在才算的历史期,「最近一次」不能取到它们;趋势另经 trend_runs
按它们对应的输入期排进去。"""
来源只认 SEALED_ORIGINS(和老运行):按新口径补算的(app_backfill)、问数据按口径出具的(ledger_issue)都不是
「最近一次」,原因见 NOT_SEALED_ORIGINS。"""
from sqlalchemy import or_
from app.scheduler.auto_compare import sealed_now
@@ -148,7 +160,7 @@ async def sealed_runs(session: AsyncSession, workflow_id: Any, version: Any, gra
return []
query = select(Run).where(Run.workflow_id == workflow_id, Run.version == version, Run.run_class == "formal",
Run.status == "succeeded", Run.version_hash == graph_hash,
or_(Run.origin_kind.is_(None), Run.origin_kind != BACKFILL_ORIGIN))
or_(Run.origin_kind.is_(None), Run.origin_kind.in_(SEALED_ORIGINS)))
if isinstance(origin, str) and origin.startswith("schedule:"):
query = query.where(Run.schedule_id == origin[len("schedule:"):])
rows = list((await session.execute(query.order_by(Run.finished_at.desc(), Run.created_at.desc())
+43
View File
@@ -225,6 +225,49 @@ async def test_fixed_mode_stays_on_the_runs_it_was_created_with(world): # noqa:
assert moving["view"]["sources"]["latest"]["run_id"] == second.id
async def test_runs_issued_from_questions_are_never_the_latest(world): # noqa: F811
"""问数据的答案按口径正式出具的运行(ledger_issue)参数是从某人的提问里绑出来的:应用页、趋势、分享链接(latest 和建
fixed 时取的那次)、视图数据接口的「最近一次封存」都不取它,下载报告也不认它(复核 H1)。"""
from app.engine.launch import launch_run
svc = await auth_env.make_service()
wf, h = await workflow(KPI)
app_id = await published(world, board(wf, h), [("user", "svc-portal", "observe")])
regular = await formal(wf, "W1")
async with SessionLocal() as s:
launched = await launch_run(s, workflow_id=wf, run_class="formal", version=1, input_payload={"week": "W9"},
actor="编辑", origin_kind="ledger_issue", origin={"source_run": "提问那次"},
expect_hash=h)
issued = await wait(launched.id)
assert issued.status == "succeeded" and issued.manifest_hash, "出具的运行成功并封存了,是更新的那一次"
admin = await auth_env.login("root")
page = (await admin.get(f"/api/apps/{app_id}/pages/home")).json()
assert page["sources"]["latest"]["run_id"] == regular.id, "看板不换成某人提问的那组参数"
points = page["tree"][1]["data"]["series"][-1]["points"]
assert [p["run_id"] for p in points] == [regular.id], "趋势里也没有它"
_, secret = await auth_env.make_key(svc, scopes=("apps.read",))
data = (await auth_env.keyed(secret).get(f"/api/apps/{app_id}/views/latest/data")).json()
assert data["state"] == "ok" and data["run_id"] == regular.id, "视图数据接口同一个取法"
fixed = await share(app_id, mode="fixed")
latest = await share(app_id)
for made in (fixed, latest):
async with anonymous(**{HEADER: made["key"]}) as c:
body = (await c.get("/api/shares/page")).json()
assert body["view"]["sources"]["latest"]["run_id"] == regular.id, made["mode"]
refused = await c.get("/api/shares/report.html", params={"run": issued.id})
assert refused.status_code == 404 and refused.json()["code"] == "share_run_not_shared", made["mode"]
def test_every_run_origin_is_classified_for_latest_sealed():
"""每种运行来源都要说清算不算「最近一次封存」:以后加了来源、没在 apps/resolve 归类的,这里先失败。"""
from app.apps.resolve import NOT_SEALED_ORIGINS, SEALED_ORIGINS
from app.engine.origin import ORIGIN_KINDS
assert not set(SEALED_ORIGINS) & set(NOT_SEALED_ORIGINS)
assert set(SEALED_ORIGINS) | set(NOT_SEALED_ORIGINS) == set(ORIGIN_KINDS)
assert "ledger_issue" in NOT_SEALED_ORIGINS and "app_backfill" in NOT_SEALED_ORIGINS
async def test_report_download_only_for_the_shares_runs_and_masked(world, monkeypatch, tmp_path): # noqa: F811
import sqlite3