From 91afe76c6ddcf8aea419f07b7bc059b932e6ae96 Mon Sep 17 00:00:00 2001 From: Huarch Date: Thu, 24 Sep 2026 10:13:52 +0800 Subject: [PATCH] docs(workbench): gate database rollout by project --- docs/operational-task-workers.md | 21 +++++++--- docs/operational-tasks.md | 12 +++++- docs/workbench-database-rollout.md | 62 ++++++++++++++++++++++++++++++ docs/workbench-runtime.md | 2 +- 4 files changed, 88 insertions(+), 9 deletions(-) create mode 100644 docs/workbench-database-rollout.md diff --git a/docs/operational-task-workers.md b/docs/operational-task-workers.md index 20e73a1..08dd42a 100644 --- a/docs/operational-task-workers.md +++ b/docs/operational-task-workers.md @@ -21,11 +21,11 @@ conda run -n server python scripts/run_operational_worker.py | `OPERATIONAL_WORKER_SERVER_URL` | 主后端地址,默认本机 8000 | | `OPERATIONAL_WORKER_AGENT_URL` | Agent 地址,默认本机 8787 | | `OPERATIONAL_WORKER_RECONCILE_SECONDS` | SSE 中断或漏通知时的对账间隔,默认 30 秒 | -| `OPERATIONAL_WORKER_MAX_CONCURRENCY` | 同时投递的 run 上限,默认 4,可设置 1–32 | +| `OPERATIONAL_WORKER_MAX_CONCURRENCY` | 同时投递的 run 上限,默认 4,可设置 1 至 32 | | `OPERATIONAL_WORKER_BEARER` | 已具有该项目权限的产品身份,仅用于受控开发或短期运行 | | `OPERATIONAL_WORKER_TOKEN_URL` | 可选 OAuth 客户端凭据取令牌地址 | | `OPERATIONAL_WORKER_CLIENT_ID`、`OPERATIONAL_WORKER_CLIENT_SECRET` | 长期运行身份;不配置固定 Bearer 时使用,并按有效期刷新令牌 | -| `OPERATIONAL_WORKER_TOKEN_REFRESH_SECONDS` | OAuth 缓存上限,默认 300 秒,可设置 30–600 秒;同时受令牌 expires_in 限制,应短于后端允许的最大令牌年龄 | +| `OPERATIONAL_WORKER_TOKEN_REFRESH_SECONDS` | OAuth 缓存上限,默认 300 秒,可设置 30 至 600 秒;同时受令牌 expires_in 限制,应短于后端允许的最大令牌年龄 | 工作身份必须能通过现有产品身份与项目成员权限校验。配置令牌获取不等于自动创建产品用户、项目成员或授权。权限错误会保留任务用于后续重试,不会绕过认证。 @@ -52,15 +52,24 @@ Agent 正常结束必须调用 run `complete`。同一执行代次缺少最终 ## 事件日志与清理 -`resources/sql/015_workbench_events_and_task_runs.sql` 在每个项目 BizDB 中增加 `domain_event_log` 和 `operational_task_runs`。事件与业务投影在同一事务提交,`LISTEN/NOTIFY` 只负责唤醒;浏览器与 worker 始终按持久游标补读。每个 API 进程对活跃项目最多保留一条 LISTEN 连接,无订阅者 60 秒后关闭。 +`resources/sql/015_workbench_events_and_task_runs.sql` 在项目 BizDB 中增加 `domain_event_log` 和 `operational_task_runs`。后续迁移默认只应用到 `tjwater_v2`,扩展规则见 [`workbench-database-rollout.md`](workbench-database-rollout.md)。事件与业务投影在同一事务提交,`LISTEN/NOTIFY` 只负责唤醒;浏览器与 worker 始终按持久游标补读。每个 API 进程对活跃项目最多保留一条 LISTEN 连接,无订阅者 60 秒后关闭。 -事件默认保留 7 天。定时任务先执行 dry-run,再执行清理;脚本从 Metadata DB 解析所有活跃项目路由,并在发现多个项目共享同一 BizDB 时拒绝运行: +事件默认保留 7 天。清理脚本会从 Metadata DB 解析项目路由,并在发现多个项目共享同一 BizDB 时拒绝运行。先确认 `OPERATIONAL_WORKER_PROJECT_ID` 对应 `tjwater_v2`,然后运行定向 dry-run: ```bash -conda run -n server python scripts/cleanup_workbench_events.py --dry-run -conda run -n server python scripts/cleanup_workbench_events.py +conda run -n server python scripts/cleanup_workbench_events.py \ + --project-id "$OPERATIONAL_WORKER_PROJECT_ID" --dry-run ``` +核对输出后,写入模式仍必须显式保留项目参数: + +```bash +conda run -n server python scripts/cleanup_workbench_events.py \ + --project-id "$OPERATIONAL_WORKER_PROJECT_ID" +``` + +禁止省略 `--project-id` 执行清理。数据库维护脚本同样遵循先在 `tjwater_v2` 验证、再逐库扩展的规则。 + 浏览器使用 `GET /api/v1/workbench/sync-snapshot` 建立原子快照,并以该游标连接 `GET /api/v1/workbench/events`。游标早于保留窗口、超前或对应事件已全部清理时返回 `reset_required`,客户端必须重建快照。SSE 需要 Bearer 与 `X-Project-Id`,且 `CORS_ALLOWED_ORIGINS` 必须配置为明确来源。 ## 外围样板工单同步 diff --git a/docs/operational-tasks.md b/docs/operational-tasks.md index 749cec5..a36e474 100644 --- a/docs/operational-tasks.md +++ b/docs/operational-tasks.md @@ -44,11 +44,19 @@ ## 数据库部署 -对每个项目 BizDB 应用: +数据库变更遵循 [`workbench-database-rollout.md`](workbench-database-rollout.md)。后续默认只在 `tjwater_v2` 应用,验证通过并由维护者明确确认后,再逐库扩展。不要从 Metadata DB 枚举所有活跃项目并批量写入。 + +应用前确认当前 DSN 指向 `tjwater_v2`: + +```bash +test "$(psql "$PROJECT_BIZ_DSN" -XAtc 'select current_database()')" = "tjwater_v2" +``` + +完成备份和回滚演练后,再应用: ```bash psql "$PROJECT_BIZ_DSN" -v ON_ERROR_STOP=1 -f resources/sql/014_operational_tasks.sql psql "$PROJECT_BIZ_DSN" -v ON_ERROR_STOP=1 -f resources/sql/015_workbench_events_and_task_runs.sql ``` -迁移不会创建或修改外围系统的业务表,也不授予 `PUBLIC` 权限。生产运行前需按部署身份显式授予新表权限。 +迁移不会创建或修改外围系统的业务表,也不授予 `PUBLIC` 权限。生产运行前需按部署身份显式授予新表权限。SQL 文件包含自己的事务边界,不能把外层 `ROLLBACK` 当作演练;具体要求见数据库变更门禁。 diff --git a/docs/workbench-database-rollout.md b/docs/workbench-database-rollout.md new file mode 100644 index 0000000..387b2b4 --- /dev/null +++ b/docs/workbench-database-rollout.md @@ -0,0 +1,62 @@ +# 工作台数据库变更门禁 + +本规则适用于工作台、工况任务、Agent Run、事件日志、通知、预约工单和告警在项目 BizDB 中的 DDL、数据回填、种子写入与清理操作。MetaDB、TimescaleDB 和外围系统数据库不在本文范围内。 + +## 当前基线 + +截至 2026-09-24,`014_operational_tasks.sql` 和 `015_workbench_events_and_task_runs.sql` 已应用到 `tjwater_v2`、`szh_v2`、`md_v2`、`fengyang_v2` 和 `zjb`。这是已有基线,不代表后续迁移可以继续批量应用。 + +后续数据库变更默认只写入 `tjwater_v2`。其他项目保持现状,必须等 `tjwater_v2` 验证通过并由维护者明确确认后,再逐库扩展。禁止把“所有活跃项目”作为写操作的默认范围。 + +## 变更顺序 + +1. 核对目标 DSN,确认物理数据库名为 `tjwater_v2`,并确认没有其他项目共享该 BizDB。 +2. 对目标库做完整、可恢复的备份。备份需通过 `pg_restore --list` 或等价方式检查,路径和校验值记录在部署记录中,不提交到仓库。 +3. 在强制回滚事务或由备份恢复出的临时库中执行完整迁移。只做静态 SQL 检查不能替代这一步。 +4. 只在 `tjwater_v2` 提交迁移,提交后立即核对表、列、约束、索引、回填数量和项目 ID。 +5. 完成接口、工作进程和浏览器验收。未通过时停止扩展,保留备份和现场信息。 +6. 维护者确认验收结果后,按一个项目一次的方式扩展。每个项目重新执行备份、回滚演练和验收,不复用 `tjwater_v2` 的通过结论。 + +SQL 文件包含自己的 `BEGIN` 和 `COMMIT` 时,不能在外层简单包一层 `ROLLBACK` 充当演练。演练工具需要移除脚本自己的事务边界,或在由备份恢复出的临时数据库中运行。 + +## 验收条件 + +`tjwater_v2` 同时满足以下条件后,才可以讨论扩展: + +- 迁移重复执行不会产生重复记录或改变已经确认的终态数据。 +- 表、约束和索引与迁移定义一致,没有其他项目 ID 的记录。 +- 带 Bearer 和 `X-Project-Id` 的 `sync-snapshot` 返回 200,游标、任务和通知可以解析。 +- SSE 可以连接、发送 keepalive,并能从持久游标补读事件。 +- 至少一条合成调查完成 queued、running、waiting_human 的主路径,计划、报告和通知属于同一执行代次。 +- Agent 或 Server 重启后,旧 Run 和旧会话不能覆盖当前代次。 +- Server、Agent 和前端的相关自动化测试通过,联调期间没有新增 5xx、跨项目数据或重复事件。 + +没有定义固定观察时长。验收记录至少包含执行时间、代码提交、迁移编号、备份标识、自动化测试结果和真实接口结果,由维护者决定是否扩大范围。 + +## 维护脚本也受范围限制 + +会枚举 Metadata DB 中所有活跃路由并写入数据库的脚本,属于跨项目变更。此类脚本必须提供显式项目参数,并默认拒绝全项目写入。 + +`cleanup_workbench_events.py` 会枚举所有活跃项目,但已经支持 `--project-id`。dry-run 可以查看全部项目;写入模式必须显式传入经核对属于 `tjwater_v2` 的项目 UUID,禁止省略 `--project-id`。种子、导入、回填和修复脚本同样不能把所有活跃项目作为隐式目标。 + +## 当前设计需要收敛的部分 + +现有设计保留以下主干: + +- `operational_tasks` 保存业务任务,`operational_task_runs` 保存每一代 Agent 执行。两者生命周期不同,分表有助于租约恢复和阻止旧代次写回。 +- `domain_event_log` 保存可重放事件,`LISTEN/NOTIFY` 只负责唤醒。当前规模不需要增加 Redis、Kafka 或新的中继服务。 +- 通知表表达用户收件箱,事件日志表达状态变化。两者不能合并。 +- 证据、计划、报告和动作修订需要保留历史版本,独立表符合审计和并发更新要求。 + +以下部分不再扩展: + +- `operational_tasks` 仍保存执行代次、执行请求号、Agent session 和租约字段,同时 Run 表也保存同类数据。新代码以 Run 表为准,不再增加任务表上的执行字段。待 `tjwater_v2` 稳定后,再设计前向迁移移除兼容字段。 +- `status`、`lifecycle_status` 和任务 JSON 中的状态存在重复表达。新接口以 `lifecycle_status` 为业务生命周期,以 Run 状态为执行状态,不再增加第四套状态字段。 +- 定时任务表和 schedule API 不在当前产品主路径。相关结构保留兼容,不继续增加调度能力,除非 `tjwater_v2` 出现明确业务需求。 +- JSON 文档与标量列只保留查询、约束或兼容所需的重复字段。新增字段应先确定唯一权威来源,不能同时在多处独立计算。 + +新增表需要同时满足三个条件:对象有独立生命周期,访问或保留策略与现有表不同,并且 `tjwater_v2` 的真实用例证明复用现有结构会造成明显问题。否则优先收敛现有模型。 + +## 失败与回滚 + +任何数据库出现迁移失败、接口 5xx、跨项目记录、Run 代次错乱或游标异常时,立即停止后续项目。未提交的事务直接回滚;已经提交且不能安全前向修复时,按该项目的迁移前备份恢复。不要为保持各项目版本一致而继续扩大故障范围。 diff --git a/docs/workbench-runtime.md b/docs/workbench-runtime.md index 96e4751..0f399bc 100644 --- a/docs/workbench-runtime.md +++ b/docs/workbench-runtime.md @@ -1,6 +1,6 @@ # 工作台运行态 -`GET /api/v1/workbench/runtime` 按当前项目读取 BizDB 中的分析成果、预约工单和任务告警。工况任务继续由前端演示数据提供,不从后端运行记录或分析结果生成。 +`GET /api/v1/workbench/runtime` 按当前项目读取 BizDB 中的分析成果、预约工单和任务告警。调查任务由独立的工况任务接口读取 `workbench.operational_tasks` 和 `workbench.operational_task_runs`,`sync-snapshot` 再把两部分合并;定时任务仍保留前端 mock,不进入后端工作队列。 - `analysis.runs/results` 只用于分析成果、历史成果和兼容场景字段。 - `workbench.work_orders` 是预约工单的后端持久化投影。主工单包含多个子任务,每个子任务包含自己的有序阶段。表存在时,空列表同样是权威结果,不回退到 mock 工单。