oh-my-muse/.agents/rules/contract-first.md
lili 3585219637
Some checks failed
Backend Maven CI / backend-local (push) Has been cancelled
feat(mvp): 收束1.0.0线A交付闭环
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-27 10:52:10 -07:00

4.9 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,切勿改历史迁移;门禁自动校验连续与唯一。
  • 需要“契约↔实现不漂移”的更强门禁(OpenAPI operationId 与控制器/服务交叉核对)时,与 P0 覆盖台账(P1rApiCoverageReportTest)合并设计,避免重复台账。