Files
next-tjwater-server/docs/operational-task-workers.md
T

120 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 工况工作进程与外部动作适配
工况执行不依赖浏览器保活。API 保存任务及审批事实,工作进程扫描持久化队列,再向 Agent 提交后端分配的执行请求编号。相同请求编号重试复用同一执行,不能根据轮询时间生成新的编号。
## 调查任务队列
当前产品分为定时任务、调查任务、预约任务。定时任务保留前端 mock 数据,不调用后端计划创建或到期调度;调查任务读取数据库并进入本工作队列;预约任务读取数据库中的外围工单投影,以外部同步进度为准。已有后端 schedule API 暂不在当前产品主路径使用。
从后端仓库运行:
```bash
conda run -n server python scripts/run_operational_worker.py --once
conda run -n server python scripts/run_operational_worker.py
```
部署环境提供以下变量,不将实际值写入仓库。工作进程也读取后端仓库已忽略的 `.env`,已存在的进程环境变量优先:
| 变量 | 用途 |
| --- | --- |
| `OPERATIONAL_WORKER_PROJECT_ID` | 唯一授权项目 UUID |
| `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_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 限制,应短于后端允许的最大令牌年龄 |
工作身份必须能通过现有产品身份与项目成员权限校验。配置令牌获取不等于自动创建产品用户、项目成员或授权。权限错误会保留任务用于后续重试,不会绕过认证。
长期 OAuth 身份由身份管理员创建支持 `client_credentials` 的专用机密客户端;服务账号令牌应具有后端配置要求的 audience、UUID `sub` 和 `preferred_username`。再由产品管理员把该 `sub` 绑定到启用的产品用户及目标项目成员关系。只赋予本次调查所需项目权限,不授予系统管理员或设备命令权限。当前项目成员策略同时包含工单审批/执行权限,不能将其误称为已经细分的专用最小权限服务角色。
本机联调配置示意(密钥由管理员通过安全配置填入,禁止粘贴到聊天):
```dotenv
OPERATIONAL_WORKER_PROJECT_ID=324ff7ee-c2d4-5184-a84b-48fb14a2933e
OPERATIONAL_WORKER_SERVER_URL=http://127.0.0.1:8000
OPERATIONAL_WORKER_AGENT_URL=http://127.0.0.1:18787
OPERATIONAL_WORKER_TOKEN_URL=https://<identity-host>/realms/<realm>/protocol/openid-connect/token
OPERATIONAL_WORKER_CLIENT_ID=<dedicated-service-client>
OPERATIONAL_WORKER_CLIENT_SECRET=<inject-securely>
OPERATIONAL_ACTION_PERIPHERAL_URL=http://127.0.0.1:8011
OPERATIONAL_DATA_PERIPHERAL_URL=http://127.0.0.1:8011
```
选择 OAuth 时不要同时配置 `OPERATIONAL_WORKER_BEARER`,否则固定 Bearer 优先。不要配置设备命令网关进行本次合成联调。
工作进程先恢复过期执行租约,再使用 keyset 游标扫描持久化 run 队列;`operational.run.queued` 事件会立即唤醒扫描,连接中断时每 30 秒对账。Agent 正在执行时每 20 秒续租,租约为 60 秒;进程中断后由后端分配新的执行代次,旧 run 和旧会话不能继续覆盖新一轮结果。网络错误及 5xx 从 5 秒指数退避到 5 分钟,确定性 4xx 终止该 run;连续 10 次投递失败产生运维通知。
Agent 正常结束必须调用 run `complete`。同一执行代次缺少最终计划或报告时,Server 将 run 以 `ARTIFACT_INCOMPLETE` 明确失败,不依赖租约超时猜测完成状态。心跳只增加 run revision,不改变任务业务 revision。
## 事件日志与清理
`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 天。清理脚本会从 Metadata DB 解析项目路由,并在发现多个项目共享同一 BizDB 时拒绝运行。先确认 `OPERATIONAL_WORKER_PROJECT_ID` 对应 `tjwater_v2`,然后运行定向 dry-run:
```bash
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` 必须配置为明确来源。
## 外围样板工单同步
```bash
conda run -n server python scripts/sync_peripheral_work_orders.py --once
conda run -n server python scripts/sync_peripheral_work_orders.py
```
使用相同工作身份,并配置 `OPERATIONAL_ACTION_PERIPHERAL_URL`。同步器读取分页工单,将外围版本投影到 `external_work_order` 任务。合成来源在摘要和证据中明确标注;工单完成只表示该外部任务完成,不代表其他调查自动完成。
通用合成检查工单不属于预约任务。早期种子脚本创建的通信、压力和零流量检查工单已经退役,种子脚本不再恢复它们;同步器按不可编辑的专用 seed actor 跳过旧种子工单。投诉、爆管事件以及调查动作实际创建的现场工单继续同步。`018_remove_synthetic_check_task_projections.sql` 只清理专用 seed actor 创建的旧检查投影,不按用户可编辑的标题或状态删除任务,也不影响 `workbench.work_orders` 中保留的三条演示工单。
水质投诉和爆管上报类型的外围工单同时创建关联调查,即使当时没有浏览器打开也会入队。两类事件从外围样板测试面板模拟上报;工况页负责展示调查。调查中创建的外围工单通过回执编号关联,后续反馈送回原调查,由 Agent 重新分析。
## 旧调查迁移
`scripts/import_agent_investigation.py <session_id> --dry-run` 只检查旧合成水质调查能否转换。去掉 `--dry-run` 后使用上述产品工作身份导入后端任务、流程、证据及阶段报告;不会修改原 Agent 文件,也不会覆盖已存在的任务计划。应在启用队列工作进程前导入,避免旧调查被当作新的待执行任务领取。
## 调查数据读取
`OPERATIONAL_DATA_SERVER_URL` 默认本机 8000;外围样板数据读取需配置 `OPERATIONAL_DATA_PERIPHERAL_URL`。查询只允许预定义的数据源、最多 20 个设备和 24 小时时间范围,响应有大小限制。每次查询保存内容摘要对应的数据快照;缺失、拒绝、超限或未配置都返回明确错误,不生成正常数据结论。截断结果必须保留不完整标记。
## 人工确认后的动作
- `OPERATIONAL_ACTION_PERIPHERAL_URL`:使用既有 `/api/sample/v1/work-orders` 创建合成外围工单。成功回执说明工单已创建,不表示现场工作已经完成。
- `OPERATIONAL_ACTION_COMMAND_GATEWAY_URL`:可选设备命令网关。未配置时明确返回不支持执行,不能生成成功回执。
- `OPERATIONAL_ACTION_TIMEOUT_SECONDS`:外部请求超时,默认 10 秒。
命令网关契约:`POST /api/v1/commands` 接收 `request_id`、`project_id`、`task_id`、`action_id`、`action_version`、`approved_by`、`approved_at` 与类型化 `command`。网关必须按 `request_id` 幂等处理,并在执行前验证目标设备当前状态、前置条件和命令有效期。
回执包含 `request_id`、`project_id`、`receipt_id`、`status`(`accepted` / `running` / `completed` / `failed`)、`submitted_at`、可空 `completed_at` 和 `detail`。`completed` 必须有完成时间。网关回执查询路径为 `GET /api/v1/commands/requests/{request_id}`。
请求和回执时间必须包含时区。网关地址只来自服务端配置,Agent 或用户动作内容不能指定任意目的地址。产品 Bearer 仅在当前请求内传递,不进入任务、会话、提示词和日志。没有回执的网络故障保留为 `unknown`,不能当作失败后直接重新下发。
## 验证边界
适配器和工作进程测试使用隔离 HTTP transport 验证权限前提、幂等身份、丢响应恢复、项目隔离、接受与完成的区别。它们不等于生产设备网关或现场执行验收;接入具体网关后还需以其真实回执验证执行链。
可从后端仓库复现聚焦验证:
```bash
conda run -n server python -m pytest tests/unit/test_operational_action_adapters.py tests/unit/test_operational_worker.py tests/unit/test_peripheral_task_sync.py tests/unit/test_operational_data.py tests/unit/test_import_agent_investigation.py -q
OPERATIONAL_TASKS_STORAGE_TEST=1 conda run -n server python -m pytest tests/integration/test_operational_tasks_storage.py -q
```
第二条需要当前配置的 PostgreSQL 可连接及测试 DDL 权限。测试在强制回滚事务中创建表结构和隔离项目数据,结束后不保留这些修改;不代替正式迁移。外部工单调用仍使用隔离 HTTP transport,不会操作实际工单系统。该测试覆盖持久化报告、反馈重新排队、旧会话拒绝、人工确认与丢响应回执核对,而非真实模型推理质量。