diff --git a/muse-cloud/muse-module-meta/muse-module-meta-api/src/main/java/cn/iocoder/muse/module/meta/api/impact/MetaImpactDimension.java b/muse-cloud/muse-module-meta/muse-module-meta-api/src/main/java/cn/iocoder/muse/module/meta/api/impact/MetaImpactDimension.java
new file mode 100644
index 00000000..f94165a6
--- /dev/null
+++ b/muse-cloud/muse-module-meta/muse-module-meta-api/src/main/java/cn/iocoder/muse/module/meta/api/impact/MetaImpactDimension.java
@@ -0,0 +1,13 @@
+package cn.iocoder.muse.module.meta.api.impact;
+
+/**
+ * Meta Schema 用量影响的 5 个消费维度(各 BC owner 实现对应贡献者)。
+ */
+public enum MetaImpactDimension {
+
+ PLANNING,
+ WORK,
+ KNOWLEDGE_PROJECTION,
+ AI_CONTEXT,
+ EXPORT
+}
diff --git a/muse-cloud/muse-module-meta/muse-module-meta-api/src/main/java/cn/iocoder/muse/module/meta/api/impact/MetaSchemaDimensionImpact.java b/muse-cloud/muse-module-meta/muse-module-meta-api/src/main/java/cn/iocoder/muse/module/meta/api/impact/MetaSchemaDimensionImpact.java
new file mode 100644
index 00000000..bcd28123
--- /dev/null
+++ b/muse-cloud/muse-module-meta/muse-module-meta-api/src/main/java/cn/iocoder/muse/module/meta/api/impact/MetaSchemaDimensionImpact.java
@@ -0,0 +1,28 @@
+package cn.iocoder.muse.module.meta.api.impact;
+
+/**
+ * 贡献者→Meta 的单维度影响结果。
+ *
+ *
无 unknown 态:无法真实作答的贡献者必须抛异常(由聚合器 fail-closed),绝不返回 0 蒙混。
+ * realityKind 区分「真实计数」与「已验证真实 0」(后者须机械化可证)。
+ */
+public record MetaSchemaDimensionImpact(
+ MetaImpactDimension dimension,
+ int affectedInstanceCount,
+ int fieldRemovedInstanceCount,
+ int typeChangedInstanceCount,
+ int visibilityChangedInstanceCount,
+ RealityKind realityKind) {
+
+ public enum RealityKind {
+ /** 真实查询计数。 */
+ REAL_COUNT,
+ /** 已验证本域确无 schema 消费(机械化可证),非 unknown。 */
+ VERIFIED_ZERO
+ }
+
+ /** 已验证真实 0:本域确不消费 schema,各计数恒 0。 */
+ public static MetaSchemaDimensionImpact verifiedZero(MetaImpactDimension dimension) {
+ return new MetaSchemaDimensionImpact(dimension, 0, 0, 0, 0, RealityKind.VERIFIED_ZERO);
+ }
+}
diff --git a/muse-cloud/muse-module-meta/muse-module-meta-api/src/main/java/cn/iocoder/muse/module/meta/api/impact/MetaSchemaImpactProbe.java b/muse-cloud/muse-module-meta/muse-module-meta-api/src/main/java/cn/iocoder/muse/module/meta/api/impact/MetaSchemaImpactProbe.java
new file mode 100644
index 00000000..18c0a859
--- /dev/null
+++ b/muse-cloud/muse-module-meta/muse-module-meta-api/src/main/java/cn/iocoder/muse/module/meta/api/impact/MetaSchemaImpactProbe.java
@@ -0,0 +1,17 @@
+package cn.iocoder.muse.module.meta.api.impact;
+
+import java.util.Set;
+
+/**
+ * Meta→各消费域贡献者的探针:携带草稿标识 + G1 算出的三类破坏字段集。
+ *
+ *
破坏字段集由 meta-server G1 统一算好传入,贡献者只在本域按 fieldKey 计数(守 BC 边界门,不回查 meta)。
+ */
+public record MetaSchemaImpactProbe(
+ String schemaKey,
+ Integer draftVersion,
+ String draftHash,
+ Set removedFieldKeys,
+ Set typeChangedFieldKeys,
+ Set visibilityChangedFieldKeys) {
+}
diff --git a/muse-cloud/muse-module-meta/muse-module-meta-api/src/main/java/cn/iocoder/muse/module/meta/api/impact/MetaSchemaUsageContributor.java b/muse-cloud/muse-module-meta/muse-module-meta-api/src/main/java/cn/iocoder/muse/module/meta/api/impact/MetaSchemaUsageContributor.java
new file mode 100644
index 00000000..9fb5cfe9
--- /dev/null
+++ b/muse-cloud/muse-module-meta/muse-module-meta-api/src/main/java/cn/iocoder/muse/module/meta/api/impact/MetaSchemaUsageContributor.java
@@ -0,0 +1,16 @@
+package cn.iocoder.muse.module.meta.api.impact;
+
+/**
+ * Meta Schema 用量贡献者端口(进程内 @Service Bean,参照 MuseAccountRecordProjectionApi 先例,非 @FeignClient)。
+ *
+ * 由 Meta 定义、各 owner BC 在本域实现:只读本域、按 probe 的破坏字段集计数,返回本维度影响。
+ * 无法真实作答→抛异常,由聚合器 fail-closed(绝不返回 0 蒙混)。
+ */
+public interface MetaSchemaUsageContributor {
+
+ /** 计算本维度受影响实例(只读本域)。 */
+ MetaSchemaDimensionImpact previewDimensionImpact(MetaSchemaImpactProbe probe);
+
+ /** 本贡献者负责的维度(供聚合器校验 5 维度齐全)。 */
+ MetaImpactDimension dimension();
+}
diff --git a/muse-cloud/muse-module-meta/muse-module-meta-server/src/main/java/cn/iocoder/muse/module/meta/application/facade/RealMetaImpactFacade.java b/muse-cloud/muse-module-meta/muse-module-meta-server/src/main/java/cn/iocoder/muse/module/meta/application/facade/RealMetaImpactFacade.java
new file mode 100644
index 00000000..0267c456
--- /dev/null
+++ b/muse-cloud/muse-module-meta/muse-module-meta-server/src/main/java/cn/iocoder/muse/module/meta/application/facade/RealMetaImpactFacade.java
@@ -0,0 +1,188 @@
+package cn.iocoder.muse.module.meta.application.facade;
+
+import cn.iocoder.muse.framework.common.exception.ServiceException;
+import cn.iocoder.muse.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.muse.module.meta.api.impact.MetaImpactDimension;
+import cn.iocoder.muse.module.meta.api.impact.MetaSchemaDimensionImpact;
+import cn.iocoder.muse.module.meta.api.impact.MetaSchemaImpactProbe;
+import cn.iocoder.muse.module.meta.api.impact.MetaSchemaUsageContributor;
+import cn.iocoder.muse.module.meta.application.MetaSchemaBreakingFieldSet;
+import cn.iocoder.muse.module.meta.application.MetaSchemaBreakingFieldSetProducer;
+import cn.iocoder.muse.module.meta.controller.admin.vo.AIContextImpactSummaryVO;
+import cn.iocoder.muse.module.meta.controller.admin.vo.ExportImpactSummaryVO;
+import cn.iocoder.muse.module.meta.controller.admin.vo.KnowledgeProjectionImpactSummaryVO;
+import cn.iocoder.muse.module.meta.controller.admin.vo.PlanningImpactSummaryVO;
+import cn.iocoder.muse.module.meta.controller.admin.vo.WorkImpactSummaryVO;
+import cn.iocoder.muse.module.meta.dal.dataobject.MetaFieldDO;
+import cn.iocoder.muse.module.meta.dal.dataobject.MetaSchemaDO;
+import cn.iocoder.muse.module.meta.dal.dataobject.MetaSchemaVersionDO;
+import cn.iocoder.muse.module.meta.dal.dataobject.MetaVisibilityPolicyDO;
+import cn.iocoder.muse.module.meta.dal.mysql.MetaFieldMapper;
+import cn.iocoder.muse.module.meta.dal.mysql.MetaSchemaMapper;
+import cn.iocoder.muse.module.meta.dal.mysql.MetaSchemaVersionMapper;
+import cn.iocoder.muse.module.meta.dal.mysql.MetaVisibilityPolicyMapper;
+import jakarta.annotation.Resource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Primary;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static cn.iocoder.muse.module.meta.enums.ErrorCodeConstants.META_EXTERNAL_OWNER_UNAVAILABLE;
+import static cn.iocoder.muse.module.meta.enums.ErrorCodeConstants.META_SCHEMA_IDENTITY_CONFLICT;
+import static cn.iocoder.muse.module.meta.enums.ErrorCodeConstants.META_SCHEMA_NOT_FOUND;
+import static cn.iocoder.muse.module.meta.enums.ErrorCodeConstants.META_SCHEMA_VERSION_NOT_FOUND;
+
+/**
+ * Meta 影响预览真实聚合器(P0b)。
+ *
+ *
替换 {@code UnavailableMetaImpactFacade}:破坏判定(Meta 域 G1)与实例计数(各消费域贡献者)干净分离——
+ * G1 算出破坏字段集、组 probe,fan-out 到各 BC 贡献者,贡献者只读本域返回本维度计数,聚合器组装既有 VO。
+ *
+ *
all-real 门(绝不部分放行/不伪造/不降级):① 5 维度贡献者须齐全(缺维度/同维度多实现→fail-closed);
+ * ② 任一贡献者抛异常 / 返回 null / 无 realityKind→整体 fail-closed。当前 P1/P2/P3 贡献者未接入→注入空 List→
+ * 缺 5 维度→fail-closed,等价于现状(preview 不放行),待贡献者接入后自然真实化。
+ *
+ *
{@code @Primary @Service}(去 @ConditionalOnMissingBean,该注解在 @Service 上不可靠):退役 Unavailable @Service 后
+ * 单体内仅此一个 {@link MetaImpactFacade} Bean。
+ */
+@Primary
+@Service
+public class RealMetaImpactFacade implements MetaImpactFacade {
+
+ /** all-real 门要求齐全的 5 个消费维度。 */
+ private static final Set REQUIRED_DIMENSIONS = Set.of(MetaImpactDimension.values());
+
+ @Resource
+ private MetaSchemaMapper schemaMapper;
+ @Resource
+ private MetaSchemaVersionMapper schemaVersionMapper;
+ @Resource
+ private MetaFieldMapper fieldMapper;
+ @Resource
+ private MetaVisibilityPolicyMapper visibilityPolicyMapper;
+ @Resource
+ private MetaSchemaBreakingFieldSetProducer breakingFieldSetProducer;
+ /** 各 BC owner 贡献者(进程内 @Service Bean);未接入时 Spring 注入空 List。 */
+ @Autowired(required = false)
+ private List contributors = new ArrayList<>();
+
+ @Override
+ public MetaSchemaImpactSummary previewMetaSchemaDraftImpact(String schemaKey, Integer draftVersion, String draftHash) {
+ // all-real 门 1:5 维度贡献者齐全(缺维度/重复→fail-closed,绝不部分放行)。
+ Map byDimension = indexRequiredContributors();
+
+ // G1 算破坏字段集 → 组 probe(破坏集由 Meta 统一算好传入,贡献者只在本域计数)。
+ MetaSchemaBreakingFieldSet breaking = computeBreakingFieldSet(schemaKey, draftVersion);
+ MetaSchemaImpactProbe probe = new MetaSchemaImpactProbe(schemaKey, draftVersion, draftHash,
+ breaking.removedFieldKeys(), breaking.typeChangedFieldKeys(), breaking.visibilityChangedFieldKeys());
+
+ // fan-out 各维度;任一异常/返回 null/无 realityKind → fail-closed(不吞/不降级/不伪造)。
+ Map impacts = new EnumMap<>(MetaImpactDimension.class);
+ for (MetaImpactDimension dimension : MetaImpactDimension.values()) {
+ MetaSchemaDimensionImpact impact = byDimension.get(dimension).previewDimensionImpact(probe);
+ if (impact == null || impact.realityKind() == null) {
+ throw new ServiceException(META_EXTERNAL_OWNER_UNAVAILABLE);
+ }
+ impacts.put(dimension, impact);
+ }
+ return toSummary(impacts);
+ }
+
+ /** 索引贡献者并校验恰好 5 维度齐全(维度为空 / 同维度重复 / 缺维度均 fail-closed)。 */
+ private Map indexRequiredContributors() {
+ Map byDimension = new EnumMap<>(MetaImpactDimension.class);
+ for (MetaSchemaUsageContributor contributor : contributors) {
+ MetaImpactDimension dimension = contributor.dimension();
+ if (dimension == null || byDimension.put(dimension, contributor) != null) {
+ // 维度为空 or 同维度重复贡献者 → 装配异常,fail-closed(防数据错乱)。
+ throw new ServiceException(META_EXTERNAL_OWNER_UNAVAILABLE);
+ }
+ }
+ if (!byDimension.keySet().containsAll(REQUIRED_DIMENSIONS)) {
+ // 5 维度未齐 → fail-closed(回到现状:preview 不放行)。
+ throw new ServiceException(META_EXTERNAL_OWNER_UNAVAILABLE);
+ }
+ return byDimension;
+ }
+
+ /** 查 active/draft 字段与可见性快照,调 G1 产出破坏字段集。 */
+ private MetaSchemaBreakingFieldSet computeBreakingFieldSet(String schemaKey, Integer draftVersion) {
+ MetaSchemaDO schema = requireUniqueSchema(schemaKey);
+ MetaSchemaVersionDO draft = schemaVersionMapper.selectByVersion(schemaKey, draftVersion);
+ if (draft == null) {
+ throw new ServiceException(META_SCHEMA_VERSION_NOT_FOUND);
+ }
+ MetaSchemaVersionDO active = schemaVersionMapper.selectActiveBySchemaId(schema.getId());
+ List activeFields = active == null ? List.of() : fieldMapper.selectListByVersionId(active.getId());
+ List draftFields = fieldMapper.selectListByVersionId(draft.getId());
+ String activeSnapshot = active == null ? null : policySnapshot(active.getId());
+ String draftSnapshot = policySnapshot(draft.getId());
+ return breakingFieldSetProducer.produce(activeFields, draftFields, activeSnapshot, draftSnapshot);
+ }
+
+ private String policySnapshot(Long versionId) {
+ MetaVisibilityPolicyDO policy = visibilityPolicyMapper.selectByVersionId(versionId);
+ return policy == null ? null : policy.getPolicySnapshot();
+ }
+
+ private MetaSchemaDO requireUniqueSchema(String schemaKey) {
+ List schemas = schemaMapper.selectList(new LambdaQueryWrapperX()
+ .eq(MetaSchemaDO::getSchemaKey, schemaKey));
+ if (schemas == null || schemas.isEmpty()) {
+ throw new ServiceException(META_SCHEMA_NOT_FOUND);
+ }
+ if (schemas.size() > 1) {
+ throw new ServiceException(META_SCHEMA_IDENTITY_CONFLICT);
+ }
+ return schemas.getFirst();
+ }
+
+ /** DTO→既有 VO 映射(口径与 MetaSchemaImpactPreviewServiceTest 既有断言一致)。 */
+ private MetaSchemaImpactSummary toSummary(Map impacts) {
+ MetaSchemaDimensionImpact work = impacts.get(MetaImpactDimension.WORK);
+ MetaSchemaDimensionImpact planning = impacts.get(MetaImpactDimension.PLANNING);
+ MetaSchemaDimensionImpact knowledge = impacts.get(MetaImpactDimension.KNOWLEDGE_PROJECTION);
+ MetaSchemaDimensionImpact ai = impacts.get(MetaImpactDimension.AI_CONTEXT);
+ MetaSchemaDimensionImpact export = impacts.get(MetaImpactDimension.EXPORT);
+
+ WorkImpactSummaryVO workVO = new WorkImpactSummaryVO();
+ workVO.setAffectedWorkCount(work.affectedInstanceCount());
+ workVO.setAffectedDynamicFieldWorkCount(work.affectedInstanceCount());
+ workVO.setWorksWithDeprecatedFields(breakingTotal(work));
+
+ PlanningImpactSummaryVO planningVO = new PlanningImpactSummaryVO();
+ planningVO.setAffectedPlanningSections(planning.affectedInstanceCount());
+ planningVO.setPlanningUsingDeprecatedFields(breakingTotal(planning));
+
+ KnowledgeProjectionImpactSummaryVO knowledgeVO = new KnowledgeProjectionImpactSummaryVO();
+ knowledgeVO.setAffectedProjections(knowledge.affectedInstanceCount());
+ knowledgeVO.setProjectionsNeedingRebuild(
+ knowledge.fieldRemovedInstanceCount() + knowledge.typeChangedInstanceCount());
+ knowledgeVO.setProjectionsWithVisibilityChange(knowledge.visibilityChangedInstanceCount());
+
+ AIContextImpactSummaryVO aiVO = new AIContextImpactSummaryVO();
+ aiVO.setAffectedAIContexts(ai.affectedInstanceCount());
+ aiVO.setContextsWithFieldRemoved(ai.fieldRemovedInstanceCount());
+ aiVO.setContextsWithVisibilityChange(ai.visibilityChangedInstanceCount());
+
+ ExportImpactSummaryVO exportVO = new ExportImpactSummaryVO();
+ exportVO.setAffectedExportPolicies(export.affectedInstanceCount());
+ exportVO.setExportsWithFieldRemoved(export.fieldRemovedInstanceCount());
+ exportVO.setExportsWithVisibilityChange(export.visibilityChangedInstanceCount());
+
+ // totalAffectedProjectionCount 口径 = KnowledgeProjection 受影响投影数(与既有测试断言一致)。
+ return new MetaSchemaImpactSummary(workVO, planningVO, knowledgeVO, aiVO, exportVO,
+ knowledge.affectedInstanceCount());
+ }
+
+ /** 单维度三类破坏实例之和(供 worksWithDeprecatedFields / planningUsingDeprecatedFields 口径)。 */
+ private int breakingTotal(MetaSchemaDimensionImpact impact) {
+ return impact.fieldRemovedInstanceCount() + impact.typeChangedInstanceCount()
+ + impact.visibilityChangedInstanceCount();
+ }
+}
diff --git a/muse-cloud/muse-module-meta/muse-module-meta-server/src/main/java/cn/iocoder/muse/module/meta/application/facade/UnavailableMetaImpactFacade.java b/muse-cloud/muse-module-meta/muse-module-meta-server/src/main/java/cn/iocoder/muse/module/meta/application/facade/UnavailableMetaImpactFacade.java
deleted file mode 100644
index bfd22cba..00000000
--- a/muse-cloud/muse-module-meta/muse-module-meta-server/src/main/java/cn/iocoder/muse/module/meta/application/facade/UnavailableMetaImpactFacade.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package cn.iocoder.muse.module.meta.application.facade;
-
-import cn.iocoder.muse.framework.common.exception.ServiceException;
-import org.springframework.stereotype.Service;
-
-import static cn.iocoder.muse.module.meta.enums.ErrorCodeConstants.META_EXTERNAL_OWNER_UNAVAILABLE;
-
-/**
- * 默认 Meta 影响 facade。
- *
- * P1R-2 Task 5 不能伪造 Content / Knowledge / AI / Export 的影响数量;在真实 owner facade 接入前,
- * 统一返回业务不可用错误,由调用方以 CommonResult code/data/msg 透出。
- */
-// 单体装配下去除 @ConditionalOnMissingBean(@Service 上不可靠,曾致单体启动 MetaImpactFacade 不注册):
-// 全仓仅此一个实现,无真实 impl 竞争,直接 @Service 总注册;语义仍 fail-closed(抛 unavailable)不变。
-@Service
-public class UnavailableMetaImpactFacade implements MetaImpactFacade {
-
- @Override
- public MetaSchemaImpactSummary previewMetaSchemaDraftImpact(String schemaKey, Integer draftVersion,
- String draftHash) {
- throw new ServiceException(META_EXTERNAL_OWNER_UNAVAILABLE);
- }
-
-}
diff --git a/muse-cloud/muse-module-meta/muse-module-meta-server/src/test/java/cn/iocoder/muse/module/meta/application/facade/RealMetaImpactFacadeTest.java b/muse-cloud/muse-module-meta/muse-module-meta-server/src/test/java/cn/iocoder/muse/module/meta/application/facade/RealMetaImpactFacadeTest.java
new file mode 100644
index 00000000..3ac0b941
--- /dev/null
+++ b/muse-cloud/muse-module-meta/muse-module-meta-server/src/test/java/cn/iocoder/muse/module/meta/application/facade/RealMetaImpactFacadeTest.java
@@ -0,0 +1,171 @@
+package cn.iocoder.muse.module.meta.application.facade;
+
+import cn.iocoder.muse.framework.common.exception.ServiceException;
+import cn.iocoder.muse.framework.test.core.ut.BaseMockitoUnitTest;
+import cn.iocoder.muse.module.meta.api.impact.MetaImpactDimension;
+import cn.iocoder.muse.module.meta.api.impact.MetaSchemaDimensionImpact;
+import cn.iocoder.muse.module.meta.api.impact.MetaSchemaImpactProbe;
+import cn.iocoder.muse.module.meta.api.impact.MetaSchemaUsageContributor;
+import cn.iocoder.muse.module.meta.application.MetaSchemaBreakingFieldSet;
+import cn.iocoder.muse.module.meta.application.MetaSchemaBreakingFieldSetProducer;
+import cn.iocoder.muse.module.meta.dal.dataobject.MetaSchemaDO;
+import cn.iocoder.muse.module.meta.dal.dataobject.MetaSchemaVersionDO;
+import cn.iocoder.muse.module.meta.dal.mysql.MetaFieldMapper;
+import cn.iocoder.muse.module.meta.dal.mysql.MetaSchemaMapper;
+import cn.iocoder.muse.module.meta.dal.mysql.MetaSchemaVersionMapper;
+import cn.iocoder.muse.module.meta.dal.mysql.MetaVisibilityPolicyMapper;
+import org.junit.jupiter.api.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import java.util.List;
+import java.util.Set;
+
+import static cn.iocoder.muse.module.meta.enums.ErrorCodeConstants.META_EXTERNAL_OWNER_UNAVAILABLE;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.when;
+
+/**
+ * 真实聚合器测试:5 维度齐全正确聚合 + DTO→VO 映射口径;缺维度/重复维度/null realityKind 均 fail-closed。
+ */
+class RealMetaImpactFacadeTest extends BaseMockitoUnitTest {
+
+ @InjectMocks
+ private RealMetaImpactFacade facade;
+ @Mock
+ private MetaSchemaMapper schemaMapper;
+ @Mock
+ private MetaSchemaVersionMapper schemaVersionMapper;
+ @Mock
+ private MetaFieldMapper fieldMapper;
+ @Mock
+ private MetaVisibilityPolicyMapper visibilityPolicyMapper;
+ @Mock
+ private MetaSchemaBreakingFieldSetProducer breakingFieldSetProducer;
+
+ @Test
+ void should_aggregate5Dimensions_andMapBreakingCounts_whenAllReal() {
+ stubSchemaLookup();
+ // Planning 真实:7 受影响,破坏 1+0+2;KnowledgeProjection 11 受影响,破坏 3+0+1。
+ setContributors(List.of(
+ stub(MetaImpactDimension.PLANNING, new MetaSchemaDimensionImpact(
+ MetaImpactDimension.PLANNING, 7, 1, 0, 2, MetaSchemaDimensionImpact.RealityKind.REAL_COUNT)),
+ stub(MetaImpactDimension.WORK, MetaSchemaDimensionImpact.verifiedZero(MetaImpactDimension.WORK)),
+ stub(MetaImpactDimension.KNOWLEDGE_PROJECTION, new MetaSchemaDimensionImpact(
+ MetaImpactDimension.KNOWLEDGE_PROJECTION, 11, 3, 0, 1, MetaSchemaDimensionImpact.RealityKind.REAL_COUNT)),
+ stub(MetaImpactDimension.AI_CONTEXT, MetaSchemaDimensionImpact.verifiedZero(MetaImpactDimension.AI_CONTEXT)),
+ stub(MetaImpactDimension.EXPORT, MetaSchemaDimensionImpact.verifiedZero(MetaImpactDimension.EXPORT))));
+
+ MetaImpactFacade.MetaSchemaImpactSummary summary =
+ facade.previewMetaSchemaDraftImpact("work_core", 3, "hash");
+
+ assertEquals(7, summary.planningImpact().getAffectedPlanningSections());
+ // planningUsingDeprecatedFields = fieldRemoved + typeChanged + visibilityChanged = 1+0+2。
+ assertEquals(3, summary.planningImpact().getPlanningUsingDeprecatedFields());
+ assertEquals(11, summary.knowledgeProjectionImpact().getAffectedProjections());
+ // projectionsNeedingRebuild = fieldRemoved + typeChanged = 3+0;visibilityChange = 1。
+ assertEquals(3, summary.knowledgeProjectionImpact().getProjectionsNeedingRebuild());
+ assertEquals(1, summary.knowledgeProjectionImpact().getProjectionsWithVisibilityChange());
+ // VERIFIED_ZERO 维度全 0。
+ assertEquals(0, summary.workImpact().getAffectedWorkCount());
+ assertEquals(0, summary.aiContextImpact().getAffectedAIContexts());
+ assertEquals(0, summary.exportImpact().getAffectedExportPolicies());
+ // totalAffectedProjectionCount = KnowledgeProjection 受影响投影数。
+ assertEquals(11, summary.totalAffectedProjectionCount());
+ }
+
+ @Test
+ void should_failClosed_whenDimensionMissing() {
+ // 仅 4 维度(缺 EXPORT)→ all-real 门 fail-closed。
+ setContributors(List.of(
+ stub(MetaImpactDimension.PLANNING, MetaSchemaDimensionImpact.verifiedZero(MetaImpactDimension.PLANNING)),
+ stub(MetaImpactDimension.WORK, MetaSchemaDimensionImpact.verifiedZero(MetaImpactDimension.WORK)),
+ stub(MetaImpactDimension.KNOWLEDGE_PROJECTION, MetaSchemaDimensionImpact.verifiedZero(MetaImpactDimension.KNOWLEDGE_PROJECTION)),
+ stub(MetaImpactDimension.AI_CONTEXT, MetaSchemaDimensionImpact.verifiedZero(MetaImpactDimension.AI_CONTEXT))));
+
+ ServiceException ex = assertThrows(ServiceException.class,
+ () -> facade.previewMetaSchemaDraftImpact("work_core", 3, "hash"));
+ assertEquals(META_EXTERNAL_OWNER_UNAVAILABLE.getCode(), ex.getCode());
+ }
+
+ @Test
+ void should_failClosed_whenNoContributors() {
+ // 空 List(P1/P2/P3 未接入的当前态)→ fail-closed,等价现状。
+ setContributors(List.of());
+
+ ServiceException ex = assertThrows(ServiceException.class,
+ () -> facade.previewMetaSchemaDraftImpact("work_core", 3, "hash"));
+ assertEquals(META_EXTERNAL_OWNER_UNAVAILABLE.getCode(), ex.getCode());
+ }
+
+ @Test
+ void should_failClosed_whenDuplicateDimension() {
+ // 同维度两个贡献者 → 装配错乱,fail-closed。
+ setContributors(List.of(
+ stub(MetaImpactDimension.PLANNING, MetaSchemaDimensionImpact.verifiedZero(MetaImpactDimension.PLANNING)),
+ stub(MetaImpactDimension.PLANNING, MetaSchemaDimensionImpact.verifiedZero(MetaImpactDimension.PLANNING)),
+ stub(MetaImpactDimension.WORK, MetaSchemaDimensionImpact.verifiedZero(MetaImpactDimension.WORK)),
+ stub(MetaImpactDimension.KNOWLEDGE_PROJECTION, MetaSchemaDimensionImpact.verifiedZero(MetaImpactDimension.KNOWLEDGE_PROJECTION)),
+ stub(MetaImpactDimension.AI_CONTEXT, MetaSchemaDimensionImpact.verifiedZero(MetaImpactDimension.AI_CONTEXT)),
+ stub(MetaImpactDimension.EXPORT, MetaSchemaDimensionImpact.verifiedZero(MetaImpactDimension.EXPORT))));
+
+ ServiceException ex = assertThrows(ServiceException.class,
+ () -> facade.previewMetaSchemaDraftImpact("work_core", 3, "hash"));
+ assertEquals(META_EXTERNAL_OWNER_UNAVAILABLE.getCode(), ex.getCode());
+ }
+
+ @Test
+ void should_failClosed_whenContributorReturnsNullRealityKind() {
+ stubSchemaLookup();
+ // 某贡献者返回无 realityKind(unknown 蒙混)→ fail-closed,绝不放行。
+ setContributors(List.of(
+ stub(MetaImpactDimension.PLANNING, new MetaSchemaDimensionImpact(
+ MetaImpactDimension.PLANNING, 1, 0, 0, 0, null)),
+ stub(MetaImpactDimension.WORK, MetaSchemaDimensionImpact.verifiedZero(MetaImpactDimension.WORK)),
+ stub(MetaImpactDimension.KNOWLEDGE_PROJECTION, MetaSchemaDimensionImpact.verifiedZero(MetaImpactDimension.KNOWLEDGE_PROJECTION)),
+ stub(MetaImpactDimension.AI_CONTEXT, MetaSchemaDimensionImpact.verifiedZero(MetaImpactDimension.AI_CONTEXT)),
+ stub(MetaImpactDimension.EXPORT, MetaSchemaDimensionImpact.verifiedZero(MetaImpactDimension.EXPORT))));
+
+ ServiceException ex = assertThrows(ServiceException.class,
+ () -> facade.previewMetaSchemaDraftImpact("work_core", 3, "hash"));
+ assertEquals(META_EXTERNAL_OWNER_UNAVAILABLE.getCode(), ex.getCode());
+ }
+
+ private void stubSchemaLookup() {
+ MetaSchemaDO schema = new MetaSchemaDO();
+ schema.setId(1L);
+ schema.setSchemaKey("work_core");
+ MetaSchemaVersionDO draft = new MetaSchemaVersionDO();
+ draft.setId(12L);
+ lenient().when(schemaMapper.selectList(any())).thenReturn(List.of(schema));
+ lenient().when(schemaVersionMapper.selectByVersion("work_core", 3)).thenReturn(draft);
+ lenient().when(schemaVersionMapper.selectActiveBySchemaId(1L)).thenReturn(null);
+ lenient().when(fieldMapper.selectListByVersionId(anyLong())).thenReturn(List.of());
+ lenient().when(visibilityPolicyMapper.selectByVersionId(anyLong())).thenReturn(null);
+ lenient().when(breakingFieldSetProducer.produce(any(), any(), any(), any()))
+ .thenReturn(new MetaSchemaBreakingFieldSet(Set.of(), Set.of(), Set.of()));
+ }
+
+ private void setContributors(List contributors) {
+ ReflectionTestUtils.setField(facade, "contributors", contributors);
+ }
+
+ private static MetaSchemaUsageContributor stub(MetaImpactDimension dimension, MetaSchemaDimensionImpact impact) {
+ return new MetaSchemaUsageContributor() {
+ @Override
+ public MetaSchemaDimensionImpact previewDimensionImpact(MetaSchemaImpactProbe probe) {
+ return impact;
+ }
+
+ @Override
+ public MetaImpactDimension dimension() {
+ return dimension;
+ }
+ };
+ }
+}