weaver-e10-builder 1.6.9 → 1.6.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. package/dist/commands/form-data.d.ts.map +1 -1
  2. package/dist/commands/form-data.js +4 -1
  3. package/dist/commands/form-data.js.map +1 -1
  4. package/dist/commands/report-admin.d.ts.map +1 -1
  5. package/dist/commands/report-admin.js +378 -2
  6. package/dist/commands/report-admin.js.map +1 -1
  7. package/dist/commands/report-validate.d.ts.map +1 -1
  8. package/dist/commands/report-validate.js +1066 -3
  9. package/dist/commands/report-validate.js.map +1 -1
  10. package/dist/commands/report.d.ts.map +1 -1
  11. package/dist/commands/report.js +341 -3
  12. package/dist/commands/report.js.map +1 -1
  13. package/dist/commands/workflow-after-submit-comment.d.ts +3 -0
  14. package/dist/commands/workflow-after-submit-comment.d.ts.map +1 -0
  15. package/dist/commands/workflow-after-submit-comment.js +56 -0
  16. package/dist/commands/workflow-after-submit-comment.js.map +1 -0
  17. package/dist/commands/workflow-setting.d.ts.map +1 -1
  18. package/dist/commands/workflow-setting.js +2 -67
  19. package/dist/commands/workflow-setting.js.map +1 -1
  20. package/dist/services/action-detail.d.ts.map +1 -1
  21. package/dist/services/action-detail.js +1 -17
  22. package/dist/services/action-detail.js.map +1 -1
  23. package/dist/services/actionflow/components/action_eb_update_compiler.d.ts.map +1 -1
  24. package/dist/services/actionflow/components/action_eb_update_compiler.js +6 -2
  25. package/dist/services/actionflow/components/action_eb_update_compiler.js.map +1 -1
  26. package/dist/services/actionflow/components/custom_trigger.js +1 -1
  27. package/dist/services/actionflow/components/custom_trigger.js.map +1 -1
  28. package/dist/services/actionflow/components/dml_execute.js +1 -1
  29. package/dist/services/actionflow/components/dml_execute.js.map +1 -1
  30. package/dist/services/actionflow/components/document_actions.js +1 -2
  31. package/dist/services/actionflow/components/document_actions.js.map +1 -1
  32. package/dist/services/actionflow/components/eb_get_data_by_id.d.ts.map +1 -1
  33. package/dist/services/actionflow/components/eb_get_data_by_id.js +2 -2
  34. package/dist/services/actionflow/components/eb_get_data_by_id.js.map +1 -1
  35. package/dist/services/actionflow/components/form_data_builder.d.ts.map +1 -1
  36. package/dist/services/actionflow/components/form_data_builder.js +18 -10
  37. package/dist/services/actionflow/components/form_data_builder.js.map +1 -1
  38. package/dist/services/actionflow/components/hrm_actions.js +0 -1
  39. package/dist/services/actionflow/components/hrm_actions.js.map +1 -1
  40. package/dist/services/actionflow/components/webhook_trigger.js +3 -3
  41. package/dist/services/actionflow/components/webhook_trigger.js.map +1 -1
  42. package/dist/services/actionflow/components/workflow_actions.js +3 -2
  43. package/dist/services/actionflow/components/workflow_actions.js.map +1 -1
  44. package/dist/services/actionflow/filter_rules.d.ts.map +1 -1
  45. package/dist/services/actionflow/filter_rules.js +15 -13
  46. package/dist/services/actionflow/filter_rules.js.map +1 -1
  47. package/dist/services/actionflow/form_source.d.ts.map +1 -1
  48. package/dist/services/actionflow/form_source.js +59 -11
  49. package/dist/services/actionflow/form_source.js.map +1 -1
  50. package/dist/services/actionflow/formula_service.d.ts.map +1 -1
  51. package/dist/services/actionflow/formula_service.js +4 -1
  52. package/dist/services/actionflow/formula_service.js.map +1 -1
  53. package/dist/services/actionflow/option_field_types.d.ts +5 -0
  54. package/dist/services/actionflow/option_field_types.d.ts.map +1 -0
  55. package/dist/services/actionflow/option_field_types.js +20 -0
  56. package/dist/services/actionflow/option_field_types.js.map +1 -0
  57. package/dist/services/actionflow/param_group_dto.d.ts.map +1 -1
  58. package/dist/services/actionflow/param_group_dto.js +7 -3
  59. package/dist/services/actionflow/param_group_dto.js.map +1 -1
  60. package/dist/services/actionflow/preflight_service.d.ts.map +1 -1
  61. package/dist/services/actionflow/preflight_service.js +42 -18
  62. package/dist/services/actionflow/preflight_service.js.map +1 -1
  63. package/dist/services/actionflow/webhook_source.d.ts.map +1 -1
  64. package/dist/services/actionflow/webhook_source.js +9 -4
  65. package/dist/services/actionflow/webhook_source.js.map +1 -1
  66. package/dist/services/actionflow/workflow_compiler.d.ts.map +1 -1
  67. package/dist/services/actionflow/workflow_compiler.js +11 -8
  68. package/dist/services/actionflow/workflow_compiler.js.map +1 -1
  69. package/dist/services/report/blueprint.d.ts +109 -0
  70. package/dist/services/report/blueprint.d.ts.map +1 -0
  71. package/dist/services/report/blueprint.js +322 -0
  72. package/dist/services/report/blueprint.js.map +1 -0
  73. package/dist/services/report/build.d.ts.map +1 -1
  74. package/dist/services/report/build.js +8 -3
  75. package/dist/services/report/build.js.map +1 -1
  76. package/dist/services/report/card.d.ts +65 -0
  77. package/dist/services/report/card.d.ts.map +1 -0
  78. package/dist/services/report/card.js +151 -0
  79. package/dist/services/report/card.js.map +1 -0
  80. package/dist/services/report/copy-health.d.ts +31 -0
  81. package/dist/services/report/copy-health.d.ts.map +1 -0
  82. package/dist/services/report/copy-health.js +87 -0
  83. package/dist/services/report/copy-health.js.map +1 -0
  84. package/dist/services/report/dataset-heal.d.ts +64 -0
  85. package/dist/services/report/dataset-heal.d.ts.map +1 -0
  86. package/dist/services/report/dataset-heal.js +89 -0
  87. package/dist/services/report/dataset-heal.js.map +1 -0
  88. package/dist/services/report/dataset-ids.d.ts +70 -0
  89. package/dist/services/report/dataset-ids.d.ts.map +1 -0
  90. package/dist/services/report/dataset-ids.js +149 -0
  91. package/dist/services/report/dataset-ids.js.map +1 -0
  92. package/dist/services/report/dataset.d.ts.map +1 -1
  93. package/dist/services/report/dataset.js +3 -1
  94. package/dist/services/report/dataset.js.map +1 -1
  95. package/dist/services/report/empty-dataset.d.ts +51 -0
  96. package/dist/services/report/empty-dataset.d.ts.map +1 -0
  97. package/dist/services/report/empty-dataset.js +213 -0
  98. package/dist/services/report/empty-dataset.js.map +1 -0
  99. package/dist/services/report/field-binding.d.ts +75 -0
  100. package/dist/services/report/field-binding.d.ts.map +1 -0
  101. package/dist/services/report/field-binding.js +160 -0
  102. package/dist/services/report/field-binding.js.map +1 -0
  103. package/dist/services/report/forms.d.ts +32 -0
  104. package/dist/services/report/forms.d.ts.map +1 -1
  105. package/dist/services/report/forms.js +90 -0
  106. package/dist/services/report/forms.js.map +1 -1
  107. package/dist/services/report/formula-parent.d.ts +25 -0
  108. package/dist/services/report/formula-parent.d.ts.map +1 -0
  109. package/dist/services/report/formula-parent.js +116 -0
  110. package/dist/services/report/formula-parent.js.map +1 -0
  111. package/dist/services/report/locate.d.ts +40 -0
  112. package/dist/services/report/locate.d.ts.map +1 -0
  113. package/dist/services/report/locate.js +122 -0
  114. package/dist/services/report/locate.js.map +1 -0
  115. package/dist/services/report/reports.d.ts +4 -0
  116. package/dist/services/report/reports.d.ts.map +1 -1
  117. package/dist/services/report/reports.js +4 -2
  118. package/dist/services/report/reports.js.map +1 -1
  119. package/dist/services/report/scene.d.ts +22 -0
  120. package/dist/services/report/scene.d.ts.map +1 -0
  121. package/dist/services/report/scene.js +145 -0
  122. package/dist/services/report/scene.js.map +1 -0
  123. package/dist/services/report/types.d.ts +28 -0
  124. package/dist/services/report/types.d.ts.map +1 -1
  125. package/dist/services/report/verify.d.ts +24 -0
  126. package/dist/services/report/verify.d.ts.map +1 -1
  127. package/dist/services/report/verify.js +115 -2
  128. package/dist/services/report/verify.js.map +1 -1
  129. package/dist/services/report/workbook.d.ts +12 -1
  130. package/dist/services/report/workbook.d.ts.map +1 -1
  131. package/dist/services/report/workbook.js +107 -8
  132. package/dist/services/report/workbook.js.map +1 -1
  133. package/dist/services/workflow-after-submit-comment.d.ts +20 -0
  134. package/dist/services/workflow-after-submit-comment.d.ts.map +1 -0
  135. package/dist/services/workflow-after-submit-comment.js +81 -0
  136. package/dist/services/workflow-after-submit-comment.js.map +1 -0
  137. package/ecodecmd/src/build.js +36 -12
  138. package/package.json +1 -1
  139. package/skill-docs/SKILL.md +4 -4
  140. package/skill-docs/_catalog.md +10 -1
  141. package/skill-docs/actionflows/SKILL.md +1 -0
  142. package/skill-docs/actionflows/references/components/ebuilder/EnquiryForm.md +2 -2
  143. package/skill-docs/actionflows/references/components/trigger/CustomTrigger.md +2 -0
  144. package/skill-docs/actionflows/references/components/trigger/WebHookTrigger.md +19 -1
  145. package/skill-docs/references/coding/guide-form-crud.md +2 -0
  146. package/skill-docs/references/coding/guide-page-code.md +108 -0
  147. package/skill-docs/references/components/ref-component-common.md +98 -0
  148. package/skill-docs/references/report/add-dataset-registration-guide.md +44 -22
  149. package/skill-docs/references/report/advanced-crosstab-tree-guide.md +82 -0
  150. package/skill-docs/references/report/ai-report-workflow.md +100 -100
  151. package/skill-docs/references/report/api-auth-headers.md +129 -0
  152. package/skill-docs/references/report/api-report-build-fast-path.md +274 -275
  153. package/skill-docs/references/report/app-datasource-lookup-guide.md +230 -230
  154. package/skill-docs/references/report/branch-scene-guide.md +293 -0
  155. package/skill-docs/references/report/build-resilience-guide.md +99 -0
  156. package/skill-docs/references/report/card-report-guide.md +110 -0
  157. package/skill-docs/references/report/cell-config-echo-guide.md +44 -9
  158. package/skill-docs/references/report/cell-transform-guide.md +9 -9
  159. package/skill-docs/references/report/column-split-report-guide.md +96 -0
  160. package/skill-docs/references/report/condition-format-guide.md +49 -24
  161. package/skill-docs/references/report/cross-dataset-filter-guide.md +180 -69
  162. package/skill-docs/references/report/crosstab-raw-workbook-guide.md +265 -0
  163. package/skill-docs/references/report/dataset-query-filter-guide.md +231 -235
  164. package/skill-docs/references/report/dataset-sidebar-ops-guide.md +75 -34
  165. package/skill-docs/references/report/dataset-table-field-contract-guide.md +196 -46
  166. package/skill-docs/references/report/datasource-metadata-guide.md +128 -119
  167. package/skill-docs/references/report/datasource-selection-and-parent-filter-guide.md +183 -148
  168. package/skill-docs/references/report/date-range-query-reference.md +97 -102
  169. package/skill-docs/references/report/default-header-style-guide.md +8 -8
  170. package/skill-docs/references/report/designer-blank-prevention-guide.md +8 -8
  171. package/skill-docs/references/report/dossier-report-guide.md +69 -0
  172. package/skill-docs/references/report/drilldown-report-guide.md +59 -0
  173. package/skill-docs/references/report/event-hreflink-guide.md +53 -47
  174. package/skill-docs/references/report/expansion-block-parent-config-guide.md +208 -0
  175. package/skill-docs/references/report/failure-cleanup-and-nonblocking-guide.md +183 -0
  176. package/skill-docs/references/report/field-bind-exact-match-guide.md +208 -208
  177. package/skill-docs/references/report/financial-stats-report-guide.md +89 -0
  178. package/skill-docs/references/report/formula-cell-api-guide.md +325 -258
  179. package/skill-docs/references/report/golden-case-summary-sheet.md +200 -0
  180. package/skill-docs/references/report/guide-report-build.md +6 -1
  181. package/skill-docs/references/report/hidden-row-and-header-merge-guide.md +10 -10
  182. package/skill-docs/references/report/layout-showlocation-freeze-guide.md +39 -10
  183. package/skill-docs/references/report/multi-report-crosstab-casebook.md +263 -0
  184. package/skill-docs/references/report/paged-group-report-guide.md +68 -0
  185. package/skill-docs/references/report/preaggregate-auto-disable-guide.md +24 -13
  186. package/skill-docs/references/report/query-condition-api-guide.md +145 -125
  187. package/skill-docs/references/report/ref-report-commands.md +41 -3
  188. package/skill-docs/references/report/report-baseinfo-defaults-guide.md +117 -117
  189. package/skill-docs/references/report/report-building-guide.md +52 -52
  190. package/skill-docs/references/report/report-case-prompts.md +104 -0
  191. package/skill-docs/references/report/report-config-schema.md +281 -281
  192. package/skill-docs/references/report/report-create-modes-guide.md +73 -35
  193. package/skill-docs/references/report/report-data-model.md +131 -131
  194. package/skill-docs/references/report/report-delivery-contract.md +23 -3
  195. package/skill-docs/references/report/report-designer-feature-map.md +30 -35
  196. package/skill-docs/references/report/report-help-guide.md +304 -0
  197. package/skill-docs/references/report/report-internal-error-guide.md +71 -0
  198. package/skill-docs/references/report/report-knowledge-base.md +27 -27
  199. package/skill-docs/references/report/report-lifecycle-api-guide.md +126 -94
  200. package/skill-docs/references/report/report-list-page-ops-guide.md +54 -8
  201. package/skill-docs/references/report/report-permission-guide.md +99 -102
  202. package/skill-docs/references/report/report-requirement-analysis-template.md +93 -29
  203. package/skill-docs/references/report/report-self-check-guide.md +164 -164
  204. package/skill-docs/references/report/report-verification-discipline-guide.md +203 -0
  205. package/skill-docs/references/report/safe-partial-save-guide.md +151 -151
  206. package/skill-docs/references/report/shard-freeze-report-guide.md +97 -0
  207. package/skill-docs/references/report/snowflake-id-precision-guide.md +84 -84
  208. package/skill-docs/references/report/spec-build-schema.md +71 -71
  209. package/skill-docs/references/report/targeted-fix-guide.md +155 -97
  210. package/skill-docs/references/report/user-request-scope-only-guide.md +23 -4
  211. package/skill-docs/references/workflow/guide-after-submit-comment.md +58 -0
  212. package/skill-docs/references/workflow/guide-workflow.md +6 -0
@@ -0,0 +1,183 @@
1
+ # 失败清理与非阻塞执行(强制)
2
+
3
+ > **适用**:API/脚本建表、改查询、改布局等全过程。
4
+ > **目标**:① 失败/中止不留下空壳报表;② 单点失败不卡死整条流水线。
5
+
6
+ ---
7
+
8
+ ## 一、空壳报表必须删除(强制)
9
+
10
+ ### 1.1 何谓空壳 / 废壳
11
+
12
+ | 情形 | 判定 |
13
+ |------|------|
14
+ | 已 `baseInfo` 建壳,但 **`saveReportDevInfo` 未成功** 或 fields 为空 | **空壳** |
15
+ | 临时名 `__build_*` / `__probe_*` / `__bench_*` 且任务失败或中止 | **废壳** |
16
+ | 同任务多次失败留下的多个半成品 reportId | **废壳(全部清)** |
17
+ | 布局已成功落库、仅查询条件等附属能力失败 | **不是空壳** → **保留**报表,只提示附属失败 |
18
+
19
+ ### 1.2 何时必须删
20
+
21
+ 出现以下**任一**情况,在收尾或换路径前 **立刻**删除(进回收站即可):
22
+
23
+ 1. 命令/脚本 **失败**(exit ≠ 0、save 500、抛错未交付可用表)
24
+ 2. 用户 **中止** / Agent 放弃本轮建表
25
+ 3. 决定 **删壳重建**(新 reportId)之前,旧失败壳必须先删
26
+ 4. 探测用临时壳(`__probe_*`)用完必须删
27
+
28
+ **优先 CLI**:
29
+
30
+ ```bash
31
+ e10-cli report cleanup shells --app-id <appId> --report-id <reportId>
32
+ # 或临时名前缀:
33
+ e10-cli report cleanup shells --app-id <appId> --prefix __build_
34
+ ```
35
+
36
+ 等价 API:
37
+
38
+ ```
39
+ POST 或 GET /api/bs/edc/report/deleteReport
40
+ id={reportId} # 可逗号分隔批量
41
+ + Header ebBusinessId={appId}
42
+ ```
43
+
44
+ 见 `report-lifecycle-api-guide.md` §删除。
45
+
46
+ ### 1.3 执行纪律
47
+
48
+ | 规则 | 说明 |
49
+ |------|------|
50
+ | **跟踪 reportId** | 建壳成功后立刻记下 `reportId`;`finally`/catch 中若未交付成功布局 → 删除 |
51
+ | **禁止留页面垃圾** | 失败后列表页不应残留「点进去空白/无字段」的报表 |
52
+ | **告知用户** | 若已删:一句话说明「已删除失败空壳 {name/id}」;若附属失败保留主表:说明「主表已交,XX 未配上请确认」 |
53
+ | **禁止** | 失败后不管、叠多个 `__build_*`;或把空壳改成目标名交付 |
54
+
55
+ ```javascript
56
+ let reportId = null;
57
+ let layoutOk = false;
58
+ try {
59
+ reportId = await createShell(tempName);
60
+ // ... addDataset / saveReportDevInfo ...
61
+ layoutOk = true;
62
+ await rename(reportId, targetName);
63
+ } catch (e) {
64
+ // 提示用户,勿死循环
65
+ } finally {
66
+ if (reportId && !layoutOk) {
67
+ await deleteReport(reportId); // 空壳必删
68
+ }
69
+ }
70
+ ```
71
+
72
+ ---
73
+
74
+ ## 二、单壳搭建(强制 · 一次任务只允许 1 个报表壳)
75
+
76
+ > **用户强制要求(2026-09-14)**:搭建报表**不要搭多个模板空壳报表**。
77
+ > 现象:一次搭建里为每个变体/每次试验各建一个壳(`__probe_*`、`__regtest_*`、`__build_a/__build_b/...`),
78
+ > 结果应用列表页堆一堆半成品,既难维护又违反交付契约。
79
+
80
+ ### 2.1 铁律
81
+
82
+ | # | 规则 |
83
+ |---|------|
84
+ | 1 | **一次搭建任务从头到尾只建 1 个报表壳**,命名 `__build_<业务>_<时间戳>`,全程复用(失败重试、变体试验、布局迭代都改同一个壳) |
85
+ | 2 | **禁止**为「试 A 方案 / 试 B 方案」各建一个壳。变体 = 同一个壳内改单元格后重新 `saveReportDevInfo` |
86
+ | 3 | **禁止**为探针建报表壳。只读回读即可满足 90% 探针需求:<br>`getReportDevInfo.fields`(结构/字段/父格)、`GET /api/bs/edc/dataset/list?reportId=`(数据集)、`fieldsAndOriginFields`(字段映射)、`getQueryItemNew`(查询条件) |
87
+ | 4 | 仅在「必须验证某个**写**接口行为」时才允许临时壳,命名 `__probe_*`,且**同一时刻最多 1 个、用完立即删** |
88
+ | 5 | 主路径成功 → **立刻把唯一那个壳改名为目标名**并交付;主路径失败 → 删掉它(见 §一),**不留待下次** |
89
+ | 6 | 交付时向用户交代的 reportId 清单必须**恰好 1 个**(最终表) |
90
+
91
+ ### 2.2 收尾自查(只读,不改任何东西)
92
+
93
+ ```bash
94
+ # 全应用空壳体检:列出每张报表的 fields 数,标出 fields=0 的空壳
95
+ e10-cli report cleanup shells --app-id <appId> --list
96
+
97
+ # 确认没有遗留的建表壳
98
+ e10-cli report cleanup shells --app-id <appId> --prefix __build_ --dry-run
99
+
100
+ # 确认没有遗留的探针壳
101
+ e10-cli report cleanup shells --app-id <appId> --prefix __probe_ --dry-run
102
+ ```
103
+
104
+ `--list` / `--dry-run` 为**只读**模式,只输出 JSON 结论(`verdict: ✅ 无空壳报表`),不会删除任何报表。
105
+
106
+ ### 2.3 报告壳 ≠ 数据集
107
+
108
+ 「空壳报表」是**报表级**残留;报表内**挂错/多余的 dataset 条目**同样算残留,须按 §四清理。
109
+
110
+ ---
111
+
112
+ ## 三、报表内多余数据集清理(强制)
113
+
114
+ 一次 `addDataset` 失败重试、或注册形态返工,很容易在同一张报表上**挂多套数据集**(设计器左侧出现重复数据源)。判定与清理:
115
+
116
+ | 步骤 | 做法 |
117
+ |------|------|
118
+ | ① 数引用 | `getReportDevInfo` → 按 `fields[].dataSetId` 分组计数,**计数 = 0 的即为残留** |
119
+ | ② 占用检查 | `GET /api/bs/edc/dataset/checkDataSetUsed?datasetId=<dsId>&reportId=<rid>`,`data=false` = 安全 |
120
+ | ③ 删除 | `POST /api/bs/edc/report/delDataset`,**multipart 参数是 `reportId` + `sourceIds`**,其中 **`sourceIds` 传的值是 `datasetId`(不是 sourceId!)** |
121
+ | ④ 豁免 | 系统默认数据集 `dataUpdateTime` / `数据更新时间` **禁止删除** |
122
+ | ⑤ 回归 | 删完再跑 `e10-cli report verify all <reportId> <appId>`,确认 `fields` 数不变、绑定 PASS |
123
+
124
+ > ⚠️ 常见坑:`delDataset` 用 `datasetId` / `id` / `sheetId` 作参数名一律 **400 Bad Request**;正确键名只有 `sourceIds`。
125
+
126
+ ---
127
+
128
+ ## 四、单点失败不卡死(强制 · 非阻塞)
129
+
130
+ ### 4.1 原则
131
+
132
+ | 能力分层 | 失败时 |
133
+ |----------|--------|
134
+ | **主路径**(找表、建壳、绑数据集、布局 save) | 失败 → 清理空壳 → **提示用户** → 可问清后重试;**禁止**同点无限重试 |
135
+ | **附属路径**(查询条件、权限、快照、预聚合、HTML 手册、视觉核对) | 失败 → **提示用户自行确认/可后补** → **继续其它步骤** → 交付主成果 → **末尾再尝试自动修复 1 次** |
136
+
137
+ **禁止**:在 `saveQueryItem` / 某次 options / 某次 verify 上连试 5~10 轮导致整任务卡死。
138
+
139
+ ### 4.2 重试上限
140
+
141
+ | 类型 | 上限 | 之后 |
142
+ |------|------|------|
143
+ | 同一 API/同一步 | **1 次重试**(共 2 次尝试) | 记入「待用户确认 / 待收尾补丁」清单,**跳过** |
144
+ | 找表探测 | 见 `app-datasource-lookup-guide.md` §六预算 | **打断询问** |
145
+ | 布局 save | 1 次修正后仍失败 | 删壳,提示用户,停止建表 |
146
+
147
+ ### 4.3 提示话术(简短)
148
+
149
+ 附属失败示例:
150
+
151
+ > 查询条件写入失败(`saveQueryItem` 返回 500/超时)。报表主体已保存,请你在设计器「查询设置」中确认或告知正确条件字段;我先继续其它配置,最后再自动补试一次。
152
+
153
+ 主路径失败示例:
154
+
155
+ > 布局保存失败,已删除空壳报表 `{临时名}`(`{reportId}`)。请确认应用/数据源是否正确,或提供可用的 sourceId 后我再重建。
156
+
157
+ ### 4.4 推荐流水线顺序
158
+
159
+ ```
160
+ 1. 主路径:找表 → 建壳 → 布局落库 → 【成功则立刻交设计器地址】
161
+ 2. 附属:查询条件 / 权限 / …(任一步失败 → 提示 + 跳过)
162
+ 3. 收尾:对「跳过清单」各自动补试 ≤1 次;仍失败 → 汇总给用户,不阻塞结束
163
+ 4. finally:若主路径未成功 → deleteReport 空壳
164
+ ```
165
+
166
+ ### 4.5 与查询条件 500
167
+
168
+ 见 `query-condition-api-guide.md` §二点五:先按调用形态自检 **1 次**;仍失败 → **提示用户 + 继续其它工作**,禁止卡在 saveQueryItem。
169
+
170
+ ---
171
+
172
+ ## 五、与现有文档
173
+
174
+ | 文档 | 关系 |
175
+ |------|------|
176
+ | `api-report-build-fast-path.md` | 建表纪律引用本节 |
177
+ | `report-delivery-contract.md` | 主路径成功才交链接;附属失败不挡交付 |
178
+ | `report-lifecycle-api-guide.md` | deleteReport |
179
+ | `app-datasource-lookup-guide.md` §六 | 找表预算与打断 |
180
+ | `query-condition-api-guide.md` §二点五 | 查询 500 不卡死 |
181
+ | `targeted-fix-guide.md` | 定点改;失败同样清理废壳 |
182
+ | `add-dataset-registration-guide.md` | §三「报表内多余数据集清理」的注册形态背景 |
183
+ | `report-data-model.md` | 数据集 / 单元格 `dataSetId` 引用模型 |
@@ -1,208 +1,208 @@
1
- # 字段精确绑定(强制 · 禁止匹配失败)
2
-
3
- > **事故案例(已验证)**:报表 `AAA`(`1312677851392417792`)保存了 9 个 Field 格,但 `name` / `fieldKey` 末段写成裸雪花 ID `3506271516100878845`,而数据源真实字段名是 `field_3506271516100878845` → **设计器有字段格、取数全部匹配失败(字段获取不上)**。
4
- > **前置事故(已验证 · `1312765120721813505`)**:Agent 用错 `addDataset`(`type=form` + `sourceGroup=appId`)时,`fieldsKey` **本身只有裸雪花**;用户在设计器手动「+」同表则得到 `field_*`。
5
- > **遗漏点**:不是写 Field 时忘加前缀,而是 **注册契约与设计器不一致** —— 必先读 **`add-dataset-registration-guide.md`**,把数据集加成 LOGIC + `weaver-ebuilder-form-service`,再绑定。
6
- > **唯一标准**:`name` / `fieldKey` 末段必须 **精确等于** 当前数据集 `fieldsAndOriginFields.fieldsKey[].name`(以接口回读为准)。
7
- > **禁止**:注册错了却在 cell 上「手工补 `field_`」;或对正确的 `field_*` fieldsKey 去前缀。
8
- > **硬目标**:任意 Field 的 `name` **必须**等于该 dataset 的 `fieldsKey[].name`;**零容忍**猜测、截断、错数据集。
9
-
10
- ---
11
-
12
- ## 〇、先保证 fieldsKey 带 `field_`(普通应用)
13
-
14
- | 检查 | 说明 |
15
- |------|------|
16
- | addDataset | 默认 `sourceType=LOGIC` + `sourceGroup=weaver-ebuilder-form-service` |
17
- | 门禁 | 业务字段若多为裸雪花 → **注册错误**,`delDataset` 重建;见 `add-dataset-registration-guide.md` |
18
- | 同 sourceId 多实例 | **优先绑**设计器/LOGIC 那条(`field_*`),勿绑旧 form+appId 实例 |
19
-
20
- ---
21
-
22
- ## 一、唯一合法字段名来源
23
-
24
- ```
25
- GET /api/bs/edc/dataset/fieldsAndOriginFields
26
- ?datasetId={datasetId}&hasField=true&isFront=true&reportId={reportId}
27
- → data.fieldsKey[] // 权威列表;isFront=false 常为空,禁止依赖
28
- ```
29
-
30
- 每个 `fieldsKey` 项的关键属性:
31
-
32
- | 属性 | 用途 | 示例 |
33
- |------|------|------|
34
- | **`name`** | **写入 Field 的 `name` / fieldKey 末段(唯一正确)** | `field_3506271516100878845`、`create_time` |
35
- | `text` / `showName` | 仅用于按中文显示名**查找**对应 `name` | `资产类别` |
36
- | `originName` | 参考,不可直接当 fieldKey 末段(除非与 `name` 相同) | — |
37
-
38
- **禁止**从下列来源臆造 `name`:
39
-
40
- | 错误来源 | 错误示例 | 后果 |
41
- |----------|----------|------|
42
- | 表单字段 ID / 雪花 ID(无 `field_`) | `3506271516100878845` | **取数匹配失败**(本案) |
43
- | 显示名 | `资产类别` | 设计器/取数均失败 |
44
- | 截断 / 半截 | `field_5`、`field_3506` | 匹配失败 |
45
- | 去掉 `field_` 前缀 | 把 `field_xxx` 写成 `xxx` | **取数匹配失败** |
46
- | 外部字段表 fieldId 未与 fieldsKey 交叉核对 | 形态可能不同 | 须与 fieldsKey.name 对齐后再用 |
47
-
48
- ---
49
-
50
- ## 二、绑定算法(强制)
51
-
52
- > **⚠️ 2026-09-11 追加门禁(事故:报表 `1313789294296735745`)**:B3 列文案「姓名」,但该表单**没有「姓名」字段**,
53
- > 旧逻辑静默兜底到系统字段 `name`(真实显示名「标题」)→ 单元格文案与绑定字段语义不符、属性面板回显对不上。
54
- > **新规则:精确匹配失败必须 `exit 4` 提示用户指定字段,禁止任何自动兜底(尤其禁止回退系统字段)。**
55
- > 落地实现:`spec_lib.resolveFieldBindings()` + `e10-cli report build` 的**建表前预检**。
56
-
57
- 对需求中的每一列(显示名 → 数据字段):
58
-
59
- ### 2.0 允许的匹配(命中即止,**全为精确匹配**)
60
-
61
- | 顺序 | 依据 | 说明 |
62
- |------|------|------|
63
- | 1 | `column.fieldName` / `spec.fieldMap[列标题]` | 用户/Agent 显式指定(可为 `field_xxx`、显示名、系统字段名) |
64
- | 2 | 列标题 === `fieldsKey[].text/showName`(归一化:去空白、全角括号→半角) | 主路径 |
65
- | 3 | `column.field` === `text/showName` 或 `name` | 兼容旧 spec |
66
- | 4 | 都不中 | **`unmatched` → exit 4**(附模糊候选供用户挑选) |
67
-
68
- **禁止**:包含/被包含(`includes`)等模糊规则自动绑定;**禁止**把 `name`/`id`/`create_time` 等系统字段当作兜底。
69
-
70
- ### 2.0.1 规则 A · 找不到字段 → **停下让用户指定**(强制 · 2026-09-11)
71
-
72
- > **核心**:需求里的字段在当前数据集表单里**找不到**时,**不许猜、不许兜底**,必须停下把候选交给用户。
73
-
74
- `resolveFieldBindings()` 的 `unmatched[]` 三种 `reason` 及处置:
75
-
76
- | `reason` | 含义 | 退出码 | 处置 |
77
- |----------|------|--------|------|
78
- | `no_exact_match` | 列标题在 `fieldsKey.text` 中**无精确命中** | **4** | 把 `candidates` + `allFields` 抛给用户,请其指定字段 |
79
- | `explicit_not_found` | 显式 `fieldName`/`fieldMap` 指向的字段**不存在** | **4** | 同上(禁止臆造字段名) |
80
- | `display_mismatch` | 能绑到,但字段**真实显示名 ≠ 列文案**且未确认 | **4** | 用户确认列名有意如此 → `fieldConfirmed` / `--field-confirmed "列标题"` |
81
-
82
- **两阶段拦截**(都不建表 / 删未完成表):
83
-
84
- | 阶段 | 时机 | 未通过 |
85
- |------|------|--------|
86
- | 预检 | `fieldsBySource`(**建表前**) | **exit 4 · 未创建任何报表** |
87
- | 复校 | 本表 `addDataset` → `fieldsKey`(落库前) | **exit 4 · 删除本轮未完成报表** |
88
-
89
- 输出结构(`rule:"A"`):
90
-
91
- ```json
92
- {
93
- "action": "confirm_field", "rule": "A", "needConfirm": true, "reportCreated": false,
94
- "unmatched": [ { "column": "姓名", "reason": "no_exact_match", "prompt": "…请用户从 allFields/candidates 中指定字段",
95
- "candidates": [ { "name": "field_...", "text": "发展人", "system": false } ] } ],
96
- "allFields": [ { "name": "field_...", "text": "发展人", "system": false } ],
97
- "next": { "specFields": { "fieldMap": { "<列标题>": "<字段显示名 | field_雪花 | 系统字段名>" } } }
98
- }
99
- ```
100
-
101
- 用户指定后复跑:`spec.fieldMap = { "姓名": "发展人" }`(或 `field_xxx`)。
102
- 若列名坚持与字段显示名不同 → 另加 `fieldConfirmed: ["姓名"]` / CLI `--field-confirmed "姓名"`。
103
-
104
- **禁止**:
105
- - 用「最相近的」字段顶上(哪怕语义看似接近);
106
- - 回退系统字段(`name`→「标题」是历史事故);
107
- - 先带病落库、事后再让用户改。
108
-
109
- > 一页速览:`e10-cli report --help`。
110
-
111
- ```javascript
112
- // 1. 拉权威列表
113
- const fieldsKey = (await fieldsAndOriginFields(...)).data.fieldsKey;
114
-
115
- // 2. 解析(库函数,已含门禁)
116
- const plan = resolveFieldBindings(fieldsKey, spec.columns, { fieldMap, fieldConfirmed });
117
- if (!plan.ok) {
118
- // exit 4:把每列 candidates 抛给用户,等其指定字段;不要自己猜
119
- }
120
- const nf = plan.bindings.find(b => b.index === colIndex).field;
121
- ```
122
-
123
- **规则摘要**:
124
-
125
- 1. **先** `fieldsAndOriginFields`,**再**写任何 Field 单元格
126
- 2. `name` **逐字等于** `fieldsKey[].name`(含 `field_` 前缀;系统字段如 `create_time` 原样)
127
- 3. `fieldKey` = `{datasetId}.{sourceId}.{fieldsKey.name}`,三段齐全
128
- 4. 保存前对**每一个** Field 做 `byName.has(name)`;任一失败 → **中止保存**,禁止带病落库
129
-
130
- ---
131
-
132
- ## 三、保存后强制回读校验(交付门禁)
133
-
134
- `getReportDevInfo` 后,对每个 `cellType === 'Field'`:
135
-
136
- ```javascript
137
- function assertFieldBindExact(devFields, fieldsKey) {
138
- const legal = new Set(fieldsKey.map(f => f.name));
139
- const fieldCells = devFields.filter(f => f.cellType === 'Field');
140
- if (!fieldCells.length) throw new Error('无 Field 单元格');
141
- for (const f of fieldCells) {
142
- const last = String(f.fieldKey || '').split('.')[2] || '';
143
- if (!f.name || !legal.has(f.name)) {
144
- throw new Error(`字段匹配失败 name=${f.name} text=${f.text}`);
145
- }
146
- if (last !== f.name || !legal.has(last)) {
147
- throw new Error(`fieldKey 末段不在 fieldsKey: ${f.fieldKey}`);
148
- }
149
- // 裸雪花:若 fieldsKey 里已有 field_xxx,则禁止用裸名;否则先查 addDataset 是否注册错
150
- }
151
- }
152
- ```
153
-
154
- | 检查 | 失败即 FAIL |
155
- |------|-------------|
156
- | 每个 Field 的 `name ∈ fieldsKey.name` | ✅ |
157
- | `fieldKey` 末段 === `name` | ✅ |
158
- | 普通应用:fieldsKey 业务字段含 `field_*`(注册门禁) | ✅ 见 `add-dataset-registration-guide.md` |
159
- | Field 数量 ≥ 需求列数 | ✅ |
160
- | **单元格文案 === 绑定字段真实显示名**(否则须用户确认) | ⚠ `verify bind` 列出 `display_mismatch` |
161
- | **同 sourceId 只挂 1 个数据集**(否则左侧重复分组) | ⚠ `verify bind` 列出 `duplicate_dataset_mount` |
162
-
163
- `e10-cli report verify bind <reportId> [appId] [--strict]`:门禁失败 exit 1;`--strict` 时「待用户确认项」exit 2。
164
-
165
- **未通过校验 → 须修正并告知用户**;可与「已交付的设计器链接」并行:先给链接(见 `report-delivery-contract.md` §〇),校验失败再补丁修复,**禁止**为等校验而拖延首轮地址。
166
-
167
- ---
168
-
169
- ## 四、故障对照
170
-
171
- | 现象 | 根因 | 处理 |
172
- |------|------|------|
173
- | 设计器有 Field 格,预览/取数无数据或字段空白 | `name` 为裸 ID,缺 `field_` | 按 fieldsKey.name 重绑并 saveReportDevInfo |
174
- | **手动加的数据集有 `field_`,API 加的没有** | **addDataset 用了 form+appId,不是设计器 LOGIC** | 按 `add-dataset-registration-guide.md` del 后重建 |
175
- | **单元格表头文案 ≠ 属性面板字段名**(如「姓名」实为「标题」) | 旧逻辑把无匹配列**兜底到系统字段** | 改为 `exit 4` 让用户指定;存量报表用 `fieldMap`/重绑修复 |
176
- | **左侧出现重复数据源分组** | 同 sourceId 被重复 `addDataset` 挂载 + 画布游离单元格引用 | 先改绑/清单元格再 `delDataset`;见 `dataset-sidebar-ops-guide.md §5.3` |
177
- | `matchByName=false`(自检) | 同上 / 用了显示名 | 强制走 §二 resolve |
178
- | 部分列有数据部分没有 | 个别列 name 写错 | 逐列对照 fieldsKey |
179
- | fieldsKey 为空 | 未传 `isFront=true` 或 datasetId 错 | 修正后再绑定,禁止用空列表继续 |
180
-
181
- ---
182
-
183
- ## 五、与现有文档关系
184
-
185
- | 文档 | 关系 |
186
- |------|------|
187
- | **`add-dataset-registration-guide.md`** | **addDataset 与设计器对齐;fieldsKey 无 `field_` 的根因** |
188
- | `api-report-build-fast-path.md` | 建表流程 §三/§五 引用本节;保存门禁 |
189
- | `cross-dataset-filter-guide.md` | leftFilter 的 fieldKey 末段同样必须是 fieldsKey.name |
190
- | `e10-cli report verify bind` | 保存后一键校验(必跑) |
191
- | `e10-cli report validate` | 离线 JSON 形态检查;**线上以本节 + verify_field_bind 为准** |
192
-
193
- ---
194
-
195
- ## 六、Agent 执行清单(复制即用)
196
-
197
- - [ ] `sourceName` 为 `{表单名}(业务模块)`(回显名),不是裸表单名(`build spec` 已自动补后缀)
198
- - [ ] 本报表**已有同 sourceId 数据集 → 复用**,不重复 `addDataset`
199
- - [ ] `addDataset` 为 `LOGIC` + `weaver-ebuilder-form-service`(非共享表特例)
200
- - [ ] `fieldsKey` 业务字段已含 `field_*`(否则先修注册)
201
- - [ ] `fieldsAndOriginFields?isFront=true` 已拿到非空 `fieldsKey`
202
- - [ ] 每个需求列:`显示名 → fieldsKey.name` 映射表已打印确认(`build spec` 输出 `fieldBindings`)
203
- - [ ] **任一无精确命中 → exit 4 时,把 `candidates`/`allFields` 交给用户指定字段,禁止自行猜**
204
- - [ ] 同 sourceId 多实例时绑的是 LOGIC/`field_*` 那条
205
- - [ ] `name` ∈ 当前 dataset 的 fieldsKey(勿臆造 / 勿手工补 `field_`)
206
- - [ ] 保存前 `assert` 全部通过
207
- - [ ] 保存后 `verify_field_bind.js` / 等价断言全部通过
208
- - [ ] 未通过则**不交付**
1
+ # 字段精确绑定(强制 · 禁止匹配失败)
2
+
3
+ > **事故案例(已验证)**:报表 `AAA`(`1312677851392417792`)保存了 9 个 Field 格,但 `name` / `fieldKey` 末段写成裸雪花 ID `3506271516100878845`,而数据源真实字段名是 `field_3506271516100878845` → **设计器有字段格、取数全部匹配失败(字段获取不上)**。
4
+ > **前置事故(已验证 · `1312765120721813505`)**:Agent 用错 `addDataset`(`type=form` + `sourceGroup=appId`)时,`fieldsKey` **本身只有裸雪花**;用户在设计器手动「+」同表则得到 `field_*`。
5
+ > **遗漏点**:不是写 Field 时忘加前缀,而是 **注册契约与设计器不一致** —— 必先读 **`add-dataset-registration-guide.md`**,把数据集加成 LOGIC + `weaver-ebuilder-form-service`,再绑定。
6
+ > **唯一标准**:`name` / `fieldKey` 末段必须 **精确等于** 当前数据集 `fieldsAndOriginFields.fieldsKey[].name`(以接口回读为准)。
7
+ > **禁止**:注册错了却在 cell 上「手工补 `field_`」;或对正确的 `field_*` fieldsKey 去前缀。
8
+ > **硬目标**:任意 Field 的 `name` **必须**等于该 dataset 的 `fieldsKey[].name`;**零容忍**猜测、截断、错数据集。
9
+
10
+ ---
11
+
12
+ ## 〇、先保证 fieldsKey 带 `field_`(普通应用)
13
+
14
+ | 检查 | 说明 |
15
+ |------|------|
16
+ | addDataset | 默认 `sourceType=LOGIC` + `sourceGroup=weaver-ebuilder-form-service` |
17
+ | 门禁 | 业务字段若多为裸雪花 → **注册错误**,`delDataset` 重建;见 `add-dataset-registration-guide.md` |
18
+ | 同 sourceId 多实例 | **优先绑**设计器/LOGIC 那条(`field_*`),勿绑旧 form+appId 实例 |
19
+
20
+ ---
21
+
22
+ ## 一、唯一合法字段名来源
23
+
24
+ ```
25
+ GET /api/bs/edc/dataset/fieldsAndOriginFields
26
+ ?datasetId={datasetId}&hasField=true&isFront=true&reportId={reportId}
27
+ → data.fieldsKey[] // 权威列表;isFront=false 常为空,禁止依赖
28
+ ```
29
+
30
+ 每个 `fieldsKey` 项的关键属性:
31
+
32
+ | 属性 | 用途 | 示例 |
33
+ |------|------|------|
34
+ | **`name`** | **写入 Field 的 `name` / fieldKey 末段(唯一正确)** | `field_3506271516100878845`、`create_time` |
35
+ | `text` / `showName` | 仅用于按中文显示名**查找**对应 `name` | `资产类别` |
36
+ | `originName` | 参考,不可直接当 fieldKey 末段(除非与 `name` 相同) | — |
37
+
38
+ **禁止**从下列来源臆造 `name`:
39
+
40
+ | 错误来源 | 错误示例 | 后果 |
41
+ |----------|----------|------|
42
+ | 表单字段 ID / 雪花 ID(无 `field_`) | `3506271516100878845` | **取数匹配失败**(本案) |
43
+ | 显示名 | `资产类别` | 设计器/取数均失败 |
44
+ | 截断 / 半截 | `field_5`、`field_3506` | 匹配失败 |
45
+ | 去掉 `field_` 前缀 | 把 `field_xxx` 写成 `xxx` | **取数匹配失败** |
46
+ | 外部字段表 fieldId 未与 fieldsKey 交叉核对 | 形态可能不同 | 须与 fieldsKey.name 对齐后再用 |
47
+
48
+ ---
49
+
50
+ ## 二、绑定算法(强制)
51
+
52
+ > **⚠️ 2026-09-11 追加门禁(事故:报表 `1313789294296735745`)**:B3 列文案「姓名」,但该表单**没有「姓名」字段**,
53
+ > 旧逻辑静默兜底到系统字段 `name`(真实显示名「标题」)→ 单元格文案与绑定字段语义不符、属性面板回显对不上。
54
+ > **新规则:精确匹配失败必须 `exit 4` 提示用户指定字段,禁止任何自动兜底(尤其禁止回退系统字段)。**
55
+ > 落地实现:构建层 + `e10-cli report build spec` 的**建表前预检**。
56
+
57
+ 对需求中的每一列(显示名 → 数据字段):
58
+
59
+ ### 2.0 允许的匹配(命中即止,**全为精确匹配**)
60
+
61
+ | 顺序 | 依据 | 说明 |
62
+ |------|------|------|
63
+ | 1 | `column.fieldName` / `spec.fieldMap[列标题]` | 用户/Agent 显式指定(可为 `field_xxx`、显示名、系统字段名) |
64
+ | 2 | 列标题 === `fieldsKey[].text/showName`(归一化:去空白、全角括号→半角) | 主路径 |
65
+ | 3 | `column.field` === `text/showName` 或 `name` | 兼容旧 spec |
66
+ | 4 | 都不中 | **`unmatched` → exit 4**(附模糊候选供用户挑选) |
67
+
68
+ **禁止**:包含/被包含(`includes`)等模糊规则自动绑定;**禁止**把 `name`/`id`/`create_time` 等系统字段当作兜底。
69
+
70
+ ### 2.0.1 规则 A · 找不到字段 → **停下让用户指定**(强制 · 2026-09-11)
71
+
72
+ > **核心**:需求里的字段在当前数据集表单里**找不到**时,**不许猜、不许兜底**,必须停下把候选交给用户。
73
+
74
+ `resolveFieldBindings()` 的 `unmatched[]` 三种 `reason` 及处置:
75
+
76
+ | `reason` | 含义 | 退出码 | 处置 |
77
+ |----------|------|--------|------|
78
+ | `no_exact_match` | 列标题在 `fieldsKey.text` 中**无精确命中** | **4** | 把 `candidates` + `allFields` 抛给用户,请其指定字段 |
79
+ | `explicit_not_found` | 显式 `fieldName`/`fieldMap` 指向的字段**不存在** | **4** | 同上(禁止臆造字段名) |
80
+ | `display_mismatch` | 能绑到,但字段**真实显示名 ≠ 列文案**且未确认 | **4** | 用户确认列名有意如此 → `fieldConfirmed` / `--field-confirmed "列标题"` |
81
+
82
+ **两阶段拦截**(都不建表 / 删未完成表):
83
+
84
+ | 阶段 | 时机 | 未通过 |
85
+ |------|------|--------|
86
+ | 预检 | `fieldsBySource`(**建表前**) | **exit 4 · 未创建任何报表** |
87
+ | 复校 | 本表 `addDataset` → `fieldsKey`(落库前) | **exit 4 · 删除本轮未完成报表** |
88
+
89
+ 输出结构(`rule:"A"`):
90
+
91
+ ```json
92
+ {
93
+ "action": "confirm_field", "rule": "A", "needConfirm": true, "reportCreated": false,
94
+ "unmatched": [ { "column": "姓名", "reason": "no_exact_match", "prompt": "…请用户从 allFields/candidates 中指定字段",
95
+ "candidates": [ { "name": "field_...", "text": "发展人", "system": false } ] } ],
96
+ "allFields": [ { "name": "field_...", "text": "发展人", "system": false } ],
97
+ "next": { "specFields": { "fieldMap": { "<列标题>": "<字段显示名 | field_雪花 | 系统字段名>" } } }
98
+ }
99
+ ```
100
+
101
+ 用户指定后复跑:`spec.fieldMap = { "姓名": "发展人" }`(或 `field_xxx`)。
102
+ 若列名坚持与字段显示名不同 → 另加 `fieldConfirmed: ["姓名"]` / CLI `--field-confirmed "姓名"`。
103
+
104
+ **禁止**:
105
+ - 用「最相近的」字段顶上(哪怕语义看似接近);
106
+ - 回退系统字段(`name`→「标题」是历史事故);
107
+ - 先带病落库、事后再让用户改。
108
+
109
+ > 一页速览:。
110
+
111
+ ```javascript
112
+ // 1. 拉权威列表
113
+ const fieldsKey = (await fieldsAndOriginFields(...)).data.fieldsKey;
114
+
115
+ // 2. 解析(库函数,已含门禁)
116
+ const plan = resolveFieldBindings(fieldsKey, spec.columns, { fieldMap, fieldConfirmed });
117
+ if (!plan.ok) {
118
+ // exit 4:把每列 candidates 抛给用户,等其指定字段;不要自己猜
119
+ }
120
+ const nf = plan.bindings.find(b => b.index === colIndex).field;
121
+ ```
122
+
123
+ **规则摘要**:
124
+
125
+ 1. **先** `fieldsAndOriginFields`,**再**写任何 Field 单元格
126
+ 2. `name` **逐字等于** `fieldsKey[].name`(含 `field_` 前缀;系统字段如 `create_time` 原样)
127
+ 3. `fieldKey` = `{datasetId}.{sourceId}.{fieldsKey.name}`,三段齐全
128
+ 4. 保存前对**每一个** Field 做 `byName.has(name)`;任一失败 → **中止保存**,禁止带病落库
129
+
130
+ ---
131
+
132
+ ## 三、保存后强制回读校验(交付门禁)
133
+
134
+ `getReportDevInfo` 后,对每个 `cellType === 'Field'`:
135
+
136
+ ```javascript
137
+ function assertFieldBindExact(devFields, fieldsKey) {
138
+ const legal = new Set(fieldsKey.map(f => f.name));
139
+ const fieldCells = devFields.filter(f => f.cellType === 'Field');
140
+ if (!fieldCells.length) throw new Error('无 Field 单元格');
141
+ for (const f of fieldCells) {
142
+ const last = String(f.fieldKey || '').split('.')[2] || '';
143
+ if (!f.name || !legal.has(f.name)) {
144
+ throw new Error(`字段匹配失败 name=${f.name} text=${f.text}`);
145
+ }
146
+ if (last !== f.name || !legal.has(last)) {
147
+ throw new Error(`fieldKey 末段不在 fieldsKey: ${f.fieldKey}`);
148
+ }
149
+ // 裸雪花:若 fieldsKey 里已有 field_xxx,则禁止用裸名;否则先查 addDataset 是否注册错
150
+ }
151
+ }
152
+ ```
153
+
154
+ | 检查 | 失败即 FAIL |
155
+ |------|-------------|
156
+ | 每个 Field 的 `name ∈ fieldsKey.name` | ✅ |
157
+ | `fieldKey` 末段 === `name` | ✅ |
158
+ | 普通应用:fieldsKey 业务字段含 `field_*`(注册门禁) | ✅ 见 `add-dataset-registration-guide.md` |
159
+ | Field 数量 ≥ 需求列数 | ✅ |
160
+ | **单元格文案 === 绑定字段真实显示名**(否则须用户确认) | ⚠ `verify bind` 列出 `display_mismatch` |
161
+ | **同 sourceId 只挂 1 个数据集**(否则左侧重复分组) | ⚠ `verify bind` 列出 `duplicate_dataset_mount` |
162
+
163
+ `e10-cli report verify bind <reportId> [appId] [--strict]`:门禁失败 exit 1;`--strict` 时「待用户确认项」exit 2。
164
+
165
+ **未通过校验 → 须修正并告知用户**;可与「已交付的设计器链接」并行:先给链接(见 `report-delivery-contract.md` §〇),校验失败再补丁修复,**禁止**为等校验而拖延首轮地址。
166
+
167
+ ---
168
+
169
+ ## 四、故障对照
170
+
171
+ | 现象 | 根因 | 处理 |
172
+ |------|------|------|
173
+ | 设计器有 Field 格,预览/取数无数据或字段空白 | `name` 为裸 ID,缺 `field_` | 按 fieldsKey.name 重绑并 saveReportDevInfo |
174
+ | **手动加的数据集有 `field_`,API 加的没有** | **addDataset 用了 form+appId,不是设计器 LOGIC** | 按 `add-dataset-registration-guide.md` del 后重建 |
175
+ | **单元格表头文案 ≠ 属性面板字段名**(如「姓名」实为「标题」) | 旧逻辑把无匹配列**兜底到系统字段** | 改为 `exit 4` 让用户指定;存量报表用 `fieldMap`/重绑修复 |
176
+ | **左侧出现重复数据源分组** | 同 sourceId 被重复 `addDataset` 挂载 + 画布游离单元格引用 | 先改绑/清单元格再 `delDataset`;见 `dataset-sidebar-ops-guide.md §5.3` |
177
+ | `matchByName=false`(自检) | 同上 / 用了显示名 | 强制走 §二 resolve |
178
+ | 部分列有数据部分没有 | 个别列 name 写错 | 逐列对照 fieldsKey |
179
+ | fieldsKey 为空 | 未传 `isFront=true` 或 datasetId 错 | 修正后再绑定,禁止用空列表继续 |
180
+
181
+ ---
182
+
183
+ ## 五、与现有文档关系
184
+
185
+ | 文档 | 关系 |
186
+ |------|------|
187
+ | **`add-dataset-registration-guide.md`** | **addDataset 与设计器对齐;fieldsKey 无 `field_` 的根因** |
188
+ | `api-report-build-fast-path.md` | 建表流程 §三/§五 引用本节;保存门禁 |
189
+ | `cross-dataset-filter-guide.md` | leftFilter 的 fieldKey 末段同样必须是 fieldsKey.name |
190
+ | `e10-cli report verify bind` | 保存后一键校验(必跑) |
191
+ | `e10-cli report py validate` | 离线 JSON 形态检查;**线上以本节 + verify_field_bind 为准** |
192
+
193
+ ---
194
+
195
+ ## 六、Agent 执行清单(复制即用)
196
+
197
+ - [ ] `sourceName` 为 `{表单名}(业务模块)`(回显名),不是裸表单名(`build spec` 已自动补后缀)
198
+ - [ ] 本报表**已有同 sourceId 数据集 → 复用**,不重复 `addDataset`
199
+ - [ ] `addDataset` 为 `LOGIC` + `weaver-ebuilder-form-service`(非共享表特例)
200
+ - [ ] `fieldsKey` 业务字段已含 `field_*`(否则先修注册)
201
+ - [ ] `fieldsAndOriginFields?isFront=true` 已拿到非空 `fieldsKey`
202
+ - [ ] 每个需求列:`显示名 → fieldsKey.name` 映射表已打印确认(`build spec` 输出 `fieldBindings`)
203
+ - [ ] **任一无精确命中 → exit 4 时,把 `candidates`/`allFields` 交给用户指定字段,禁止自行猜**
204
+ - [ ] 同 sourceId 多实例时绑的是 LOGIC/`field_*` 那条
205
+ - [ ] `name` ∈ 当前 dataset 的 fieldsKey(勿臆造 / 勿手工补 `field_`)
206
+ - [ ] 保存前 `assert` 全部通过
207
+ - [ ] 保存后 `e10-cli report verify bind` / 等价断言全部通过
208
+ - [ ] 未通过则**不交付**