Skip to content

[Decision] DESIGN.md §03 requires a termination reason but declares no field for it #6

Description

@hotlong

维护者速读

事情:设计方案第 7 章的状态转换守卫写着「生效 → 已终止:终止日期与原因必填」,但第 3 章的字段清单里没有「终止原因」这个字段。卡 02 实现状态机时只能盖终止日期,原因无处可存。

为什么现在问你:修法本身很小(加一个字段),但它要改 DESIGN.md §03,而仓库规矩是代码 PR 不得改 §01–§04,必须先有你的裁定。

选项:

  • A(推荐):加一个「终止原因」文本字段,只在终止时必填,其余时候不出现在表单上;在卡 09 落地,同时补 §03 字段清单。
  • B:把守卫里的「原因必填」去掉,终止原因写在合同摘要里。

代价:A 多一个字段,多一行设计文档修订;B 不加字段,但终止合同将没有结构化的原因记录,审计时只能翻自由文本。

你要做的:回一个字母,A 或 B。


Context

DESIGN.md §03 state-machine table:

从 到 守卫
active terminated closed_at 与终止原因必填

The §03 field list for clm_contract declares closed_at but no reason field. Card 02 (#2, PR #5) implemented the guard as far as it could — it stamps closed_at and refuses without it — and reported the gap rather than inventing a field outside the pinned list.

Governing text: DESIGN.md §03 (the guard row above, and the clm_contract field list); AGENTS.md → Delivery process → "Files a code PR never touches: … DESIGN.md §01–§04 without a needs-user-decision first".

Options

A — declare termination_reason (textarea, requiredWhen the status is terminated), amend the §03 field list in the same change, implement in card 09 alongside F13/F14. The field never appears on the intake form; it is asked exactly once, at termination.

B — drop "原因必填" from the guard wording. The reason, when someone bothers to record it, lives in summary.

四棱分析

实际业务需求 — A. 提前终止的合同是法务与审计最常回头查的一类记录,而「为什么终止」正是他们问的第一个问题:是对方违约、是我方不再需要、还是双方协商。设计方案自己的守卫已经写了「原因必填」,说明写方案时就认定这是业务需要,不是事后加戏。B 把这个答案散进自由文本,等于放弃了按原因统计终止合同的能力。

项目长远合理性 — A。当前设计文档自相矛盾:守卫引用了一个字段清单里不存在的字段。两个选项都能消除矛盾,但方向相反:A 让实现追上已声明的意图,B 削弱意图去迁就实现。契约优先的规矩是以协议为基准、改代码对齐,A 是那个方向。

防 AI 写错 — A,而且这一棱最不平衡。requiredWhen 是运行期强制的结构性约束:任何写入者,无论是人、流程还是 AI,都不可能在不给原因的情况下终止一份合同。B 依赖的是约定(「记得写进摘要」),而约定恰恰是错误能静默累积的地方 —— 这正是「声明即强制,绝不让 AI 声明一个运行时不兑现的能力」要防的形状。今天守卫说必填而系统不存原因,就已经是一个不兑现的声明。

创业阶段不扩散 — 大致中性,略偏 A。A 只加一个字段,且这个字段是已确认的 V1.0 基准自己要求的,不是新增能力面,因此不构成需求扩散。B 表面上更紧,但它删掉的是一条已声明的能力,属于反方向的基准变更,同样需要你拍板。

结论:四棱同向 A,第三棱(防错)差距最大。推荐 A。

Impact if deferred

None blocking. Card 02 shipped with closed_at only; card 09 (F13/F14, expiry and archive) is where the terminate path gets its flows and is the natural place to land the field. Answering before card 09 is dispatched avoids a second pass over the same hook.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions