@salesforce/afv-skills 1.59.0 → 1.60.0

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 (173) hide show
  1. package/package.json +1 -1
  2. package/skills/agentforce-architecture-analyze/SKILL.md +66 -17
  3. package/skills/agentforce-architecture-analyze/assets/cli/show_access_token.yaml +4 -7
  4. package/skills/agentforce-architecture-analyze/references/contract.json +1 -1
  5. package/skills/agentforce-architecture-analyze/scripts/emit_env.py +2 -2
  6. package/skills/agentforce-architecture-analyze/scripts/main.py +40 -44
  7. package/skills/agentforce-architecture-analyze/scripts/rest_client.py +11 -3
  8. package/skills/agentforce-architecture-analyze/scripts/sf_cli.py +77 -10
  9. package/skills/agentforce-architecture-analyze/scripts/tests/test_end_to_end_fixture.py +1 -0
  10. package/skills/agentforce-architecture-analyze/scripts/tests/test_flow_children_inflation.py +1 -0
  11. package/skills/agentforce-architecture-analyze/scripts/tests/test_main_pipeline.py +53 -2
  12. package/skills/agentforce-architecture-analyze/scripts/tests/test_resolve_creds.py +184 -36
  13. package/skills/agentforce-architecture-analyze/scripts/tests/test_rest_client.py +17 -0
  14. package/skills/agentforce-architecture-analyze/scripts/tests/test_sf_cli.py +165 -11
  15. package/skills/agentforce-architecture-analyze/scripts/tests/test_skill_md_pipeline_block.py +319 -0
  16. package/skills/agentforce-d360-analyze/SKILL.md +28 -10
  17. package/skills/agentforce-d360-analyze/scripts/dc.py +200 -50
  18. package/skills/agentforce-d360-analyze/scripts/discover_sessions.py +10 -6
  19. package/skills/agentforce-d360-analyze/scripts/fetch_dc.py +22 -6
  20. package/skills/agentforce-d360-analyze/scripts/resolve_session.py +21 -13
  21. package/skills/agentforce-d360-analyze/scripts/tests/test_dc_and_resolve_session.py +298 -76
  22. package/skills/agentforce-d360-analyze/scripts/tests/test_default_target_org.py +432 -0
  23. package/skills/agentforce-d360-analyze/scripts/tests/test_discover_sessions.py +1 -1
  24. package/skills/agentforce-d360-analyze/scripts/tests/test_resolve_session_main.py +9 -3
  25. package/skills/agentforce-d360-analyze/scripts/tests/test_skill_md_prefix_block.py +176 -0
  26. package/skills/consumer-goods-accruals-datakit-deploy/SKILL.md +17 -27
  27. package/skills/consumer-goods-accruals-datakit-deploy/scripts/setup.js +7 -17
  28. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/00-check-prerequisites.js +47 -63
  29. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/01-download-static-resource.js +10 -13
  30. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/02-replace-org-id.js +46 -46
  31. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/03-deploy-metadata.js +4 -21
  32. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/06-create-dataspace.js +58 -82
  33. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/07-deploy-accruals-reports.js +16 -315
  34. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/08-deploy-ui.js +9 -24
  35. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/09-completion.js +44 -44
  36. package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/utils.js +11 -63
  37. package/skills/consumer-goods-rtr-datacloud-export-configure/SKILL.md +2 -1
  38. package/skills/consumer-goods-tpe-dashboard-configure/SKILL.md +2 -1
  39. package/skills/consumer-goods-tpe-dashboard-custom-kpi-configure/SKILL.md +2 -1
  40. package/skills/consumer-goods-tpe-datakit-deploy/SKILL.md +2 -1
  41. package/skills/data360-schema-get/SKILL.md +3 -3
  42. package/skills/design-systems-slds-validate/references/report-format.md +1 -1
  43. package/skills/dx-org-manage/SKILL.md +1 -0
  44. package/skills/dx-org-shape-manage/SKILL.md +1 -0
  45. package/skills/dx-org-snapshot-manage/SKILL.md +172 -0
  46. package/skills/dx-org-snapshot-manage/examples/create_error_output.json +16 -0
  47. package/skills/dx-org-snapshot-manage/examples/create_success_output.json +19 -0
  48. package/skills/dx-org-snapshot-manage/examples/delete_output.json +9 -0
  49. package/skills/dx-org-snapshot-manage/examples/get_output.json +19 -0
  50. package/skills/dx-org-snapshot-manage/examples/list_output.json +36 -0
  51. package/skills/dx-org-snapshot-manage/references/cli_flags.md +179 -0
  52. package/skills/dx-org-snapshot-manage/references/snapshot_usage.md +55 -0
  53. package/skills/dx-org-trial-expiration-check/SKILL.md +1 -1
  54. package/skills/dx-org-trial-expiration-check/scripts/check_expiration.sh +2 -2
  55. package/skills/education-cloud-student-recruitment-agent-configure/SKILL.md +7 -5
  56. package/skills/education-cloud-student-recruitment-agent-configure/references/prerequisites.md +11 -11
  57. package/skills/experience-content-media-image-generate/SKILL.md +83 -0
  58. package/skills/experience-content-media-image-generate/scripts/download-image.sh +84 -0
  59. package/skills/external-diagram-mermaid-generate/SKILL.md +12 -12
  60. package/skills/field-service-data-capture-migrate/references/component-mapping.md +1 -1
  61. package/skills/field-service-data-capture-migrate/references/fsm-dc-capability-catalog.md +1 -2
  62. package/skills/field-service-data-capture-migrate/references/known-deploy-errors.md +1 -0
  63. package/skills/field-service-data-capture-migrate/references/transformer-rules.md +1 -1
  64. package/skills/field-service-data-capture-migrate/scripts/transform_flow.py +12 -1
  65. package/skills/field-service-data-capture-migrate/scripts/vendor/build_flow.py +4 -1
  66. package/skills/field-service-setup-orchestrator-get/SKILL.md +0 -1
  67. package/skills/insurance-brokerage-agency-billing-configure/SKILL.md +233 -0
  68. package/skills/insurance-brokerage-agency-billing-configure/references/accounting-setup.md +117 -0
  69. package/skills/insurance-brokerage-agency-billing-configure/references/billing-rules-config.md +237 -0
  70. package/skills/insurance-brokerage-agency-billing-configure/references/billing-settings.md +271 -0
  71. package/skills/insurance-brokerage-agency-billing-configure/references/demo-policy-config.md +138 -0
  72. package/skills/insurance-brokerage-agency-billing-configure/references/org-defaults.md +64 -0
  73. package/skills/insurance-brokerage-agency-billing-configure/references/permissions-config.md +226 -0
  74. package/skills/insurance-brokerage-agency-billing-configure/references/prerequisites.md +142 -0
  75. package/skills/life-sciences-fieldsalesrep-coordinate/SKILL.md +3 -2
  76. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-execution-state-and-recovery.md +3 -3
  77. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-overview.md +68 -45
  78. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-visit-creation-data.md +17 -18
  79. package/skills/life-sciences-kam-coordinate/references/stage-5-data-and-plan-templates-overview.md +16 -3
  80. package/skills/life-sciences-kam-coordinate/references/stage-5-data-creation-data.md +16 -13
  81. package/skills/marketing-message-test/SKILL.md +194 -0
  82. package/skills/marketing-message-test/references/api-contract.md +333 -0
  83. package/skills/omnistudio-callable-apex-generate/SKILL.md +3 -3
  84. package/skills/omnistudio-datamapper-generate/SKILL.md +6 -6
  85. package/skills/omnistudio-datapacks-deploy/SKILL.md +11 -11
  86. package/skills/omnistudio-dependencies-analyze/SKILL.md +5 -5
  87. package/skills/omnistudio-epc-catalog-generate/SKILL.md +6 -6
  88. package/skills/omnistudio-flexcard-generate/SKILL.md +6 -6
  89. package/skills/omnistudio-integration-procedure-generate/SKILL.md +6 -6
  90. package/skills/omnistudio-omniscript-generate/SKILL.md +6 -5
  91. package/skills/platform-apex-logs-debug/SKILL.md +1 -0
  92. package/skills/platform-metadata-deploy/SKILL.md +1 -0
  93. package/skills/platform-models-api-configure/SKILL.md +5 -5
  94. package/skills/platform-soql-query/SKILL.md +1 -0
  95. package/skills/service-itsm-agentic-setup-cmdb-coordinate/SKILL.md +17 -6
  96. package/skills/service-itsm-agentic-setup-incident-sla-configure/SKILL.md +107 -192
  97. package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/attach-milestone-action.json +6 -6
  98. package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/milestone-patterns.md +49 -20
  99. package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/output-templates.md +4 -4
  100. package/skills/service-itsm-agentic-setup-incident-sla-configure/references/mcp-invocation.md +224 -290
  101. package/skills/service-itsm-agentic-setup-incident-sla-configure/references/workflow.md +295 -0
  102. package/skills/service-itsm-incident-priority-configure/SKILL.md +1 -1
  103. package/skills/service-itsm-slack-itservice-configure/SKILL.md +2 -1
  104. package/skills/service-itsm-slack-itservice-configure/references/manage-slack-connection.md +2 -2
  105. package/skills/service-omni-channel-setup-coordinate/SKILL.md +20 -13
  106. package/skills/service-omni-channel-setup-coordinate/scripts/integration-driver.sh +5 -55
  107. package/skills/service-omni-channel-setup-coordinate/scripts/tests/test_omni_contracts.py +0 -38
  108. package/skills/service-omni-command-center-analyze/SKILL.md +2 -3
  109. package/skills/service-omni-command-center-analyze/scripts/analyze.sh +3 -3
  110. package/skills/tableau-next-asset-create/README.md +65 -0
  111. package/skills/tableau-next-asset-create/SKILL.md +409 -0
  112. package/skills/tableau-next-asset-create/references/admin-users.md +149 -0
  113. package/skills/tableau-next-asset-create/references/ai-readiness.md +355 -0
  114. package/skills/tableau-next-asset-create/references/alerts.md +111 -0
  115. package/skills/tableau-next-asset-create/references/build-workflow.md +819 -0
  116. package/skills/tableau-next-asset-create/references/dashboard-authoring.md +302 -0
  117. package/skills/tableau-next-asset-create/references/dashboard-design-principles.md +352 -0
  118. package/skills/tableau-next-asset-create/references/data-transform-gotchas.md +210 -0
  119. package/skills/tableau-next-asset-create/references/data-understanding.md +387 -0
  120. package/skills/tableau-next-asset-create/references/deep-reference-index.md +74 -0
  121. package/skills/tableau-next-asset-create/references/destructive-operations.md +62 -0
  122. package/skills/tableau-next-asset-create/references/edit-dashboard.md +273 -0
  123. package/skills/tableau-next-asset-create/references/edit-visualization.md +529 -0
  124. package/skills/tableau-next-asset-create/references/empty-source-handling.md +104 -0
  125. package/skills/tableau-next-asset-create/references/ingest-and-metric-gotchas.md +638 -0
  126. package/skills/tableau-next-asset-create/references/insight-bundle.md +69 -0
  127. package/skills/tableau-next-asset-create/references/large-flat-file-handling.md +331 -0
  128. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/_envelope.json +54 -0
  129. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/_shared-widget-fields.json +122 -0
  130. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/add_page.json +29 -0
  131. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/delete_page.json +27 -0
  132. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/discard_dashboard.json +11 -0
  133. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/move_widget.json +30 -0
  134. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/move_widget_to_page.json +30 -0
  135. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/place_widget_on_page.json +30 -0
  136. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/remove_widget_from_dashboard.json +16 -0
  137. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/remove_widget_from_page.json +26 -0
  138. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/rename_page.json +28 -0
  139. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/save_dashboard.json +18 -0
  140. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/undo.json +11 -0
  141. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_button_widget.json +69 -0
  142. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_container_widget.json +88 -0
  143. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_filter_widget.json +146 -0
  144. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_metric_widget.json +78 -0
  145. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_navigation_widget.json +80 -0
  146. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_parameter_widget.json +160 -0
  147. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_text_widget.json +103 -0
  148. package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_visualization_widget.json +68 -0
  149. package/skills/tableau-next-asset-create/references/sdm-tool-reference.md +721 -0
  150. package/skills/tableau-next-asset-create/references/semantic-query-and-enrichment.md +355 -0
  151. package/skills/tableau-next-asset-create/references/shared-gates.md +322 -0
  152. package/skills/tableau-next-asset-create/references/sharing-and-promotion.md +306 -0
  153. package/skills/tableau-next-asset-create/references/tasks/ai-readiness-audit.md +51 -0
  154. package/skills/tableau-next-asset-create/references/tasks/analyze-data.md +48 -0
  155. package/skills/tableau-next-asset-create/references/tasks/build-dashboard.md +66 -0
  156. package/skills/tableau-next-asset-create/references/tasks/build-end-to-end.md +73 -0
  157. package/skills/tableau-next-asset-create/references/tasks/create-data-transform.md +51 -0
  158. package/skills/tableau-next-asset-create/references/tasks/create-viz.md +58 -0
  159. package/skills/tableau-next-asset-create/references/tasks/edit-dashboard.md +80 -0
  160. package/skills/tableau-next-asset-create/references/tasks/edit-sdm.md +106 -0
  161. package/skills/tableau-next-asset-create/references/tasks/edit-viz.md +40 -0
  162. package/skills/tableau-next-asset-create/references/tasks/enrich-model.md +68 -0
  163. package/skills/tableau-next-asset-create/references/tasks/ingest-flat-file.md +68 -0
  164. package/skills/tableau-next-asset-create/references/tasks/manage-alert.md +35 -0
  165. package/skills/tableau-next-asset-create/references/tasks/profile-dataset.md +47 -0
  166. package/skills/tableau-next-asset-create/references/tasks/promote-or-reuse.md +46 -0
  167. package/skills/tableau-next-asset-create/references/tasks/provision-user.md +39 -0
  168. package/skills/tableau-next-asset-create/references/tasks/query-model.md +92 -0
  169. package/skills/tableau-next-asset-create/references/tasks/review-verified-questions.md +42 -0
  170. package/skills/tableau-next-asset-create/references/tasks/share-asset.md +37 -0
  171. package/skills/tableau-next-asset-create/references/verified-questions.md +125 -0
  172. package/skills/tableau-next-asset-create/references/viz-authoring.md +992 -0
  173. package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/predefined-incident-policy.json +0 -102
@@ -0,0 +1,638 @@
1
+ # Ingestion & Metric Gotchas — verified payloads
2
+
3
+ Load this for **Step 0** (file or existing-connection ingestion → DLO → DMO →
4
+ mapping) and for **Step 8** (metric creation gotchas).
5
+
6
+ For visualization authoring (Step 10), see `references/viz-authoring.md`.
7
+ For dashboard building (Step 11), see `references/dashboard-authoring.md`.
8
+
9
+ The MCP tools are namespaced per server — names here are bare; use whichever
10
+ server prefix is connected.
11
+
12
+ ## Contents
13
+
14
+ Intent → heading (this file is cited by `ingest-flat-file.md` and
15
+ `enrich-model.md`; it is not a first hop):
16
+
17
+ - File CSV / Excel ingest (upload → DLO → DMO → mapping) → §A
18
+ - Presigned PUT / SfDrive (`&`, opaque directories, `expiryTime`, 400 codes) → §A step 2
19
+ - Excel sheets vs CSV → §A step 3
20
+ - `create_data_stream` payload traps (format, mappings, timeout, name collision) → §A step 4 + Ingestion gotchas
21
+ - Existing database / lakehouse connection ingest (Snowflake, …) → §C
22
+ - Table already `inUse` by another stream → opaque `INTERNAL_ERROR` on create → §C step 5
23
+ - `Direct_Access` streams never call `run_data_stream` — go `ACTIVE` → `run_query` → §C step 6
24
+ - Connection list / test (`connectorType` casing, `9cg` id, paging bounds) → §C
25
+ - By-id connection lookup (`get_connection` typed 400 vs 404) → §C step 1
26
+ - DMO create + mapping traps (`__dlm`, Engagement event field, duplicate map, `ACTIVE` ≠ table exists) → §A steps 8–11 + Ingestion gotchas
27
+ - Metric creation (`Auto`, identifying dimension, count-via-numeric) → §B
28
+
29
+ ---
30
+
31
+ ## A. Ingest flat files (CSV / upload) → DLO → DMO → mapping
32
+
33
+ This is the front half that SKILL.md Steps 1–7 assume already exists. Run it
34
+ when the user points at local files instead of existing data assets. The order
35
+ is fixed; each tool feeds the next.
36
+
37
+ 1. **`get_upload_connection`** `{ "connectorType": "UploadedFiles" }` → returns
38
+ the `connectionId` used by every `infer_object_schema` call. Prefer this
39
+ singleton lookup over `list_connections` for the file-upload flow.
40
+ 2. **`generate_presigned_credential`** `{ "fileName": "fact_orders.csv" }` once
41
+ **per file** → returns the presigned upload target plus opaque
42
+ `parentDirectory` and `importDirectory`. PUT the file body to `presignedUrl`
43
+ out of band:
44
+ - Decode HTML-encoded `&` in the URL to `&` before the PUT.
45
+ - If the response includes `headers`, attach them on the PUT (BYOK).
46
+ - Pass `parentDirectory` and `importDirectory` **verbatim** into
47
+ `infer_object_schema` and `create_data_stream`. Never construct, trim, or
48
+ substitute them — downstream fails with *"Given file is not present in the given location"*.
49
+ - `sfdrive_not_provisioned` (503): do **not** retry; uploads are not enabled
50
+ for the org. `sfdrive_unavailable` (503) may be retried once; if it
51
+ persists, treat it as not-provisioned.
52
+ - `invalid_file_name` (400): empty name, missing extension, or path-traversal
53
+ characters (`..`, `/`, `\`). `unsupported_file_type` (400): extension is
54
+ present but not `.csv` / `.xls` / `.xlsx`. Do not treat those as the same
55
+ error.
56
+ - `expiryTime` (ISO-8601) is when `presignedUrl` stops accepting writes. PUT
57
+ promptly. If the flow stalls past `expiryTime`, re-call this tool for a
58
+ fresh URL — do not PUT to a stale one.
59
+ - Do not show raw URLs, S3 paths, or directory tokens to the user.
60
+ 3. **Excel vs CSV.** CSV has no sheets — go straight to infer. For `.xls` /
61
+ `.xlsx`, call **`list_data_connection_objects` first** (same
62
+ `advancedAttributes` as infer, `fileType: "EXCEL"`) to list sheet names.
63
+ Each selected sheet becomes a **separate** DLO; pass `sheetName` on every
64
+ subsequent infer/create. Listing sheets is **NOT needed for CSV**.
65
+ 4. **`infer_object_schema`** per file (and per Excel sheet) — detects column
66
+ names + types. `resourceName` is always `"files"` for uploads.
67
+
68
+ ```jsonc
69
+ {
70
+ "connectionId": "9cg…",
71
+ "resourceName": "files",
72
+ "advancedAttributes": {
73
+ "driveLibraryId": "fileUploads$tua",
74
+ "parentDirectory": "<verbatim from generate_presigned_credential>", // often "$tua$"
75
+ "importDirectory": "005a…/1782143244125/", // verbatim from step 2, per file
76
+ "fileName": "fact_orders.csv",
77
+ "fileType": "CSV"
78
+ }
79
+ }
80
+ ```
81
+
82
+ Forward each Date/DateTime field's `format` and `originalType` into
83
+ `create_data_stream` `sourceFields`. Dropping `format` on a Date column
84
+ causes ingest to fail with *"missing required format string"*
85
+ (`DATACLOUD_API_CLIENT_EXCEPTION: Date field [...] is missing required
86
+ format string`).
87
+
88
+ Present the inferred fields, then **wait for the user to explicitly confirm
89
+ or adjust** before `create_data_stream` (see `ingest-flat-file.md`).
90
+
91
+ 5. **`create_data_stream`** per file — creates the **DLO** (`*__dll`). Pass
92
+ `sourceFields` (the inferred raw columns, including Date `format`),
93
+ `dataLakeObjectInfo` (name `*__dll`, `category`, fields, ≥1 primary key),
94
+ **`mappings`** (1:1 passthrough per source column), **`refreshConfig`**, and
95
+ file **`advancedAttributes`** (same directory tokens as infer).
96
+ `sourceFields` must be non-empty and `dataLakeObjectInfo.fields` must contain
97
+ at least one `isPrimaryKey: true` — the tool does not validate that
98
+ client-side.
99
+
100
+ The server silently **strips a trailing `__dlm`** from stream `name` (only
101
+ the DLO keeps `__dll`). Use the **returned** developer name for
102
+ `run_data_stream` / `get_data_stream`. Pre-check DLO name collisions with
103
+ `browse_data_assets` (`AssetType` `MktDataLakeObject`) — a duplicate
104
+ `dataLakeObjectInfo.name` surfaces as opaque `INTERNAL_ERROR` *"Unable to
105
+ create…"*, not `ALREADY_EXISTS`. There is no overwrite.
106
+
107
+ This call can take ~3 minutes; the MCP layer often times out after the
108
+ stream has already persisted. A naive retry against an already-persisted stream returns `INTERNAL_ERROR` *"Unable to create"* again. On timeout:
109
+ `get_data_stream` with the submitted name (`__dlm` stripped); if it exists,
110
+ proceed to `run_data_stream` (do not pick a different existing stream); if
111
+ it does not, retry create once.
112
+
113
+ After a successful create, register the DLO with `add_workspace_asset`:
114
+ `assetId` = `dataLakeObjectInfo.id`, `assetType` = `"MktDataLakeObject"`,
115
+ `assetUsageType` = `"Created"`. Resolve workspace: use one the user named,
116
+ else `list_workspaces` and **STOP to ask** — do not infer a workspace.
117
+
118
+ Reference shape (file source):
119
+
120
+ ```jsonc
121
+ {
122
+ "name": "fact_orders",
123
+ "label": "Fact Orders",
124
+ "connectorInfo": { "connectorType": "DataConnector",
125
+ "connectorDetails": { "name": "UploadedFiles" } },
126
+ "sourceFields": [
127
+ { "name": "Order_ID", "dataType": "Text" },
128
+ { "name": "Order_Date", "dataType": "Date", "format": "yyyy-MM-dd" },
129
+ { "name": "Sales", "dataType": "Number" }
130
+ /* … every column; forward infer format + originalType unchanged … */
131
+ ],
132
+ "dataLakeObjectInfo": {
133
+ "name": "fact_orders__dll",
134
+ "label": "Fact Orders",
135
+ "category": "Other", // ← see gotcha below; NOT "Engagement"
136
+ "dataspaceInfo": [{ "name": "default" }],
137
+ "fields": [
138
+ { "name": "Order_ID", "label": "Order ID", "dataType": "Text", "isPrimaryKey": true },
139
+ { "name": "Order_Date", "label": "Order Date", "dataType": "Date", "isPrimaryKey": false }
140
+ /* … clean names, NO __c — the platform adds it … */
141
+ ]
142
+ },
143
+ "mappings": [
144
+ { "sourceFieldLabel": "Order_ID", "targetFieldName": "Order_ID", "targetFieldReturntype": "TEXT" },
145
+ { "sourceFieldLabel": "Order_Date", "targetFieldName": "Order_Date", "targetFieldReturntype": "DATE" },
146
+ { "sourceFieldLabel": "Sales", "targetFieldName": "Sales", "targetFieldReturntype": "NUMBER" }
147
+ /* … 1:1 passthrough; targetFieldReturntype is UPPERCASE; omit transformationFormula … */
148
+ ],
149
+ "refreshConfig": { "refreshMode": "TOTAL_REPLACE", "frequency": { "frequencyType": "None" } },
150
+ "advancedAttributes": {
151
+ "driveLibraryId": "fileUploads$tua",
152
+ "parentDirectory": "<verbatim from generate_presigned_credential>",
153
+ "importDirectory": "<verbatim from generate_presigned_credential>",
154
+ "fileName": "fact_orders.csv",
155
+ "fileType": "CSV"
156
+ }
157
+ }
158
+ ```
159
+
160
+ Synthetic PK **only** when the source has no natural per-row unique column
161
+ (e.g. event logs): add
162
+ `{"transformationFormula": "UUID()", "targetFieldName": "record_id",
163
+ "targetFieldReturntype": "TEXT"}` and a matching `record_id` field with
164
+ `isPrimaryKey: true`.
165
+
166
+ **Type-enum asymmetry.** `sourceFields[].dataType` and
167
+ `mappings[].targetFieldReturntype` only have five values (`Text`/`Number`/
168
+ `Date`/`DateTime`/`Boolean`, resp. `TEXT`/`NUMBER`/`DATE`/`DATETIME`/
169
+ `BOOLEAN`). `dataLakeObjectInfo.fields[].dataType` also allows `Email` /
170
+ `Phone` / `Url` / `Percent` / `Currency` — there is no matching mapping
171
+ return type. A DLO field using one of those specializations still maps via
172
+ `TEXT` (Email/Phone/Url) or `NUMBER` (Percent/Currency).
173
+
174
+ **DLO category extras (file default is still `"Other"`).** `"Profile"`
175
+ requires `orgUnitIdentifierFieldName`. `"Engagement"` requires that plus
176
+ `eventDateTimeFieldName`. Do not pick those categories unless the source
177
+ actually has those columns — inventing them to satisfy the enum still
178
+ breaks mapping (Engagement DMO → `MISSING_ARGUMENT`). Ambiguous event/log
179
+ data without a natural entity identifier stays `"Other"`.
180
+
181
+ 6. **`run_data_stream`** `{ "recordIdOrDeveloperName": "fact_orders", "interactive": true }`
182
+ per stream — materializes rows into the DLO. **Do this before the Step 2
183
+ row-count gate** — a stream that hasn't run leaves the DLO at 0 rows.
184
+ **Then wait until the DLO is Active** before going on — "does it have rows
185
+ yet?" is a stand-in, not the same as ready. Only call this on a stream
186
+ created in the **current** DLO-creation flow.
187
+ - **Wait for stream `ACTIVE` first.** A freshly created stream returns
188
+ `PROCESSING` and flips to `ACTIVE` within ~5-15 seconds; calling
189
+ `run_data_stream` before that errors with *"Data Stream status must be
190
+ ACTIVE"*. Poll `get_data_stream` for stream status `ACTIVE`, or just retry
191
+ once after a short wait. (`get_data_stream` is valid for this status poll
192
+ only — not for schema or Primary Key; see step 7.)
193
+ - **`interactive` is picked by source type, not preference.** Uploaded files
194
+ use `interactive: true` (synchronous fast-ingest). Database/BYOL sources
195
+ (Snowflake, Databricks, BigQuery, Redshift, any connector-based source)
196
+ MUST use `interactive: false` — those connectors don't **support fast
197
+ ingest** and `interactive: true` fails deterministically with
198
+ `BAD_REQUEST` (*"Only connections configured with connectors that support fast ingest are permitted to execute data streams in interactive mode"*).
199
+ Never retry a DB source with `true`.
200
+ - **HTTP 412 on an uploaded file → retry the same call with
201
+ `interactive: false`.** 412 Precondition Failed means the file is too
202
+ large for the synchronous path; re-issuing the identical call with
203
+ `interactive: false` routes it through the async path instead. Don't loop
204
+ on 412 with the same `interactive` value.
205
+ - **`{success: true}` means the run was accepted, NOT that ingestion has finished.** Treat it as kicked off.
206
+ - **Verify row materialization with `run_query`, not `get_data_stream`.**
207
+ Confirm with a `SELECT COUNT(*)` against the `*__dll` table —
208
+ `get_data_stream`'s `lastRunStatus` field is unreliable/never updated via
209
+ the MCP path (it can stay `"NONE"` indefinitely even after CDP has rows).
210
+ Do **not** poll `lastRunStatus` until `"Success"` — that completion check
211
+ is not valid on this path. Wait at least ~10 seconds after
212
+ `run_data_stream` returns before the count. `0` or `table does not exist`
213
+ is catalog lag — wait 15–30 seconds and retry; that is not ingestion
214
+ failure.
215
+ - **Not-found:** `run_data_stream` says *"... DataStream is not found"*;
216
+ `get_data_stream` says *"DataStream found null for developerName = ..."*.
217
+ Match `errorCode: ITEM_NOT_FOUND`, not the message wording.
218
+ - **`get_data_stream` `mappings: []` after create** is a known read-time
219
+ projection quirk — mappings submitted on create were not lost.
220
+ 7. **Read back the real Primary Key and field names from the materialized DLO** — do not
221
+ assume the CSV column name survives ingestion (same doctrine as the
222
+ apiName-mutation rule / G3).
223
+
224
+ **SCHEMA REALITY CHECK — `get_data_stream` is not a valid source for this.**
225
+ `get_data_stream` returns the stream's *declared* schema — what was passed
226
+ into `dataLakeObjectInfo.fields[]` at `create_data_stream` time (i.e., what
227
+ `infer_object_schema` inferred from the raw CSV header, echoed back). It is
228
+ not the same source this check requires. Query the actual materialized
229
+ table: `run_query SELECT * FROM <dlo>__dll LIMIT 5`, or `browse_data_assets`,
230
+ to confirm actual field names. Real ingested DLOs commonly rename/suffix
231
+ fields (e.g. `sales_id` may actually be `sales_id_a__c` and nullable) and
232
+ sometimes carry a non-null surrogate key instead. Copy the actual Primary Key
233
+ field name from that result into the DMO create and the mapping.
234
+ 8. **`create_data_model_object`** per logical entity — creates the **DMO**
235
+ (`*__dlm`). Do **NOT** append the `__dlm` suffix yourself; the platform
236
+ adds it (`Orders`, not `Orders__dlm`). Same field list (using the
237
+ *read-back* names from step 7), `category: "Other"`, `dataSpaceName:
238
+ "default"`. **`isPrimaryKey` is required on every field object** — at least
239
+ one `true`, every other field explicit `false`. Omitting it (null) NPEs as UNKNOWN_EXCEPTION when a field's `isPrimaryKey` arrives null.
240
+
241
+ `category` is REQUIRED (`INVALID_INPUT` *"Please provide a category for the
242
+ Data Model Object."*). **Do NOT use `Engagement`** for a DMO you will map
243
+ from a file-upload DLO: mapping then hard-fails with `MISSING_ARGUMENT`
244
+ *"Unable to find Event Field for Engagement DLO in POST request of Mapping
245
+ Creation"* (this tool exposes no event-time parameter). If the data looks
246
+ like events but has no natural per-row entity identifier, pick `"Other"`
247
+ rather than inventing an org-unit field.
248
+
249
+ Field `dataType` **must match the source DLO column**. A numeric DLO column
250
+ is `Number`, not `Currency`/`Percent` — mapping fails with `INTERNAL_ERROR`
251
+ *"<field>'s type X is different from <field>'s type Y"*.
252
+
253
+ Duplicate developer name surfaces as `DB_SAVE_FAILED` with
254
+ `Duplicate_Developer_Name` in the message (not `ALREADY_EXISTS`). Pre-check
255
+ `browse_data_assets` (`MktDataModelObject`).
256
+
257
+ Field names in the **create response** are stored with `__c` (you send
258
+ `Order_ID`, the DMO stores `Order_ID__c`). The platform also injects
259
+ `DataSource__c`, `DataSourceObject__c`, `InternalOrganization__c`, and a
260
+ `KQ_<pk>__c` key-qualifier. Use the **returned** `__c` names as mapping
261
+ `targetFieldDeveloperName` values — don't reconstruct them.
262
+
263
+ After mapping succeeds, `add_workspace_asset` with `assetType`
264
+ `"MktDataModelObject"` and `assetUsageType` `"Created"`. If the create
265
+ response has no usable id, look it up via `browse_data_assets`. Same
266
+ workspace rule as the DLO: named workspace, else STOP and ask.
267
+
268
+ ```jsonc
269
+ {
270
+ "name": "Orders", "label": "Orders",
271
+ "category": "Other", "dataSpaceName": "default",
272
+ "fields": [
273
+ { "name": "Order_ID", "label": "Order ID", "dataType": "Text", "isPrimaryKey": true },
274
+ { "name": "Sales", "label": "Sales", "dataType": "Number", "isPrimaryKey": false }
275
+ /* … */
276
+ ]
277
+ }
278
+ ```
279
+
280
+ 9. **Wait until the DMO is Ready** before mapping. This is the check that was
281
+ missing — using "does it have rows yet?" as a stand-in for "is this thing
282
+ actually ready?" skips the DMO entirely. If the DMO isn't fully ready by
283
+ the time we try to map it, `create_dlo_to_dmo_mapping` fails with
284
+ "Primary Key not found".
285
+
286
+ - **`isEnabled: false` right after DMO creation is normal.** Check it, but
287
+ don't gate on it and don't wait for it before mapping — it's expected
288
+ transient state, not a readiness signal.
289
+ - Mapping may briefly fail with `INTERNAL_ERROR` *"DMO not found for the given developer name ... in the data space"* until the DMO is
290
+ mapping-eligible. Wait a few seconds and retry the **mapping** (not the
291
+ create). There is no delete-DMO tool.
292
+ - After the DMO is Ready, query/describe it and **copy its actual Primary Key
293
+ field name** again — don't assume the DLO Primary Key survived unchanged.
294
+ 10. **`create_dlo_to_dmo_mapping`** per DMO — wires the DLO rows into the DMO.
295
+ **Both sides carry the `__c` suffix** that the platform adds to every
296
+ file-ingested field, matched 1:1. This is a *different* suffix from the
297
+ numeric `shouldIncludeAllFields` suffix (SKILL.md Step 5) — here it is a
298
+ literal `__c` on the source AND target field dev names. Bare field names
299
+ (no `__c`) hard-fail; the server demands the `__c` form, including on the
300
+ PK. Bare entity names without `__dll`/`__dlm` suffixes are also rejected.
301
+
302
+ **Not idempotent.** Call exactly once per DLO→DMO pair. A duplicate is a
303
+ HARD error: `INTERNAL_ERROR`/500 whose message contains
304
+ `DUPLICATE_DLO_TO_DMO_MAPPING` (legacy path: `DUPLICATE_ARGUMENT_VALUE`/400).
305
+ The server never emits `ALREADY_EXISTS` here. Do **not** retry create. If
306
+ `get_dlo_to_dmo_mapping_status` returns `ERROR`, the mapping failed — read
307
+ the message and fix the cause; do **not** re-POST create (hits the duplicate
308
+ guard). `ERROR` = the mapping failed.
309
+
310
+ ```jsonc
311
+ {
312
+ "sourceEntityDeveloperName": "fact_orders__dll",
313
+ "targetEntityDeveloperName": "Orders__dlm",
314
+ "fieldMapping": [
315
+ { "sourceFieldDeveloperName": "Order_ID__c", "targetFieldDeveloperName": "Order_ID__c" },
316
+ { "sourceFieldDeveloperName": "Order_Date__c", "targetFieldDeveloperName": "Order_Date__c" },
317
+ { "sourceFieldDeveloperName": "Sales__c", "targetFieldDeveloperName": "Sales__c" }
318
+ /* … one entry per field … */
319
+ ]
320
+ }
321
+ ```
322
+
323
+ 11. **Mapping status vs query readiness.** `create_dlo_to_dmo_mapping` returns
324
+ `status: CREATING`. Use **`get_dlo_to_dmo_mapping_status`** (returned
325
+ `developerName` as `objectSourceTargetMapDeveloperName`) to inspect the
326
+ field mapping and to detect `ERROR` / `INACTIVE`. `ACTIVE` means only that
327
+ mapping metadata was deployed off-core — it is **not** a readiness signal
328
+ and does **not** mean the `<dmo>__dlm` table exists.
329
+
330
+ **Authoritative readiness gate:** poll `run_query`
331
+ `SELECT 1 FROM <dmo>__dlm LIMIT 1` (or `SELECT COUNT(*)`). Treat `42P01` /
332
+ "table does not exist" as NOT-READY (keep polling). Treat any success,
333
+ including `COUNT = 0`, as READY. Also query the actual mapped fields — a
334
+ partial/bad field mapping often surfaces only when that field is selected.
335
+ If `run_query` succeeds, the mapping committed — do not re-POST create.
336
+
337
+ Then build the SDM (Step 4) over the DMOs with `dataObjectType: "Dmo"` and
338
+ `dataObjectName: "Orders__dlm"`.
339
+
340
+ **Carve-out to the Step 2 hard gate:** for a DMO you just created and mapped
341
+ yourself in this same flow, `COUNT = 0` after a successful `run_query` is
342
+ READY — do not refuse to build as if it were a discovered empty object.
343
+ `42P01` is not READY — keep polling; do not proceed to SDM on metadata while
344
+ the table is missing. Keep the existing hard gate as-is for externally
345
+ discovered candidate sources (picking an empty pre-existing object).
346
+ Authoritative statement: `shared-gates.md` G2.
347
+
348
+ **`__dlm` still missing after a long poll:** keep treating `42P01` as
349
+ not-ready. Only fall back to querying the raw DLO if a query through the
350
+ semantic model itself, later (at the viz/query stage), fails with
351
+ table-not-found. Authoritative statement: `empty-source-handling.md`.
352
+
353
+ ### Ingestion gotchas
354
+ - **`category: "Other"` for file-upload DLOs *and* DMOs.** Using `"Engagement"`
355
+ (or another semantic category) on a file-backed object is wrong and causes
356
+ downstream grief. File uploads are category-neutral → `Other`. An Engagement
357
+ DMO cannot be mapped here: `MISSING_ARGUMENT` *"Unable to find Event Field
358
+ for Engagement DLO in POST request of Mapping Creation"*. A DLO `"Profile"`
359
+ also requires `orgUnitIdentifierFieldName`; `"Engagement"` requires that
360
+ plus `eventDateTimeFieldName` — do not pick those without real columns.
361
+ - **DLO/DMO status waits are not the mapping query gate.** Create DLO → wait
362
+ until Active; create DMO → wait until Ready. Mapping: use
363
+ `get_dlo_to_dmo_mapping_status` for `ERROR` / `INACTIVE`; **query readiness
364
+ is `run_query`** (`42P01` keep polling; `COUNT = 0` is READY). `ACTIVE` on
365
+ the status tool is metadata only. Skipping the DMO-ready wait before
366
+ `create_dlo_to_dmo_mapping` still produces "Primary Key not found".
367
+ - **`run_data_stream` is mandatory** before treating the DLO as Active. A
368
+ freshly created DLO has 0 rows until the stream runs.
369
+ - **Read back the real Primary Key, don't assume it.** After DLO creation (and
370
+ again after DMO creation), query/describe the materialized object and copy
371
+ its actual Primary Key field name. `get_data_stream` is the declared schema, not the
372
+ materialized one — do not use it for the SCHEMA REALITY CHECK.
373
+ - **`isEnabled: false` right after DMO creation is normal** — not a readiness
374
+ signal; do not wait for it before mapping.
375
+ - **The `__c` suffix is universal on ingested fields.** Mapping field dev names
376
+ are `Field_Name__c` on both sides — do not strip it, do not add a numeric
377
+ suffix here. Confirm the actual names from the materialized table first.
378
+ - **`mappings` + `refreshConfig` + `advancedAttributes` are part of create, not
379
+ optional commentary.** `targetFieldReturntype` is UPPERCASE (`TEXT` /
380
+ `NUMBER` / `DATE`); field `dataType` is capitalized (`Text` / `Number` /
381
+ `Date`). Mapping return types are only those five — DLO `Email` / `Phone` /
382
+ `Url` / `Percent` / `Currency` still map via `TEXT` or `NUMBER`. File refresh
383
+ is `TOTAL_REPLACE` + `"frequencyType": "None"` (`None`
384
+ is the only valid frequency for one-time uploads and for federated
385
+ Direct_Access).
386
+ - **Mapping is not idempotent.** `DUPLICATE_DLO_TO_DMO_MAPPING` means the pair
387
+ is already mapped — stop. `ERROR` on `get_dlo_to_dmo_mapping_status` means
388
+ the mapping failed; do not re-POST create.
389
+
390
+ ---
391
+
392
+ ## C. Ingest from an existing database / lakehouse connection
393
+
394
+ Same DLO → DMO → mapping tail as §A. The front half is connection discovery
395
+ instead of SfDrive upload. Connection **creation** is not on this server —
396
+ reuse an existing connection.
397
+
398
+ 1. **`list_connections`** — `connectorType` is REQUIRED, **case-sensitive**, and
399
+ cased inconsistently. Database / lakehouse connectors are UPPERCASE
400
+ (`SNOWFLAKE`, `BIGQUERY`, `REDSHIFT`, `DATABRICKS_UNITY`); others are
401
+ CamelCase (`UploadedFiles`, `AwsS3`, `SalesforceDotCom`, `Databricks`).
402
+ Omitting it returns 400 `ILLEGAL_QUERY_PARAMETER_VALUE` *"ConnectorType must be provided"*. A 400 *"ConnectorType [X] is not supported"* almost always
403
+ means the string is cased wrong (`"Snowflake"` is rejected; `"SNOWFLAKE"`
404
+ returns the connection) — match an exact string; do not brute-force casing.
405
+ Reuse the echoed `connectorType` for follow-up calls. `label` / `devName`
406
+ are EXACT-match, not prefix/contains. `limit` is 1–299 (default 50);
407
+ `offset` must be ≥ 0 (default 0); out of range → 400. A 200 with an empty
408
+ collection means no connection matched the filter, not a failure — do not
409
+ retry; report "none found". Empty collection means no connection matched.
410
+ If the org is not provisioned for CDP / connections, the upstream returns
411
+ a typed `INTERNAL_ERROR` that includes a tenant id (sometimes a Java
412
+ stacktrace fragment on infer / list-objects). That is a **provisioning
413
+ gap**, not a bad `connectorType` — do not retry, brute-force casing, or
414
+ treat it as a client payload bug. Same convention on
415
+ `get_connection`, `test_existing_connection`,
416
+ `list_data_connection_objects`, and `infer_object_schema`.
417
+
418
+ **By-id companion: `get_connection`.** When you already have a
419
+ `connectionId` from `list_connections` and need that one record's full
420
+ detail (`label`, `developerName`, `connectorType`, config), call
421
+ `get_connection`. Unknown id is a typed error, not a crash: a malformed
422
+ `connectionId` → 400 `"Entity ID Not Valid."`; a well-formed but
423
+ non-existent id → 404 `"Invalid Connection Id"`. Report the error; do
424
+ not retry. Unprovisioned org is the same CDP `INTERNAL_ERROR` + tenant
425
+ id as `list_connections` — a provisioning gap, not a client bug. Do not
426
+ surface the raw connection id unless the user needs it; present the
427
+ label / connector type. For the singleton UploadedFiles connection after
428
+ a file PUT, `get_upload_connection` is the more specific tool. To test
429
+ reachability, `test_existing_connection`.
430
+
431
+ 2. **`test_existing_connection`** (optional, once) — live round-trip; **do not call it in a loop** or poll it. `connectionId` is an 18-char Salesforce id beginning `9cg` (from `list_connections`), **not** a developer name, label,
432
+ or setup URL. A malformed id → 400 *"Entity ID Not Valid."*; well-formed
433
+ missing id → 404. HTTP **a 200 alone does NOT mean healthy** — read the
434
+ `success` flag. A failed test is a real connection failure; do not retry
435
+ until the stored config is fixed. Unprovisioned CDP: same `INTERNAL_ERROR`
436
+ + tenant id as `list_connections` — do not retry.
437
+
438
+ 3. **`list_data_connection_objects`** — list tables. `advancedAttributes` may
439
+ be `{}` when the connection already pins database/schema, or carry
440
+ uppercase `DATABASE` / `SCHEMA` to scope. Restrict to tables:
441
+
442
+ ```json
443
+ {
444
+ "filters": { "filtersByProperty": [
445
+ { "name": "objectTypes", "filterOperator": "EqualsOp",
446
+ "values": ["StructuredData"] }
447
+ ]}
448
+ }
449
+ ```
450
+
451
+ Omit `filters` for file/Excel sources. Present names; each selected table
452
+ becomes a separate DLO.
453
+
454
+ 4. **`infer_object_schema`** — `connectionId` from `list_connections`;
455
+ `resourceName` = the **table name** (same value you will pass as create's
456
+ object). Body: `SCHEMA` (uppercase) for the schema; add `DATABASE`
457
+ (uppercase) when the connection does not already pin a database. The table
458
+ itself is the path `resourceName`, not an advancedAttribute. Remember
459
+ database / schema / table for create. Same Date `format` forward rule as
460
+ §A. Confirm the schema with the user before create.
461
+
462
+ 5. **`create_data_stream`** — `connectorInfo.connectorDetails.name` is the
463
+ connection **developer name** from `list_connections` (e.g.
464
+ `My_Snowflake_Conn`), **not** the connector type `"SNOWFLAKE"`. Set
465
+ **`dataAccessMode`: `"Direct_Access"`** (federated / zero-copy External
466
+ DLO). Do **not** set a top-level `datasource` field — the federated path
467
+ rejects it with *"DataSource name should be empty for External data streams"*. `refreshConfig` is the same `TOTAL_REPLACE` +
468
+ `"frequencyType": "None"` as files (Upsert / recurring frequency are
469
+ rejected on this non-accelerated path). Do not send `incrementalColumn` /
470
+ `deleteColumn`.
471
+
472
+ **`advancedAttributes` location keys — two-step casing:**
473
+ 1. First attempt: lowercase `object`, `database`, `schema` (documented
474
+ Connect API contract). Live path: this casing is accepted on the first
475
+ attempt; do not skip it.
476
+ 2. Fallback **only** if that fails with *"<name> attribute is required, but missed"*: resend the **same** request with uppercase `objectName`,
477
+ `DATABASE`, `SCHEMA`. Send only one casing per attempt. Infer uses
478
+ uppercase `SCHEMA`/`DATABASE`; create tries lowercase first — do not
479
+ collapse those into one rule.
480
+ 3. After create, `get_data_stream` may echo `DATABASE` / `SCHEMA` /
481
+ `objectName` (uppercase) even when lowercase was accepted on input.
482
+ That is storage/normalization, not the create contract — do not rewrite
483
+ the next `create_data_stream` to match the echo.
484
+
485
+ **A table already `inUse` by another stream fails opaquely — not a payload
486
+ bug.** `create_data_stream` against a table some other stream already
487
+ targets returns the same generic `INTERNAL_ERROR` *"Unable to create a
488
+ data-stream. <name>__dll"* that a casing or field mistake produces, so a
489
+ failure here does not mean the casing fallback or field list is wrong.
490
+ `list_data_connection_objects`'s response carries a per-object `inUse` flag
491
+ (not called out in its own description) — check it for the target table
492
+ before create. If `inUse: true`, pick a different, unused table rather than
493
+ retrying the same table with different casing/field-count variations, which
494
+ will not help.
495
+
496
+ Reference shape (database source). Substitute apiNames from `list_*`:
497
+
498
+ ```jsonc
499
+ {
500
+ "name": "snowflake_orders",
501
+ "label": "Snowflake Orders",
502
+ "connectorInfo": { "connectorType": "DataConnector",
503
+ "connectorDetails": { "name": "<list_connections.developerName>" } },
504
+ "dataAccessMode": "Direct_Access",
505
+ "sourceFields": [
506
+ { "name": "ORDER_ID", "dataType": "Text" },
507
+ { "name": "ORDER_DATE", "dataType": "Date", "format": "yyyy-MM-dd" }
508
+ ],
509
+ "dataLakeObjectInfo": {
510
+ "name": "snowflake_orders__dll",
511
+ "label": "Snowflake Orders",
512
+ "category": "Other",
513
+ "dataspaceInfo": [{ "name": "default" }],
514
+ "fields": [
515
+ { "name": "ORDER_ID", "label": "Order ID", "dataType": "Text", "isPrimaryKey": true },
516
+ { "name": "ORDER_DATE", "label": "Order Date", "dataType": "Date", "isPrimaryKey": false }
517
+ ]
518
+ },
519
+ "mappings": [
520
+ { "sourceFieldLabel": "ORDER_ID", "targetFieldName": "ORDER_ID", "targetFieldReturntype": "TEXT" },
521
+ { "sourceFieldLabel": "ORDER_DATE", "targetFieldName": "ORDER_DATE", "targetFieldReturntype": "DATE" }
522
+ ],
523
+ "refreshConfig": { "refreshMode": "TOTAL_REPLACE", "frequency": { "frequencyType": "None" } },
524
+ "advancedAttributes": {
525
+ "object": "<table>",
526
+ "database": "<database>",
527
+ "schema": "<schema>"
528
+ }
529
+ }
530
+ ```
531
+
532
+ 6. **Skip `run_data_stream` entirely for `Direct_Access` streams — go straight
533
+ from `ACTIVE` to `run_query`.** A federated/zero-copy DLO queries the
534
+ source table live; there is no ingestion run to trigger. Calling
535
+ `run_data_stream` on one of these streams fails deterministically with
536
+ `DATACLOUD_API_CLIENT_EXCEPTION "Connector type <X> is not allowed to
537
+ process now"` — a server-side guard on any `Direct_Access`/streaming/
538
+ file-federated stream, not a
539
+ payload, casing, or timing problem, and not fixable by retrying, waiting
540
+ longer, or trying `interactive: true` (also illegal for DB sources, for the
541
+ unrelated fast-ingest reason above). Confirmed live: once
542
+ `get_data_stream`'s DLO `status` reaches `ACTIVE`, `run_query SELECT
543
+ COUNT(*) FROM <dll>` already returns real row counts pulled live from the
544
+ source — no run step needed or possible. Go straight from `ACTIVE` to §A's
545
+ SCHEMA REALITY CHECK through DMO + mapping.
546
+
547
+ (This guide only ever creates `Direct_Access` streams for database
548
+ sources — see step 5 above — so `run_data_stream` never applies to the
549
+ database path in this flow. The `interactive: false` /
550
+ accept-not-finished / verify-via-`run_query` guidance a few paragraphs up
551
+ is for **file** sources only.)
552
+
553
+ ---
554
+
555
+ ## B. Enrichment — metric gotchas beyond SKILL.md Step 8
556
+
557
+ SKILL.md Step 8 has the calc-measure / metric basics. These three rules were
558
+ each a failed call in the run — encode them:
559
+
560
+ 1. **A metric's `aggregationType` may never be `Auto`.** Rejected with *"The
561
+ aggregation type (Auto) is not allowed for metric aggregation."* Use a
562
+ concrete enum: `Sum`, `Count`, `Average`, `Min`, `Max`. (`Auto` is valid only
563
+ on a *row-level calculated measure*, not on a metric.)
564
+ 2. **`insightsSettings.identifyingDimension` is REQUIRED on every metric** — the
565
+ bare `measurementReference` + `timeDimensionReference` shape in older notes is
566
+ incomplete. Point the identifier at the grain's primary key, and list that
567
+ same field in top-level `additionalDimensions[]` (create fails with
568
+ `Insight dimension (...) is missing from the metric additional dimensions`).
569
+ Verified shape:
570
+
571
+ ```jsonc
572
+ {
573
+ "modelApiNameOrId": "Brightleaf_Sales",
574
+ "apiName": "Total_Sales", "label": "Total Sales",
575
+ "measurementReference": { "tableFieldReference": { "tableApiName": "Orders", "fieldApiName": "Sales" } },
576
+ "timeDimensionReference": { "tableFieldReference": { "tableApiName": "Orders", "fieldApiName": "Order_Date" } },
577
+ "aggregationType": "Sum",
578
+ "timeGrains": ["Day", "Week", "Month", "Quarter", "Year"],
579
+ "sentiment": "SentimentTypeUpIsGood",
580
+ "insightsSettings": {
581
+ "identifyingDimension": {
582
+ "identifierDimensionReference": {
583
+ "tableFieldReference": { "tableApiName": "Orders", "fieldApiName": "Order_ID" }
584
+ }
585
+ }
586
+ },
587
+ "additionalDimensions": [
588
+ { "tableFieldReference": { "tableApiName": "Orders", "fieldApiName": "Order_ID" } }
589
+ ]
590
+ }
591
+ ```
592
+
593
+ 3. **To count records, point `measurementReference` at a *measurable* field with
594
+ `aggregationType: "Count"` — NOT at a dimension/Primary Key.** Counting via the Primary Key
595
+ (`Order_ID`) is rejected: *"Table field … field name: (Order_ID) was not
596
+ found in the model"* — because `Order_ID` is a dimension, not a measurement.
597
+ Use a numeric measure column (e.g. `Sales`) as the `measurementReference` and
598
+ `aggregationType: "Count"`; it counts one row per record. The
599
+ `identifyingDimension` can still be `Order_ID`.
600
+
601
+ 4. **A metric whose `measurementReference.calculatedFieldApiName` is a *row-level
602
+ ratio* calc measure needs `aggregationType: "Average"`** (or another concrete
603
+ agg), not `Auto`. E.g. a `Profit_Margin` calc = `[Orders].[Profit] /
604
+ [Orders].[Sales]` with row-level `Auto` becomes a metric with
605
+ `aggregationType: "Average"`.
606
+ 5. **Preflight `additionalDimensions` cardinality before finalizing a metric.**
607
+ Detail view auto-groups by every dimension listed in `additionalDimensions`;
608
+ if the grouped result exceeds ~5,000 rows the metric detail becomes
609
+ unopenable, even though `create`/`update` itself succeeds. This is
610
+ especially damaging on **LOCKED** models, where the metric can't easily be
611
+ fixed after the fact. Verify each candidate `additionalDimensions`
612
+ combination with a grouped `run_semantic_query` *before* finalizing the
613
+ metric, and omit, filter, or replace any dimension whose grouped
614
+ cardinality exceeds the cap. (Keep the skill **Date-type exception**: the
615
+ server rejects `Date` fields in `additionalDimensions` — do not mirror a
616
+ Date insight/filter field there.)
617
+
618
+ 6. **`identifyingDimension` MAY point at a field on a joined object**
619
+ (cross-object identifying dimension). The identifier does not have to live
620
+ on the same object as `measurementReference`.
621
+
622
+ 7. **Inputs must be a JSON object, NOT a stringified JSON string.** Passing a
623
+ stringified body fails at the tool layer.
624
+
625
+ 8. **Optional `secondaryTimeComparison`** alongside `primaryTimeComparison`
626
+ (`None` / `PreviousYear` / `PriorPeriod`) — a dual comparison window, not
627
+ a replacement for the primary.
628
+
629
+ 9. **Metric PUT recovery.** `update_semantic_model_metric` is full PUT — GET
630
+ via `get_semantic_model_metric` first. Omitting `measurementReference` →
631
+ HTTP 500 NPE; `{description: "x"}` → `"Required fields are missing:
632
+ [MasterLabel]"` (the payload field is `label`); `{}` → 500 NPE on
633
+ `Parameter.getValue()`. Path is `metricNameOrId`.
634
+
635
+ ---
636
+
637
+ *For visualization authoring (Step 10), see `references/viz-authoring.md`.*
638
+ *For dashboard building (Step 11), see `references/dashboard-authoring.md`.*