Skip to content

Result Backend 跨项目契约(TFRS-370)

文档同步的 converting/persisting 两阶段结果均写入同一 Redis result backend:本端 (TFRobotServer)聚合任务状态时真实读取对侧(TFRSUtilsCelery)写入的结果 key (protocols/sync_task_manage_celery.py / services/doc_source_service/doc_source_manage_service.py)。 两侧 backend 配置漂移会静默让 1~7 天龄记录退化回「排队中」假象(RESULT_EXPIRED 逃逸未触发)。 本页是 TFRS-335 登记限制的兑现记录(TFRS-370),与对侧任务 TFRSUC-15 配套。

契约维度(4 项,SoT = tfrobotserver/protocols/result_backend_contract.py)

维度 契约值 本端来源 对侧来源
result backend DB index 1 configs/celery_config.py celery.backend(Annotated AnyUrl redis) tfrsutilscelery/configs/uri_config.py uri.backend(TFC_URI__BACKEND)
result_expires 7 天 configs/celery_config.py:result_expires_days=7(TFS_CELERY__RESULT_EXPIRES_DAYS 可覆盖),写入三应用 conf:celery.py(memory worker)/ dependencies/tfscontainer.py:_build_api_celery(API)/ robot_celery.py(robot worker) tfrsutilscelery/configs/celery_config.py:Field(default=7, ge=1)(TFC_CELERY__RESULT_EXPIRES_DAYS 可覆盖),main.py update_conf 写入
result serializer json 未显式设置 → Celery 5.x 默认 json(celery_config.py:result_serializer=None) 无配置 knob → Celery 5.x 默认 json
backend scheme 族 redis / rediss 本端 confz allowed_schemes=["redis"] 固禁 rediss 无 scheme 约束(可带凭证)

核验记录(TFRS-370 对表结果)

环境值随部署渲染变化;本表为检录时点快照。运行时对表用 make check-result-contract(部署管道注入对侧 env 后全维度核验)。

环境 本端 backend 对侧 backend 本端/对侧 expires 结论
local(debug/dev/test/cnb) redis://127.0.0.1:30379/1(resources/tfrobotserver-{profile}.toml [celery].backend) redis://127.0.0.1:30379/1(resources/tfrobotcelery-{test,debug}.toml [uri].backend) 7 / 7 ✅ 一致
tketest redis://redis-service.tfrobotserver:6379/1(resources/tfrobotserver-tketest.toml) 部署 env 注入(tketest toml 无 uri 键) 7 / 7 ✅ DB 1(对侧 DB 1 按对侧 TFRSUC-15 检录陈述;对表脚本注入 TFC_URI__BACKEND 后方可机器证)
生产(operator 租户) operator 渲染 operator 渲染 7 / 7 ✅ 对侧 DB 1 按 TFRSUC-15 检录陈述

两侧均无 result_serializer/task_serializer/accept_content 显式配置 → 默认 json 一致。

校验机制

  1. 启动期断言(fail-fast):三个 Celery 应用构造期统一经 configs/celery_config.py:load_celery_config() 取配置——读取即校验 protocols/result_backend_contract.validate_result_backend_contract。违约抛 ResultBackendContractError,消息逐项含变量名 + 实际/期望值 + 修复指引 (TFS_CELERY__RESULT_EXPIRES_DAYS、TFS_CELERY__BACKEND 等)。
  2. 对表工具:make check-result-contract(scripts/check-result-contract.py)——本端 经 loader,对侧经 TFC_URI__BACKEND / TFC_CELERY__RESULT_EXPIRES_DAYS 注入; 输出逐维度 ✅/❌/⚠️,任何 ❌ exit 1;对侧未提供维度 ⚠️ 不 gate,--strict 可强制。
  3. 继承约束:本端 confz allowed_schemes=["redis"] 固禁 rediss(合法但收窄); 对侧可 rediss——若对侧迁移 TLS,对表脚本的 scheme 族维度会显式 ❌。

明确排除项(非契约维度)

项 为何排除
broker(AMQP) 两侧队列架构按设计不同(本端 TFRS-217 per-robot 队列 vs 对侧共享 celery 队列);broker 不承载跨侧结果读取
backend host:port 各环境/namespace 天然不同(本地 vs redis-service.tfrobotserver);对表仅作脱敏展示,不作失败维度
task_serializer / accept_content 两侧均无配置 knob,强制会导致假置信;作为上面 serializer 证据记录(默认 json)

跨侧行为假设:converting 任务失败必须 raise(TFRS-508)

上面 4 项是结果读取的契约;本项是失败上报的契约,登记在此以便与上面同等对表。

假设:TFRSUtilsCelery 的 converting 任务(url.sync_file_obj / feishu.sync_drive_obj / feishu.sync_wiki_obj)在解析/转换失败时抛出异常,而不是捕获后返回错误对象。

为什么本端依赖它:converting 属对侧,本端拿不到它的完成回调。TFRS-508 起,本端在派发时挂 link_error errback(protocols/sync_task_manage_celery.py::sync_converting_failed), 把「转换失败」这件事经对侧 worker 转发投递回本端 memory 队列,落成登记行的 FAILURE。 errback 只在任务以 FAILURE 终结时触发 ⇒ 若对侧改成「吞错返回」,本端会静默失去该上报。

当前核验(三支同形):

任务 autoretry_for 失败路径
url.sync_file_obj (RuntimeError,) except Exception → raise ValueError(...)
feishu.sync_drive_obj (RuntimeError,) 同上
feishu.sync_wiki_obj (RuntimeError,) 同上

ValueError 与 autoretry_for 不匹配 ⇒ 不重试、立即最终失败 ⇒ errback 及时触发。 (CELERY 语义:重试期间不触发 errback,只有最终失败才触发。)

投递路径不引入新契约:errback 与既有 link 回调(memory_manage.add_dpe)同一条路 —— 均由对侧 worker 在任务结束时按签名里烘好的 queue=per_robot_queue("memory_tasks") 投递回 本端队列。故「link 能用 ⇒ link_error 能用」,无需对侧注册本端任务名。

违约后果与兜底:若该假设被打破,本端的 converting 失败可见性降级(登记行停在 PENDING,直到读取侧两阶段探测补位或孤儿老化窗口解锁),不会变成「失败被误判为成功」。 这是 TFRS-508 同时保留 errback 与读取侧探测两条腿的理由。

对侧无需改码;只需在改动 converting 的异常处理时知悉本项。若要在对侧 CI 固化该假设, 需在对侧加守卫测试(本端无法代劳,CLAUDE.md 边界),可挂 TFRSUC 后续单。

后续建议(不在本任务范围)

  • 对侧启动期断言:TFRSUtilsCelery 建议同款(其配置字段已有 ge=1,补一份 == 7+DB 1 断言,违约显式失败)。本端无法修改对侧代码(CLAUDE.md 边界), 由对侧跟进(可挂 TFRSUC-15 后续)。
  • 契约值调整(如迁移 DB 2)须两侧同步修改 + 更新本页 + 更新 protocols/result_backend_contract.py 常量(其 docstring 已注明)。