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 一致。
校验机制
- 启动期断言(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等)。 - 对表工具:
make check-result-contract(scripts/check-result-contract.py)——本端 经 loader,对侧经TFC_URI__BACKEND/TFC_CELERY__RESULT_EXPIRES_DAYS注入; 输出逐维度 ✅/❌/⚠️,任何 ❌ exit 1;对侧未提供维度 ⚠️ 不 gate,--strict可强制。 - 继承约束:本端 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 已注明)。