Skip to content

data import: mid-lifecycle rows rejected by state_machine.initialStates — historical-data migration blocked (#3433 sibling) #3479

Description

@os-zhuang

现象

用户通过数据导入(CSV / 批量 API)导入历史 mid-lifecycle 记录时,凡是记录的状态字段值 ∉ 对象 state_machine.initialStates,该行被拒。典型场景:迁移一批已 closed 的历史工单、已 closed_won 的历史商机、已 completed 的历史项目——这些正是数据迁移的核心内容,却被 FSM 入口校验挡下。

#3433(种子重放被 initialStates 拒)完全同构,只是换了入口:#3433 修了种子写路径,但数据导入这条平行路径没修。区别是导入不静默——import-runner 有 per-row 结果,会报 invalid_initial_state,所以用户看得到"导入 100 行,拒了 80 行",但合法的历史迁移场景被功能性挡住。

机制

  1. feat(validation): state_machine.initialStates enforces the FSM entry point on INSERT (#3165) #3188(实现 enhancement: state_machine 校验规则在 INSERT 上是 no-op —— 无法强制记录的初始状态(declared ≠ enforced) #3165)让 state_machine.initialStates 在 INSERT 上强制 FSM 入口:创建值必须 ∈ initialStates,否则 400 invalid_initial_state(packages/objectql/src/validation/rule-validator.ts)。
  2. seed replay vs state_machine initialStates: mid-lifecycle fixture rows are silently rejected on INSERT (showcase kanban can never repopulate) #3433种子写路径豁免 state_machine:SeedLoaderService.SEED_OPTIONSExecutionContext.seedReplay → 引擎传 skipStateMachine → rule-validator 跳过。
  3. 数据导入的写入 context 没带这个旗标:packages/rest/src/import-runner.ts:265
    const writeCtx = { ...(context ?? {}), skipAutomations: !runAutomations };
    只有 skipAutomations,没有 seedReplay/skipStateMachine。导入写入经 createManyData/insertManyData(packages/metadata-protocol/src/protocol.ts:3377/3405)→ 引擎 → 照跑 state_machine initialStates 校验。

影响面

候选修法(需拍板)

不能照抄种子的一刀切豁免。 种子是平台/包策展的既成事实,无条件豁免合理;用户导入的意图是二义的:

  • 历史数据迁移 → 导入的是既成事实,应豁免 state_machine(和种子同理);
  • 批量新建 → 真的在创建新记录,应走 initialStates(否则用户能批量绕过 FSM 入口)。

所以应该是一个用户可选开关,而非默认豁免。好消息:

  • 引擎管道已就绪(seed replay vs state_machine initialStates: mid-lifecycle fixture rows are silently rejected on INSERT (showcase kanban can never repopulate) #3433 建的 seedReplay → skipStateMachine),import 只需在开关打开时往 writeCtx 塞对应 context 旗标即可,几乎零引擎改动。
  • 有现成的开关先例:import 已有 runAutomations / run_automations(import-runner.ts:104rest-server.ts:3833)映射到 skipAutomations。平行地加一个 treatAsHistorical(或 skipLifecycleValidation)选项,映射到 skipStateMachine/seedReplay context,默认(保持新建走 FSM),迁移历史数据时由用户显式开。
  • 配套:导入向导(objectui)加一个复选框「作为历史数据导入(跳过生命周期校验)」;REST 导入 API 加对应参数;import_job 记录该标志(便于审计 + undo 语义一致)。

倾向:加显式开关(默认关),复用 #3433 的引擎豁免管道。

复现

对一个声明了 state_machine + initialStates: ['prospecting'](events 含 insert)的对象,通过导入 API 批量导入若干 stage: 'closed_won' 的记录 → 每行 400 invalid_initial_state,import 结果 per-row errored,历史行全部丢失。

参考

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions