release: FL 2.0 候选(C01 记账边界+P2-2 认领协议+D10 变量契约+D11 资格终审+D09 激活代次+T01/T03/T04/T05)

按《FL最终代码审计与上线阻断清单》194 项已审计工作树形成候选,基座 = origin/main 11e0778。

- 源码/测试/资源:沿用已审计工作树内容(96 项相对远端为新增或更新),含未跟踪实现
  CallbackFactStore、CallbackSourcePolicy、VariableContract、FormalApplyCommandService、
  FlowActorContextGate 等;C01 续验夹具修正一并入内。
- CI:保留远端 11e0778 的门禁强化(CI_SOURCE_SHA 核对、容器回收验证、空 schema 反向门禁、
  main ref 才推镜像);ci/flow-test-schema.sql 按当前实体重新生成(14→20 表,补 6 新表与
  6 新列,回调日志复合唯一键 uk_flw_log_event_reg),workflow 表数断言同步 14→20。
- 依据:docs/tasks/evidence/FL-final-audit-20261027(194 项哈希/版本核对)。
This commit is contained in:
chenxing
2026-10-27 12:05:08 +08:00
parent 11e077867b
commit 500a9a2fcb
99 changed files with 7973 additions and 633 deletions
@@ -12,6 +12,7 @@ import org.junit.jupiter.api.Test;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -64,7 +65,9 @@ class FlowDefAdminServiceUpdateMetaTest {
}
@Test
void 三不变_contentJson仅顶层name() throws Exception {
void 已发布版本_contentJson封存_仅行内元数据可改() throws Exception {
// T03(2026-10-25)发布封存:执行文档不可变——行 name 照改(目录元数据),
// content_json 顶层 name 保持发布时字节(旧断言"同步改 JSON"已按新原则作废)
String json = "{\"code\":\"work-leave\",\"name\":\"旧名\",\"start\":\"start\","
+ "\"nodes\":[{\"id\":\"start\",\"type\":\"START\"}],"
+ "\"edges\":[{\"from\":\"start\",\"to\":\"end\"}]}";
@@ -79,23 +82,36 @@ class FlowDefAdminServiceUpdateMetaTest {
assertEquals("work-leave", saved.getCode());
assertEquals("新名称", saved.getName());
assertEquals("100008", saved.getAppKey());
assertEquals("新名称", new ObjectMapper().readTree(saved.getContentJson()).path("name").asText());
// 结构零改动:节点/边/其余顶层字段原样保留
// 发布字节冻结:文档内 name 不随目录改名漂移
assertEquals("旧名", new ObjectMapper().readTree(saved.getContentJson()).path("name").asText());
assertEquals("work-leave", new ObjectMapper().readTree(saved.getContentJson()).path("code").asText());
assertTrue(saved.getContentJson().contains("\"type\":\"START\""));
}
@Test
void contentJson无顶层name_补写() throws Exception {
void 已发布版本_无顶层name_不补写() throws Exception {
// 发布封存:缺顶层 name 的已发布文档维持原样,不补写
FlwDef def = published("{\"code\":\"c\",\"start\":\"start\",\"nodes\":[],\"edges\":[]}");
when(defRepository.findById(9L)).thenReturn(Optional.of(def));
FlwDef saved = service.updateMeta(SCH, 9L, "补写名", null);
assertEquals("补写名", new ObjectMapper().readTree(saved.getContentJson()).path("name").asText());
assertFalse(new ObjectMapper().readTree(saved.getContentJson()).has("name"));
assertNull(saved.getAppKey());
}
@Test
void 草稿版本_contentJson顶层name仍同步() throws Exception {
// 草稿文档可变:顶层 name 同步保留(与发布封存互为边界)
FlwDef def = published("{\"code\":\"c\",\"name\":\"旧名\",\"start\":\"start\",\"nodes\":[],\"edges\":[]}");
def.setStatus(0); // DRAFT
when(defRepository.findById(9L)).thenReturn(Optional.of(def));
FlwDef saved = service.updateMeta(SCH, 9L, "草稿新名", null);
assertEquals("草稿新名", new ObjectMapper().readTree(saved.getContentJson()).path("name").asText());
}
@Test
void appKey白名单外_被拦() {
when(appRepository.isEnabledApp(SCH, "888888")).thenReturn(false);