From 8f64853bf6498734c150c5c8dcbf994f340ef904 Mon Sep 17 00:00:00 2001 From: ThearchyHelios Date: Wed, 14 Oct 2026 17:54:58 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A5=91=E7=BA=A6=E9=AA=A8=E6=9E=B6=E8=A1=A5?= =?UTF-8?q?=E5=85=85=EF=BC=9Alaunch=5Frun=20=E5=92=8C=20run=5Fmanager.star?= =?UTF-8?q?t=20=E6=8E=A5=E6=94=B6=E8=BF=90=E8=A1=8C=E6=9D=A5=E6=BA=90?= =?UTF-8?q?=EF=BC=88origin=5Fkind=E3=80=81origin=E3=80=81app=5Fid=E3=80=81?= =?UTF-8?q?app=5Fversion=EF=BC=89=E5=92=8C=20expect=5Fhash=EF=BC=8C?= =?UTF-8?q?=E6=9D=A5=E6=BA=90=E8=AE=B0=E5=9C=A8=E8=BF=90=E8=A1=8C=E4=B8=8A?= =?UTF-8?q?=E3=80=81run.origin=20=E4=BA=8B=E4=BB=B6=E6=94=BE=E8=BF=9B?= =?UTF-8?q?=E5=8F=91=E8=B5=B7=E6=97=B6=E7=9A=84=20notes=EF=BC=88=E5=9C=A8?= =?UTF-8?q?=E5=B0=81=E5=AD=98=E8=8C=83=E5=9B=B4=E5=86=85=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/engine/launch.py | 23 +++++++++++++++++++++++ backend/app/engine/runner.py | 10 ++++++++++ 2 files changed, 33 insertions(+) diff --git a/backend/app/engine/launch.py b/backend/app/engine/launch.py index dddfd94b..9cc63a9c 100644 --- a/backend/app/engine/launch.py +++ b/backend/app/engine/launch.py @@ -55,10 +55,23 @@ async def launch_run( actor: str | None, schedule_id: str | None = None, options: dict[str, Any] | None = None, + origin_kind: str | None = None, + origin: dict[str, Any] | None = None, + app_id: str | None = None, + app_version: int | None = None, + expect_hash: str | None = None, ) -> Run: """发起一次运行,返回刚建好的运行记录(执行已经交给 run_manager)。不发起就抛 LaunchRefused;抛别的异常时 运行也没有开始执行。 + 应用平台(docs/design/app-platform-and-look.md §2.1、§2.10 X3): + - origin_kind / origin:运行来源(engine/origin.py 的 ORIGIN_KINDS)。记在运行上,同时把 run.origin 事件放进 + notes(在封存范围内)。调用方:POST /api/runs(客户端只能声明 CLIENT_ORIGINS)、定时任务(schedule)、 + 应用入口和视图(app_*)、入站钩子(hook)。 + - app_id / app_version:应用发起的运行属于哪个应用版本。 + - expect_hash:应用发起正式运行时必须带上钉住版本的 graph_hash,和快照对不上就拒绝(run_version_hash_mismatch)。 + 「已发布且未退役」的判定见 app/apps/versions.py(BE-3 实现,替换下面「只认当前发布版」的规则,F1)。 + 参数和 POST /api/runs 的请求体一一对应(input 叫 input_payload);actor 是记在运行上的发起人。 schedule_id 是发起它的定时任务,options 是整次运行沿用的选项快照,都原样交给 run_manager.start;图里有双盲复算、 反方审稿节点时 options 另加核查设置的快照(_with_verify_snapshot)。 @@ -159,6 +172,12 @@ async def launch_run( payload_ev = dict(ev) node = payload_ev.pop("node_id", None) notes.append(("caliber.upgrade", node, payload_ev)) + if origin_kind: + from app.engine.origin import origin_note + + data = {**(origin or {}), "actor": actor, "app_id": app_id, "app_version": app_version} + data.pop("kind", None) + notes.insert(0, (EventType.RUN_ORIGIN, None, origin_note(origin_kind, **data))) try: run = await run_manager.start( graph=graph, @@ -174,6 +193,10 @@ async def launch_run( options=options, schedule_id=schedule_id, notes=notes, + origin_kind=origin_kind, + origin=origin, + app_id=app_id, + app_version=app_version, ) except ValueError as e: raise LaunchRefused(400, str(e)) from e diff --git a/backend/app/engine/runner.py b/backend/app/engine/runner.py index 2e65f504..8389af5d 100644 --- a/backend/app/engine/runner.py +++ b/backend/app/engine/runner.py @@ -195,8 +195,14 @@ class RunManager: options: dict[str, Any] | None = None, schedule_id: str | None = None, notes: Sequence[tuple[EventType | str, str | None, dict[str, Any]]] = (), + origin_kind: str | None = None, + origin: dict[str, Any] | None = None, + app_id: str | None = None, + app_version: int | None = None, ) -> Run: """建运行记录、交给执行任务。options 是这次运行的选项快照,schedule_id 是发起它的定时任务(可空)。 + origin_kind / origin / app_id / app_version 是运行来源(engine/origin.py),由 launch_run 定下,原样记在运行上; + 对应的 run.origin 事件由 launch_run 放进 notes。 notes 是发起时就定下的事件 (类型, node_id, 数据),比如正式运行按什么级别出具、口径升版怎么处置。它们和 运行记录同一个事务提交,排在 run.started 前面:写不进去就不建运行,不会出现运行已经在执行、调用方却 @@ -242,6 +248,10 @@ class RunManager: data_versions=data_versions, options=options, schedule_id=schedule_id, + origin_kind=origin_kind, + origin=origin, + app_id=app_id, + app_version=app_version, ) run.thread_id = run.id # 一个 run 一条 checkpoint 线程 session.add(run)