1.0.539.verify.sql 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. -- 1.0.539.verify.sql — HARD ASSERTIONS,每条 SELECT 都必须返回 1
  2. --
  3. -- ⚠️ AutoVersionUpdate 按分号切分本文件,并把【每一个切片】都当断言、要求返回 1。因此:
  4. -- ① 不得放"仅供排查"的 informational 查询(它返回的是结果集,不是 1)
  5. -- ② 文件必须以最后一条断言的分号结尾,末尾不能留纯注释块
  6. -- ③ 注释里不得出现任何字面分号,否则会被切在错误的位置
  7. -- 正因为 runner 只认「返回 1」,它没有"告警"这一档 —— 只阻断或不阻断。
  8. -- 故全局卫生类观测放在同目录的 1.0.539.warnings.ops.sql(runner 不执行,人工按需跑)。
  9. --
  10. -- ── 断言口径(重要,改本文件前先读)────────────────────────────────────────────────
  11. -- 只断言【本批 writer 实际控制的产出】,不断言全库状态。判据是「列的归属」:
  12. --
  13. -- 本批新建、且只由 S3 标准层 INSERT 写入 → BATCH INVARIANT,必须阻断
  14. -- · mdp_std_purchase_request.work_order
  15. -- · mdp_std_purchase_request.sales_order_entry_id
  16. -- · mdp_std_purchase_order.purchase_request_no
  17. --
  18. -- 本批之前就存在、且已确认存在本批边界外的写入者 → GLOBAL HYGIENE,只观测不阻断
  19. -- · mdp_std_purchase_order.work_order
  20. -- (本批仅做过一次性归一,稳态不归本批管。实测租户 838 的 S3_MDP_FULL 批次
  21. -- 在贴源侧同批次同 source_biz_key 归一结果为 NULL 的前提下,标准层仍出现
  22. -- 26 行字面量 —— 该值不可能由当前标准层 INSERT 在该批次产生,写入者未定位)
  23. -- · mdp_std_purchase_receipt.tenant_id
  24. -- (写入者已定位:MaterialWarehouse/PurchaseReceiptMdpSyncService.cs,
  25. -- 批次前缀 S5_PUR_RCT_STD_,属 S5 模块边界,见 CLAUDE.md 第九节模块边界表)
  26. --
  27. -- 这两项失败都不能证明本批身份桥失败 —— 身份桥完全不经过它们:
  28. -- PO.purchase_request_no → PR.pr_no → PR.sales_order_entry_id → mdp_std_so.order_entry_id
  29. --
  30. -- 注意 mdp_std_so 在 (tenant_id, order_entry_id) 上并不唯一(实测 25 组重复),
  31. -- 所以凡是经它取订单的断言一律用 DISTINCT,不用行数。
  32. -- ── ① PR 身份列已就位 ──────────────────────────────────────────────────────────────
  33. SELECT (SELECT COUNT(*) FROM `information_schema`.`COLUMNS`
  34. WHERE `TABLE_SCHEMA` = DATABASE() AND `TABLE_NAME` = 'mdp_std_purchase_request'
  35. AND `COLUMN_NAME` IN ('work_order','sales_order_entry_id')) = 2;
  36. SELECT (SELECT COUNT(*) FROM `information_schema`.`COLUMNS`
  37. WHERE `TABLE_SCHEMA` = DATABASE() AND `TABLE_NAME` = 'mdp_std_purchase_request'
  38. AND `COLUMN_NAME` = 'sales_order_entry_id'
  39. AND `DATA_TYPE` = 'bigint' AND `IS_NULLABLE` = 'YES') = 1;
  40. SELECT (SELECT COUNT(DISTINCT `INDEX_NAME`) FROM `information_schema`.`STATISTICS`
  41. WHERE `TABLE_SCHEMA` = DATABASE() AND `TABLE_NAME` = 'mdp_std_purchase_request'
  42. AND `INDEX_NAME` IN ('idx_std_pr_entry','idx_std_pr_wo')) = 2;
  43. -- ── ② PO → PR 指针已就位 ───────────────────────────────────────────────────────────
  44. SELECT (SELECT COUNT(*) FROM `information_schema`.`COLUMNS`
  45. WHERE `TABLE_SCHEMA` = DATABASE() AND `TABLE_NAME` = 'mdp_std_purchase_order'
  46. AND `COLUMN_NAME` = 'purchase_request_no' AND `IS_NULLABLE` = 'YES') = 1;
  47. SELECT (SELECT COUNT(*) FROM `information_schema`.`STATISTICS`
  48. WHERE `TABLE_SCHEMA` = DATABASE() AND `TABLE_NAME` = 'mdp_std_purchase_order'
  49. AND `INDEX_NAME` = 'idx_std_po_req') > 0;
  50. -- ── ③ 收货 → PO 身份键与索引已就位 ─────────────────────────────────────────────────
  51. SELECT (SELECT COUNT(*) FROM `information_schema`.`COLUMNS`
  52. WHERE `TABLE_SCHEMA` = DATABASE() AND `TABLE_NAME` = 'mdp_std_purchase_receipt'
  53. AND `COLUMN_NAME` IN ('ord_nbr','ord_line')) = 2;
  54. SELECT (SELECT COUNT(*) FROM `information_schema`.`STATISTICS`
  55. WHERE `TABLE_SCHEMA` = DATABASE() AND `TABLE_NAME` = 'mdp_std_purchase_receipt'
  56. AND `INDEX_NAME` = 'idx_std_rct_po') > 0;
  57. -- ── ④ S3 标准层自身不得有非法租户 ──────────────────────────────────────────────────
  58. -- 只覆盖 S3 转换独占写入的两张表。收货表有 S5 writer,见 warnings.ops.sql
  59. SELECT (SELECT COUNT(*) FROM `mdp_std_purchase_request`
  60. WHERE `tenant_id` IS NULL OR `tenant_id` <= 0) = 0;
  61. SELECT (SELECT COUNT(*) FROM `mdp_std_purchase_order`
  62. WHERE `tenant_id` IS NULL OR `tenant_id` <= 0) = 0;
  63. -- ── ⑤ sales_order_entry_id 有值时必须能在同租户的 mdp_std_so 里命中 ────────────────
  64. -- 0 也算非法:无法确定订单行就必须留 NULL,不得落 0
  65. SELECT (SELECT COUNT(*) FROM `mdp_std_purchase_request` p
  66. WHERE p.`sales_order_entry_id` IS NOT NULL
  67. AND (p.`sales_order_entry_id` <= 0
  68. OR NOT EXISTS (SELECT 1 FROM `mdp_std_so` s
  69. WHERE s.`tenant_id` = p.`tenant_id`
  70. AND s.`order_entry_id` = p.`sales_order_entry_id`))) = 0;
  71. -- ── ⑥ PO → PR 桥可跑通(全程只读 mdp_std_*)────────────────────────────────────────
  72. SELECT (SELECT COUNT(*) FROM `mdp_std_purchase_order` o
  73. JOIN `mdp_std_purchase_request` p
  74. ON p.`tenant_id` = o.`tenant_id` AND p.`pr_no` = o.`purchase_request_no`
  75. WHERE o.`purchase_request_no` IS NOT NULL) > 0;
  76. -- ── ⑦ PO → 销售订单 桥可跑通(全程只读 mdp_std_*,不触碰任何源业务表)──────────────
  77. SELECT (SELECT COUNT(DISTINCT s.`order_id`) FROM `mdp_std_purchase_order` o
  78. JOIN `mdp_std_purchase_request` p
  79. ON p.`tenant_id` = o.`tenant_id` AND p.`pr_no` = o.`purchase_request_no`
  80. JOIN `mdp_std_so` s
  81. ON s.`tenant_id` = p.`tenant_id` AND s.`order_entry_id` = p.`sales_order_entry_id`
  82. WHERE p.`sales_order_entry_id` IS NOT NULL) > 0;
  83. -- ── ⑧ 收货 → PO 桥可跑通(收货侧贴源无 Req,必须经 PO 才能到 PR)───────────────────
  84. SELECT (SELECT COUNT(*) FROM `mdp_std_purchase_receipt` r
  85. JOIN `mdp_std_purchase_order` o
  86. ON o.`tenant_id` = r.`tenant_id` AND o.`po_no` = r.`ord_nbr` AND o.`po_line` = r.`ord_line`) > 0;
  87. -- ── ⑨ 无跨租户命中 ─────────────────────────────────────────────────────────────────
  88. -- ⑤ 已保证"同租户能命中",这里补的是"绝不能靠别的租户命中"
  89. SELECT (SELECT COUNT(*) FROM `mdp_std_purchase_request` p
  90. JOIN `mdp_std_so` s ON s.`order_entry_id` = p.`sales_order_entry_id`
  91. WHERE p.`sales_order_entry_id` IS NOT NULL
  92. AND s.`tenant_id` <> p.`tenant_id`) = 0;
  93. SELECT (SELECT COUNT(*) FROM `mdp_std_purchase_order` o
  94. JOIN `mdp_std_purchase_request` p ON p.`pr_no` = o.`purchase_request_no`
  95. WHERE o.`purchase_request_no` IS NOT NULL
  96. AND p.`tenant_id` <> o.`tenant_id`
  97. AND NOT EXISTS (SELECT 1 FROM `mdp_std_purchase_request` q
  98. WHERE q.`tenant_id` = o.`tenant_id`
  99. AND q.`pr_no` = o.`purchase_request_no`)) = 0;
  100. -- ── ⑩ 本批 writer 独占的身份列不得出现字面量 'null' 或空串 ─────────────────────────
  101. -- 这三列全部由 1.0.539 新建,只有 S3 标准层 INSERT 写它们 → 属 BATCH INVARIANT
  102. SELECT (SELECT COUNT(*) FROM `mdp_std_purchase_request`
  103. WHERE `work_order` IN ('null', '')) = 0;
  104. SELECT (SELECT COUNT(*) FROM `mdp_std_purchase_order`
  105. WHERE `purchase_request_no` IN ('null', '')) = 0;
  106. -- ── ⑪ 归因 Grain 必须是 PO 行:一条 PO 行不得被归给多张销售订单 ─────────────────────
  107. SELECT (SELECT COUNT(*) FROM (
  108. SELECT o.`tenant_id`, o.`po_no`, o.`po_line`
  109. FROM `mdp_std_purchase_order` o
  110. JOIN `mdp_std_purchase_request` p
  111. ON p.`tenant_id` = o.`tenant_id` AND p.`pr_no` = o.`purchase_request_no`
  112. JOIN `mdp_std_so` s
  113. ON s.`tenant_id` = p.`tenant_id` AND s.`order_entry_id` = p.`sales_order_entry_id`
  114. WHERE p.`sales_order_entry_id` IS NOT NULL
  115. GROUP BY o.`tenant_id`, o.`po_no`, o.`po_line`
  116. HAVING COUNT(DISTINCT s.`order_id`) > 1) t) = 0;
  117. -- ── ⑫ 投影 / 派生正确性:本批三列必须逐行等于其定义式 ──────────────────────────────
  118. -- 这才是本批能担保的东西。刻意【不】断言"PO 的指针一定能找到 PR"——
  119. -- PR 行能否进标准层取决于转换既有的工厂谓词
  120. -- COALESCE(NULLIF(g.factory_id,0),1)=@FactoryId,与本批投影无关:
  121. -- 实测 PR20260903080128786 贴源 factory_id=838257186320453,被 @FactoryId=1 挡下,
  122. -- 而其 PO 行 factory_id=1 得以入库,于是指针悬空。那是既有作用域行为,
  123. -- 不是本批的投影缺陷,断言它等于替边界外逻辑背书。
  124. -- (a) PO.purchase_request_no 必须逐行等于贴源 $.Req 的归一值
  125. SELECT (SELECT COUNT(*) FROM `mdp_std_purchase_order` o
  126. JOIN `mdp_stg_purchase_order` g
  127. ON g.`tenant_id` = o.`tenant_id` AND g.`source_biz_key` = o.`source_biz_key`
  128. AND g.`source_table` = 'PurOrdDetail'
  129. WHERE IFNULL(o.`purchase_request_no`, '#')
  130. <> IFNULL(NULLIF(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(g.`raw_data`, '$.Req')), 'null'), ''), '#')) = 0;
  131. -- (b) PR.work_order 必须逐行等于贴源 $.pr_mono 的归一值
  132. SELECT (SELECT COUNT(*) FROM `mdp_std_purchase_request` p
  133. JOIN `mdp_stg_supply_demand` g
  134. ON g.`tenant_id` = p.`tenant_id` AND g.`source_biz_key` = p.`source_biz_key`
  135. AND g.`source_table` = 'srm_pr_main'
  136. WHERE IFNULL(p.`work_order`, '#')
  137. <> IFNULL(NULLIF(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(g.`raw_data`, '$.pr_mono')), 'null'), ''), '#')) = 0;
  138. -- (c) PR.sales_order_entry_id 必须逐行等于同租户工单桥的派生值,不得自行发明
  139. SELECT (SELECT COUNT(*) FROM `mdp_std_purchase_request` p
  140. LEFT JOIN `mdp_std_work_order_schedule` w
  141. ON w.`tenant_id` = p.`tenant_id` AND w.`work_order` = p.`work_order`
  142. WHERE p.`work_order` IS NOT NULL
  143. AND IFNULL(p.`sales_order_entry_id`, -1) <> IFNULL(w.`sales_order_entry_id`, -1)) = 0;
  144. -- (d) 没有工单号就不可能有订单行 Id —— 杜绝绕过工单桥的野路子赋值
  145. SELECT (SELECT COUNT(*) FROM `mdp_std_purchase_request`
  146. WHERE `work_order` IS NULL AND `sales_order_entry_id` IS NOT NULL) = 0;
  147. -- ── ⑬ 本批不得建 Stage-3 KPI 事实表 ────────────────────────────────────────────────
  148. SELECT (SELECT COUNT(*) FROM `information_schema`.`TABLES`
  149. WHERE `TABLE_SCHEMA` = DATABASE()
  150. AND `TABLE_NAME` IN ('dwd_procurement_kpi','dwd_purchase_kpi')) = 0;
  151. -- ── ⑭ 本批不得引入 Stage-3 完成事件 ────────────────────────────────────────────────
  152. SELECT (SELECT COUNT(*) FROM `aidop_action_run_log`
  153. WHERE `action_code` = 'S3_PROCUREMENT_COMPLETE') = 0;
  154. SELECT (SELECT COUNT(*) FROM `mdp_std_action_event`
  155. WHERE `action_code` = 'S3_PROCUREMENT_COMPLETE') = 0;
  156. -- ── ⑮ Stage 1 / Stage 2 未被本批触碰 ───────────────────────────────────────────────
  157. SELECT (SELECT COUNT(*) FROM `information_schema`.`TABLES`
  158. WHERE `TABLE_SCHEMA` = DATABASE()
  159. AND `TABLE_NAME` IN ('dwd_order_review_kpi','dwd_product_design_kpi')) = 2;
  160. SELECT (SELECT COUNT(*) FROM `dwd_order_review_kpi` k
  161. WHERE k.`tenant_id` IS NULL OR k.`tenant_id` <= 0
  162. OR NOT EXISTS (SELECT 1 FROM `mdp_std_so` s
  163. WHERE s.`tenant_id` = k.`tenant_id`
  164. AND s.`order_id` = k.`sales_order_id`)) = 0;
  165. SELECT (SELECT COUNT(*) FROM `dwd_product_design_kpi` p
  166. WHERE p.`tenant_id` IS NULL OR p.`tenant_id` <= 0
  167. OR NOT EXISTS (SELECT 1 FROM `mdp_std_so` s
  168. WHERE s.`tenant_id` = p.`tenant_id`
  169. AND s.`order_id` = p.`sales_order_id`)) = 0;