Skip to content

feat(workflow-run): 实现执行记录 CRUD 服务与 API - #176

Merged
nighca merged 1 commit into
1024XEngineer:mainfrom
xiaocheny214:feat/workflow-run
Aug 10, 2026
Merged

feat(workflow-run): 实现执行记录 CRUD 服务与 API#176
nighca merged 1 commit into
1024XEngineer:mainfrom
xiaocheny214:feat/workflow-run

Conversation

@xiaocheny214

Copy link
Copy Markdown
Contributor

概述

实现工作流执行记录(WorkflowRun)的完整 CRUD,为工作流节点编排和前端画布提供持久化层。

包含内容

模型层(server/workflow_run/model.py)

  • dataclass → SQLAlchemy ORM(windup_workflow_run 表)
  • nodes 字段:JSONB(Postgres)/ JSON(SQLite),前端自定义结构,后端不校验
  • status 字段:active / soft_deleted
  • version 字段:每次更新自增,支持乐观并发

服务层(server/workflow_run/service.py)

  • SqlAlchemyWorkflowRunService:实现 WorkflowRunService 接口
  • create_run / get_run / list_runs(分页) / update_run / delete_run(软删除)

API 层(web/api/workflow_run.py)

  • POST /workflow-runs — 创建
  • GET /workflow-runs?project_id=... — 分页列表(排除软删除)
  • GET /workflow-runs/{id} — 详情
  • PATCH /workflow-runs/{id} — 全量更新 nodes / status
  • DELETE /workflow-runs/{id} — 软删除
  • 全部端点加 JWT 归属校验(通过 project_id 反查 user_id)

路由注册(bootstrap/app.py)

  • 注册 workflow_run_router
  • 导入 WorkflowRun model 触发 Base.metadata 注册,启动时自动建表

测试(17 用例全通过)

  • 创建(空 nodes / 带 nodes)
  • 列表(空 / 分页 / 软删除过滤)
  • 详情 / 404
  • 更新(nodes / status / 无效 status 400)
  • 删除 / 404
  • 跨用户权限校验(5 用例)

关联

@vercel

vercel Bot commented Aug 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
windup Ignored Ignored Preview Aug 10, 2026 6:08am

@fennoai fennoai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No actionable issues found in the fixed PR diff. compileall and git diff --check pass; the targeted pytest suite could not be run because pytest is not installed in the review environment.

huyanxius
huyanxius previously approved these changes Aug 10, 2026
@huyanxius

Copy link
Copy Markdown
Collaborator

等等,发现个问题

Comment thread backend/packages/app/src/windup_app/server/workflow_run/service.py

@fennoai fennoai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

提交 review 时 PR head 从锁定的 9ad72d5 回退到 ea24946,因此新增的 .github/workflows/deploy.yml 已不在当前 PR diff 中,相关部署工作流发现不再适用于当前版本。对当前可见的 CRUD diff,除已有未解决的乐观并发版本校验评论外,没有新增的高置信度问题。git diff --check 通过;当前环境未安装 pytest,未能运行目标测试。

@huyanxius

Copy link
Copy Markdown
Collaborator

没问题,可以合并

@nighca
nighca merged commit 1813e8c into 1024XEngineer:main Aug 10, 2026
10 checks passed
johnnyzhang-eng added a commit to johnnyzhang-eng/game-asset-character that referenced this pull request Aug 10, 2026
1024XEngineer#151 / 1024XEngineer#152 的内容迁到当前 main(efd230e)之上重提。主线上 orchestrator 只有
interface + model,缺 service / executor / task_repo;generation API 三个端点是
「接口待实现」。

## 迁移时按主线现状做的调整

**接口签名统一到 session-per-call。** 主线 orchestrator/interface.py 的三个方法没有
session 参数,而 1024XEngineer#176 刚合入的 workflow_run/service.py 用的是「session 首参 + 关键字
入参 + 只 flush 不 commit」。两处不一致会让同一个仓里出现两种事务写法,故 orchestrator
跟上已确立的那套;事务边界仍归 windup_framework.db.get_session。

**保留主线的 SSE 文档与终态关流。** interface.py 的 SSE 契约说明、generation.py 的
_TERMINAL_EVENTS 与终态后 break,都是主线后来加的,比源分支的「前端轮询」更准确,
原样保留。终态关流治的是:服务端发完 task_update 就关流但带 retry: 3000,浏览器原生
EventSource 每 3 秒重连、45 秒内 15 次,每次重收同一条 completed。

**user_id 从 JWT 取,不信客户端。** 源分支的请求体里有 user_id: int = Field(gt=0),
客户端可以填别人的 id。改为 request.state.current_user.id,并加项目归属校验
(项目不存在或不属于当前用户一律 404,不区分两者以免泄露他人项目是否存在)。

## 后台执行的两处竞态与分层

**after_commit 再起线程。** create_task 只 flush,session 要等 handler 返回后才 commit。
直接起线程的话,后台 session 可能读不到未提交的任务行,update 静默跳过——任务永远停在
PENDING 且无任何报错。改为注册 after_commit 回调,提交成功后才派发。

**executor 挂 app.state,不 import 进 web 层。** import-linter 的分层契约禁止
app.web 直连 ai_engine,而 executor 要调 ai_engine.impl。放进 state 由 bootstrap
(唯一装配点)注入,契约与实现两边都成立。

## 依赖链

orchestrator/executor.py import 了 windup_ai_engine.{impl,ports,strategy.concrete} 与
windup_common.models,四层里主线只有 windup_framework.providers。故本分支 stack 在
ai_engine 那条线之上,那条合入后 rebase。

Refs 1024XEngineer#171
johnnyzhang-eng added a commit to johnnyzhang-eng/game-asset-character that referenced this pull request Aug 10, 2026
1024XEngineer#151 / 1024XEngineer#152 的内容迁到当前 main(efd230e)之上重提。主线上 orchestrator 只有
interface + model,缺 service / executor / task_repo;generation API 三个端点是
「接口待实现」。

## 迁移时按主线现状做的调整

**接口签名统一到 session-per-call。** 主线 orchestrator/interface.py 的三个方法没有
session 参数,而 1024XEngineer#176 刚合入的 workflow_run/service.py 用的是「session 首参 + 关键字
入参 + 只 flush 不 commit」。两处不一致会让同一个仓里出现两种事务写法,故 orchestrator
跟上已确立的那套;事务边界仍归 windup_framework.db.get_session。

**保留主线的 SSE 文档与终态关流。** interface.py 的 SSE 契约说明、generation.py 的
_TERMINAL_EVENTS 与终态后 break,都是主线后来加的,比源分支的「前端轮询」更准确,
原样保留。终态关流治的是:服务端发完 task_update 就关流但带 retry: 3000,浏览器原生
EventSource 每 3 秒重连、45 秒内 15 次,每次重收同一条 completed。

**user_id 从 JWT 取,不信客户端。** 源分支的请求体里有 user_id: int = Field(gt=0),
客户端可以填别人的 id。改为 request.state.current_user.id,并加项目归属校验
(项目不存在或不属于当前用户一律 404,不区分两者以免泄露他人项目是否存在)。

## 后台执行的两处竞态与分层

**after_commit 再起线程。** create_task 只 flush,session 要等 handler 返回后才 commit。
直接起线程的话,后台 session 可能读不到未提交的任务行,update 静默跳过——任务永远停在
PENDING 且无任何报错。改为注册 after_commit 回调,提交成功后才派发。

**executor 挂 app.state,不 import 进 web 层。** import-linter 的分层契约禁止
app.web 直连 ai_engine,而 executor 要调 ai_engine.impl。放进 state 由 bootstrap
(唯一装配点)注入,契约与实现两边都成立。

## 依赖链

orchestrator/executor.py import 了 windup_ai_engine.{impl,ports,strategy.concrete} 与
windup_common.models,四层里主线只有 windup_framework.providers。故本分支 stack 在
ai_engine 那条线之上,那条合入后 rebase。

Refs 1024XEngineer#171
johnnyzhang-eng added a commit to johnnyzhang-eng/game-asset-character that referenced this pull request Aug 10, 2026
1024XEngineer#151 / 1024XEngineer#152 的内容迁到当前 main(efd230e)之上重提。主线上 orchestrator 只有
interface + model,缺 service / executor / task_repo;generation API 三个端点是
「接口待实现」。

## 迁移时按主线现状做的调整

**接口签名统一到 session-per-call。** 主线 orchestrator/interface.py 的三个方法没有
session 参数,而 1024XEngineer#176 刚合入的 workflow_run/service.py 用的是「session 首参 + 关键字
入参 + 只 flush 不 commit」。两处不一致会让同一个仓里出现两种事务写法,故 orchestrator
跟上已确立的那套;事务边界仍归 windup_framework.db.get_session。

**保留主线的 SSE 文档与终态关流。** interface.py 的 SSE 契约说明、generation.py 的
_TERMINAL_EVENTS 与终态后 break,都是主线后来加的,比源分支的「前端轮询」更准确,
原样保留。终态关流治的是:服务端发完 task_update 就关流但带 retry: 3000,浏览器原生
EventSource 每 3 秒重连、45 秒内 15 次,每次重收同一条 completed。

**user_id 从 JWT 取,不信客户端。** 源分支的请求体里有 user_id: int = Field(gt=0),
客户端可以填别人的 id。改为 request.state.current_user.id,并加项目归属校验
(项目不存在或不属于当前用户一律 404,不区分两者以免泄露他人项目是否存在)。

## 后台执行的两处竞态与分层

**after_commit 再起线程。** create_task 只 flush,session 要等 handler 返回后才 commit。
直接起线程的话,后台 session 可能读不到未提交的任务行,update 静默跳过——任务永远停在
PENDING 且无任何报错。改为注册 after_commit 回调,提交成功后才派发。

**executor 挂 app.state,不 import 进 web 层。** import-linter 的分层契约禁止
app.web 直连 ai_engine,而 executor 要调 ai_engine.impl。放进 state 由 bootstrap
(唯一装配点)注入,契约与实现两边都成立。

## 依赖链

orchestrator/executor.py import 了 windup_ai_engine.{impl,ports,strategy.concrete} 与
windup_common.models,四层里主线只有 windup_framework.providers。故本分支 stack 在
ai_engine 那条线之上,那条合入后 rebase。

Refs 1024XEngineer#171
johnnyzhang-eng added a commit to johnnyzhang-eng/game-asset-character that referenced this pull request Aug 11, 2026
1024XEngineer#151 / 1024XEngineer#152 的内容迁到当前 main(efd230e)之上重提。主线上 orchestrator 只有
interface + model,缺 service / executor / task_repo;generation API 三个端点是
「接口待实现」。

## 迁移时按主线现状做的调整

**接口签名统一到 session-per-call。** 主线 orchestrator/interface.py 的三个方法没有
session 参数,而 1024XEngineer#176 刚合入的 workflow_run/service.py 用的是「session 首参 + 关键字
入参 + 只 flush 不 commit」。两处不一致会让同一个仓里出现两种事务写法,故 orchestrator
跟上已确立的那套;事务边界仍归 windup_framework.db.get_session。

**保留主线的 SSE 文档与终态关流。** interface.py 的 SSE 契约说明、generation.py 的
_TERMINAL_EVENTS 与终态后 break,都是主线后来加的,比源分支的「前端轮询」更准确,
原样保留。终态关流治的是:服务端发完 task_update 就关流但带 retry: 3000,浏览器原生
EventSource 每 3 秒重连、45 秒内 15 次,每次重收同一条 completed。

**user_id 从 JWT 取,不信客户端。** 源分支的请求体里有 user_id: int = Field(gt=0),
客户端可以填别人的 id。改为 request.state.current_user.id,并加项目归属校验
(项目不存在或不属于当前用户一律 404,不区分两者以免泄露他人项目是否存在)。

## 后台执行的两处竞态与分层

**after_commit 再起线程。** create_task 只 flush,session 要等 handler 返回后才 commit。
直接起线程的话,后台 session 可能读不到未提交的任务行,update 静默跳过——任务永远停在
PENDING 且无任何报错。改为注册 after_commit 回调,提交成功后才派发。

**executor 挂 app.state,不 import 进 web 层。** import-linter 的分层契约禁止
app.web 直连 ai_engine,而 executor 要调 ai_engine.impl。放进 state 由 bootstrap
(唯一装配点)注入,契约与实现两边都成立。

## 依赖链

orchestrator/executor.py import 了 windup_ai_engine.{impl,ports,strategy.concrete} 与
windup_common.models,四层里主线只有 windup_framework.providers。故本分支 stack 在
ai_engine 那条线之上,那条合入后 rebase。

Refs 1024XEngineer#171
johnnyzhang-eng added a commit to johnnyzhang-eng/game-asset-character that referenced this pull request Aug 11, 2026
1024XEngineer#151 / 1024XEngineer#152 的内容迁到当前 main(efd230e)之上重提。主线上 orchestrator 只有
interface + model,缺 service / executor / task_repo;generation API 三个端点是
「接口待实现」。

## 迁移时按主线现状做的调整

**接口签名统一到 session-per-call。** 主线 orchestrator/interface.py 的三个方法没有
session 参数,而 1024XEngineer#176 刚合入的 workflow_run/service.py 用的是「session 首参 + 关键字
入参 + 只 flush 不 commit」。两处不一致会让同一个仓里出现两种事务写法,故 orchestrator
跟上已确立的那套;事务边界仍归 windup_framework.db.get_session。

**保留主线的 SSE 文档与终态关流。** interface.py 的 SSE 契约说明、generation.py 的
_TERMINAL_EVENTS 与终态后 break,都是主线后来加的,比源分支的「前端轮询」更准确,
原样保留。终态关流治的是:服务端发完 task_update 就关流但带 retry: 3000,浏览器原生
EventSource 每 3 秒重连、45 秒内 15 次,每次重收同一条 completed。

**user_id 从 JWT 取,不信客户端。** 源分支的请求体里有 user_id: int = Field(gt=0),
客户端可以填别人的 id。改为 request.state.current_user.id,并加项目归属校验
(项目不存在或不属于当前用户一律 404,不区分两者以免泄露他人项目是否存在)。

## 后台执行的两处竞态与分层

**after_commit 再起线程。** create_task 只 flush,session 要等 handler 返回后才 commit。
直接起线程的话,后台 session 可能读不到未提交的任务行,update 静默跳过——任务永远停在
PENDING 且无任何报错。改为注册 after_commit 回调,提交成功后才派发。

**executor 挂 app.state,不 import 进 web 层。** import-linter 的分层契约禁止
app.web 直连 ai_engine,而 executor 要调 ai_engine.impl。放进 state 由 bootstrap
(唯一装配点)注入,契约与实现两边都成立。

## 依赖链

orchestrator/executor.py import 了 windup_ai_engine.{impl,ports,strategy.concrete} 与
windup_common.models,四层里主线只有 windup_framework.providers。故本分支 stack 在
ai_engine 那条线之上,那条合入后 rebase。

Refs 1024XEngineer#171
johnnyzhang-eng added a commit to johnnyzhang-eng/game-asset-character that referenced this pull request Aug 11, 2026
1024XEngineer#151 / 1024XEngineer#152 的内容迁到当前 main(efd230e)之上重提。主线上 orchestrator 只有
interface + model,缺 service / executor / task_repo;generation API 三个端点是
「接口待实现」。

## 迁移时按主线现状做的调整

**接口签名统一到 session-per-call。** 主线 orchestrator/interface.py 的三个方法没有
session 参数,而 1024XEngineer#176 刚合入的 workflow_run/service.py 用的是「session 首参 + 关键字
入参 + 只 flush 不 commit」。两处不一致会让同一个仓里出现两种事务写法,故 orchestrator
跟上已确立的那套;事务边界仍归 windup_framework.db.get_session。

**保留主线的 SSE 文档与终态关流。** interface.py 的 SSE 契约说明、generation.py 的
_TERMINAL_EVENTS 与终态后 break,都是主线后来加的,比源分支的「前端轮询」更准确,
原样保留。终态关流治的是:服务端发完 task_update 就关流但带 retry: 3000,浏览器原生
EventSource 每 3 秒重连、45 秒内 15 次,每次重收同一条 completed。

**user_id 从 JWT 取,不信客户端。** 源分支的请求体里有 user_id: int = Field(gt=0),
客户端可以填别人的 id。改为 request.state.current_user.id,并加项目归属校验
(项目不存在或不属于当前用户一律 404,不区分两者以免泄露他人项目是否存在)。

## 后台执行的两处竞态与分层

**after_commit 再起线程。** create_task 只 flush,session 要等 handler 返回后才 commit。
直接起线程的话,后台 session 可能读不到未提交的任务行,update 静默跳过——任务永远停在
PENDING 且无任何报错。改为注册 after_commit 回调,提交成功后才派发。

**executor 挂 app.state,不 import 进 web 层。** import-linter 的分层契约禁止
app.web 直连 ai_engine,而 executor 要调 ai_engine.impl。放进 state 由 bootstrap
(唯一装配点)注入,契约与实现两边都成立。

## 依赖链

orchestrator/executor.py import 了 windup_ai_engine.{impl,ports,strategy.concrete} 与
windup_common.models,四层里主线只有 windup_framework.providers。故本分支 stack 在
ai_engine 那条线之上,那条合入后 rebase。

Refs 1024XEngineer#171
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants