oh-my-muse/.agents/rules/contract-first.md
lili 7a6e46b2fe fix(test): S9 整分支收口修 S6d 台账漂移+V35 跳号登记+S8 导入向导 IT 上下文
- 覆盖台账 p1r-api-coverage.json 与生成器去 S6d 已删 RAGFlow 文件引用,知识域 serviceFiles/testFiles 换 Dify 继任,保 32 market dormant 口径不被重生成摧毁
- ContractFirstGateTest 登记 V35 有意跳号(并行线版本预留,V36/V37 已应用 live 库不可回填)+ stale 守卫
- P1rContentImportWizardCompletedApprovalIT 补 MuseAiProperties @Bean(S8 加 @Resource 依赖后极简上下文缺 bean 致 ApplicationContext 加载失败)
- 独立复跑:local 门 65/0F/0E BUILD SUCCESS;import wizard 真PG IT 2/0F/0E(1 live skip)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 10:19:11 -07:00

5.5 KiB

规则:契约先行 —— API/DB 契约为 SSOT(机械门禁)

类型:硬约束(rules) · 状态:生效 2026-06-14 · 门禁:JUnit/surefire(本地+CI 机械阻断) 上位:verification-and-anti-false-green.md §二 由来:契约与 DB schema 此前靠自觉维护、无机械校验,易漂移、易被误删/写坏而不被发现——又一处“假绿”温床。


一、SSOT 原地认定(不另起 contracts/ 副本)

本项目已有两套契约源,原地认定为 SSOT,禁止复制到新目录形成双源漂移(否则即犯“孤儿/拼接设计”):

契约类别 SSOT 位置 运行时关系
API 契约 docs/api-contracts/<域>/openapi.yaml + docs/api-contracts/openapi-base.yaml 前后端/跨 BC 协作契约;docs/api-contracts/generated/ 为产物,非 SSOT
DB schema 契约 muse-cloud/sql/muse/V<版本>__<描述>.sql Flyway spring.flyway.locationsfilesystem:sql/muse,运行时真实执行

参考项目 games-development-ai 把契约集中到根 contracts/;本项目因 Flyway 已绑定 sql/muse、OpenAPI 已成体系,原地认定比物理搬迁更小风险、零重复。需要统一入口时由 ../../AGENTS.md 与本规则索引指向,不复制内容。


二、规则

  • 契约先行:任何对外 API / DB 结构变更,先改契约(OpenAPI / 新增 Flyway 迁移),再改实现;实现反向对齐契约。
  • DB 迁移只增不改:已合入的 V*.sql 不得编辑(改其内容会破坏 Flyway 校验和与他人已应用的历史);变更一律新增更高版本号迁移。
  • 跨 BC / 前后端只认契约,不靠口头约定;契约缺失即视为接口未定义。

三、机械门禁(不是自觉)

  • 测试:muse-server/src/test/java/cn/iocoder/muse/server/framework/contract/ContractFirstGateTest.java,随 CI(maven.yml,P0 已打开测试门 + JDK21)在 PR/push 阻断。四类:
    1. Flyway 迁移卫生:版本号唯一、从 1 连续无缺口、命名 V<版本>__<描述>.sql。重复/缺口/乱名 → 红。
    2. OpenAPI 契约存在性+结构:必备业务域(account/ai/content/events/knowledge/market/meta)各有可解析的 openapi.yaml,含 openapi/info/paths 顶层键且为 OAS3。被删/写坏 → 红。
    3. OpenAPI diff workflow 位置:必须在仓库根 .github/workflows/openapi-diff.yml,不得回流到子目录。
    4. Maven CI workflow 位置与纯层口径:必须在仓库根 .github/workflows/maven.yml,不得跳测试,不得在默认 CI 接 real-PG/live 凭据。
  • 已验证:2026-06-14 JDK21 全 reactor clean test 曾通过;2026-06-27 E0 后该门禁扩为 4 类,以当前 CI / 本地复跑结果为准。

四、加固:openapi-diff 破坏性变更检测(CI 级,仓库根 workflow 已接线 / 检测逻辑已本地实证 / 远端首跑待确认)

存在性/结构门禁挡不住“悄悄改坏既有契约语义”(删字段、改必填、改类型)。破坏性变更检测oasdiff 在 PR 上比对 base 分支与 PR 的 OpenAPI,发现破坏性差异即红。 已落地为独立 workflow .github/workflows/openapi-diff.yml(PR 改 docs/api-contracts/** 时触发,逐域 oasdiff breaking ... --fail-on ERR,镜像钉 tufin/oasdiff:v1.19.1)。workflow 即 SSOT,此处不复制 YAML(避免双源漂移)。2026-06-19 已从 muse-cloud/.github/workflows/ 移到仓库根,因为自建 Gitea Actions 只扫描仓库根 .github/workflows/

检测逻辑 + 逐域循环聚合:已本地实证(2026-06-17,oasdiff v1.19.1)。复现脚本 muse-cloud/scripts/verify-openapi-diff.sh 忠实复刻 workflow 循环,对全部 7 域真实契约跑 5 场景全绿:无变更/新增可选参数/新增端点→放行;参数变必填(request-parameter-became-required)/删端点(api-path-removed-without-deprecation)→拦截。坑与配方见 knowledge §七

⚠️ 残留:远端 Gitea Actions 触发管道(pull_request paths 触发、base.sha worktree、docker 镜像拉取)须一次真实 PR(改 docs/api-contracts/**)首跑确认。首跑前:可宣称"破坏性变更检测逻辑已本地实证(对真实契约拦真破坏、放行安全演化),root workflow 已接线",但不得宣称"CI 已在线上实拦"。


五、扩展方式

  • 新增业务域:在 docs/api-contracts/<域>/openapi.yaml 建契约,并把 <域> 加入 ContractFirstGateTest.REQUIRED_API_DOMAINS(机械要求该域契约长存)。
  • DB 结构演进:新增 V<下一版本>__<描述>.sql,切勿改历史迁移;门禁自动校验连续与唯一。
  • 有意跳号登记(V35):muse-cloud/sql/muse/ 现有 V34、V36、V37 而无 V35,是并行开发线 S6/S7/S8 之间预留版本号造成的跳号(V34=import-parse、V36=瞬态 fulltext、V37=knowledge runtime_batch_id)。V36/V37 已应用到 live 库 muse_slice_live,按“迁移只增不改”回填 V35 会破坏 Flyway 校验和、损坏已部署库,故不回填。V35 已在 ContractFirstGateTest.INTENTIONALLY_SKIPPED_VERSIONS 登记为有意跳号,连续性门禁豁免它;日后若确有该结构变更需求,新建更高版本号迁移承载,不占用 V35。
  • 需要“契约↔实现不漂移”的更强门禁(OpenAPI operationId 与控制器/服务交叉核对)时,与 P0 覆盖台账(P1rApiCoverageReportTest)合并设计,避免重复台账。