@salesforce/afv-skills 1.59.0 → 1.61.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.
- package/package.json +1 -1
- package/skills/agentforce-architecture-analyze/SKILL.md +66 -17
- package/skills/agentforce-architecture-analyze/assets/cli/show_access_token.yaml +4 -7
- package/skills/agentforce-architecture-analyze/references/contract.json +1 -1
- package/skills/agentforce-architecture-analyze/scripts/emit_env.py +2 -2
- package/skills/agentforce-architecture-analyze/scripts/main.py +40 -44
- package/skills/agentforce-architecture-analyze/scripts/rest_client.py +11 -3
- package/skills/agentforce-architecture-analyze/scripts/sf_cli.py +77 -10
- package/skills/agentforce-architecture-analyze/scripts/tests/test_end_to_end_fixture.py +1 -0
- package/skills/agentforce-architecture-analyze/scripts/tests/test_flow_children_inflation.py +1 -0
- package/skills/agentforce-architecture-analyze/scripts/tests/test_main_pipeline.py +53 -2
- package/skills/agentforce-architecture-analyze/scripts/tests/test_resolve_creds.py +184 -36
- package/skills/agentforce-architecture-analyze/scripts/tests/test_rest_client.py +17 -0
- package/skills/agentforce-architecture-analyze/scripts/tests/test_sf_cli.py +165 -11
- package/skills/agentforce-architecture-analyze/scripts/tests/test_skill_md_pipeline_block.py +319 -0
- package/skills/agentforce-d360-analyze/SKILL.md +28 -10
- package/skills/agentforce-d360-analyze/scripts/dc.py +200 -50
- package/skills/agentforce-d360-analyze/scripts/discover_sessions.py +10 -6
- package/skills/agentforce-d360-analyze/scripts/fetch_dc.py +22 -6
- package/skills/agentforce-d360-analyze/scripts/resolve_session.py +21 -13
- package/skills/agentforce-d360-analyze/scripts/tests/test_dc_and_resolve_session.py +298 -76
- package/skills/agentforce-d360-analyze/scripts/tests/test_default_target_org.py +432 -0
- package/skills/agentforce-d360-analyze/scripts/tests/test_discover_sessions.py +1 -1
- package/skills/agentforce-d360-analyze/scripts/tests/test_resolve_session_main.py +9 -3
- package/skills/agentforce-d360-analyze/scripts/tests/test_skill_md_prefix_block.py +176 -0
- package/skills/consumer-goods-accruals-datakit-deploy/SKILL.md +17 -27
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/setup.js +7 -17
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/00-check-prerequisites.js +47 -63
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/01-download-static-resource.js +10 -13
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/02-replace-org-id.js +46 -46
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/03-deploy-metadata.js +4 -21
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/06-create-dataspace.js +58 -82
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/07-deploy-accruals-reports.js +16 -315
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/08-deploy-ui.js +9 -24
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/09-completion.js +44 -44
- package/skills/consumer-goods-accruals-datakit-deploy/scripts/steps/utils.js +11 -63
- package/skills/consumer-goods-rtr-datacloud-export-configure/SKILL.md +2 -1
- package/skills/consumer-goods-tpe-dashboard-configure/SKILL.md +2 -1
- package/skills/consumer-goods-tpe-dashboard-custom-kpi-configure/SKILL.md +2 -1
- package/skills/consumer-goods-tpe-datakit-deploy/SKILL.md +2 -1
- package/skills/data360-schema-get/SKILL.md +3 -3
- package/skills/design-systems-slds-validate/references/report-format.md +1 -1
- package/skills/dx-org-manage/SKILL.md +1 -0
- package/skills/dx-org-shape-manage/SKILL.md +1 -0
- package/skills/dx-org-snapshot-manage/SKILL.md +172 -0
- package/skills/dx-org-snapshot-manage/examples/create_error_output.json +16 -0
- package/skills/dx-org-snapshot-manage/examples/create_success_output.json +19 -0
- package/skills/dx-org-snapshot-manage/examples/delete_output.json +9 -0
- package/skills/dx-org-snapshot-manage/examples/get_output.json +19 -0
- package/skills/dx-org-snapshot-manage/examples/list_output.json +36 -0
- package/skills/dx-org-snapshot-manage/references/cli_flags.md +179 -0
- package/skills/dx-org-snapshot-manage/references/snapshot_usage.md +55 -0
- package/skills/dx-org-trial-expiration-check/SKILL.md +1 -1
- package/skills/dx-org-trial-expiration-check/scripts/check_expiration.sh +2 -2
- package/skills/education-cloud-student-recruitment-agent-configure/SKILL.md +7 -5
- package/skills/education-cloud-student-recruitment-agent-configure/references/prerequisites.md +11 -11
- package/skills/experience-content-media-image-generate/SKILL.md +83 -0
- package/skills/experience-content-media-image-generate/scripts/download-image.sh +84 -0
- package/skills/external-diagram-mermaid-generate/SKILL.md +12 -12
- package/skills/field-service-data-capture-migrate/references/component-mapping.md +1 -1
- package/skills/field-service-data-capture-migrate/references/fsm-dc-capability-catalog.md +1 -2
- package/skills/field-service-data-capture-migrate/references/known-deploy-errors.md +1 -0
- package/skills/field-service-data-capture-migrate/references/transformer-rules.md +1 -1
- package/skills/field-service-data-capture-migrate/scripts/transform_flow.py +12 -1
- package/skills/field-service-data-capture-migrate/scripts/vendor/build_flow.py +4 -1
- package/skills/field-service-setup-orchestrator-get/SKILL.md +0 -1
- package/skills/insurance-brokerage-agency-billing-configure/SKILL.md +233 -0
- package/skills/insurance-brokerage-agency-billing-configure/references/accounting-setup.md +117 -0
- package/skills/insurance-brokerage-agency-billing-configure/references/billing-rules-config.md +237 -0
- package/skills/insurance-brokerage-agency-billing-configure/references/billing-settings.md +271 -0
- package/skills/insurance-brokerage-agency-billing-configure/references/demo-policy-config.md +138 -0
- package/skills/insurance-brokerage-agency-billing-configure/references/org-defaults.md +64 -0
- package/skills/insurance-brokerage-agency-billing-configure/references/permissions-config.md +226 -0
- package/skills/insurance-brokerage-agency-billing-configure/references/prerequisites.md +142 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/SKILL.md +3 -2
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-execution-state-and-recovery.md +3 -3
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-overview.md +68 -45
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-visit-creation-data.md +17 -18
- package/skills/life-sciences-kam-coordinate/references/stage-5-data-and-plan-templates-overview.md +16 -3
- package/skills/life-sciences-kam-coordinate/references/stage-5-data-creation-data.md +16 -13
- package/skills/marketing-message-test/SKILL.md +194 -0
- package/skills/marketing-message-test/references/api-contract.md +333 -0
- package/skills/omnistudio-callable-apex-generate/SKILL.md +3 -3
- package/skills/omnistudio-datamapper-generate/SKILL.md +6 -6
- package/skills/omnistudio-datapacks-deploy/SKILL.md +11 -11
- package/skills/omnistudio-dependencies-analyze/SKILL.md +5 -5
- package/skills/omnistudio-epc-catalog-generate/SKILL.md +6 -6
- package/skills/omnistudio-flexcard-generate/SKILL.md +6 -6
- package/skills/omnistudio-integration-procedure-generate/SKILL.md +6 -6
- package/skills/omnistudio-omniscript-generate/SKILL.md +6 -5
- package/skills/platform-apex-logs-debug/SKILL.md +1 -0
- package/skills/platform-metadata-deploy/SKILL.md +1 -0
- package/skills/platform-models-api-configure/SKILL.md +5 -5
- package/skills/platform-soql-query/SKILL.md +1 -0
- package/skills/service-itsm-agentic-setup-cmdb-coordinate/SKILL.md +17 -6
- package/skills/service-itsm-agentic-setup-incident-sla-configure/SKILL.md +107 -192
- package/skills/service-itsm-agentic-setup-incident-sla-configure/assets/attach-milestone-action.json +6 -6
- package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/milestone-patterns.md +49 -20
- package/skills/service-itsm-agentic-setup-incident-sla-configure/examples/output-templates.md +4 -4
- package/skills/service-itsm-agentic-setup-incident-sla-configure/references/mcp-invocation.md +224 -290
- package/skills/service-itsm-agentic-setup-incident-sla-configure/references/workflow.md +295 -0
- package/skills/service-itsm-incident-priority-configure/SKILL.md +1 -1
- package/skills/service-itsm-slack-itservice-configure/SKILL.md +2 -1
- package/skills/service-itsm-slack-itservice-configure/references/manage-slack-connection.md +2 -2
- package/skills/service-omni-channel-setup-coordinate/SKILL.md +20 -13
- package/skills/service-omni-channel-setup-coordinate/scripts/integration-driver.sh +5 -55
- package/skills/service-omni-channel-setup-coordinate/scripts/tests/test_omni_contracts.py +0 -38
- package/skills/service-omni-command-center-analyze/SKILL.md +2 -3
- package/skills/service-omni-command-center-analyze/scripts/analyze.sh +3 -3
- package/skills/tableau-next-asset-create/README.md +65 -0
- package/skills/tableau-next-asset-create/SKILL.md +409 -0
- package/skills/tableau-next-asset-create/references/admin-users.md +149 -0
- package/skills/tableau-next-asset-create/references/ai-readiness.md +355 -0
- package/skills/tableau-next-asset-create/references/alerts.md +111 -0
- package/skills/tableau-next-asset-create/references/build-workflow.md +819 -0
- package/skills/tableau-next-asset-create/references/dashboard-authoring.md +302 -0
- package/skills/tableau-next-asset-create/references/dashboard-design-principles.md +352 -0
- package/skills/tableau-next-asset-create/references/data-transform-gotchas.md +210 -0
- package/skills/tableau-next-asset-create/references/data-understanding.md +387 -0
- package/skills/tableau-next-asset-create/references/deep-reference-index.md +74 -0
- package/skills/tableau-next-asset-create/references/destructive-operations.md +62 -0
- package/skills/tableau-next-asset-create/references/edit-dashboard.md +273 -0
- package/skills/tableau-next-asset-create/references/edit-visualization.md +529 -0
- package/skills/tableau-next-asset-create/references/empty-source-handling.md +104 -0
- package/skills/tableau-next-asset-create/references/ingest-and-metric-gotchas.md +638 -0
- package/skills/tableau-next-asset-create/references/insight-bundle.md +69 -0
- package/skills/tableau-next-asset-create/references/large-flat-file-handling.md +331 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/_envelope.json +54 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/_shared-widget-fields.json +122 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/add_page.json +29 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/delete_page.json +27 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/discard_dashboard.json +11 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/move_widget.json +30 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/move_widget_to_page.json +30 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/place_widget_on_page.json +30 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/remove_widget_from_dashboard.json +16 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/remove_widget_from_page.json +26 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/rename_page.json +28 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/save_dashboard.json +18 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/undo.json +11 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_button_widget.json +69 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_container_widget.json +88 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_filter_widget.json +146 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_metric_widget.json +78 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_navigation_widget.json +80 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_parameter_widget.json +160 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_text_widget.json +103 -0
- package/skills/tableau-next-asset-create/references/schemas/edit-dashboard/upsert_visualization_widget.json +68 -0
- package/skills/tableau-next-asset-create/references/sdm-tool-reference.md +721 -0
- package/skills/tableau-next-asset-create/references/semantic-query-and-enrichment.md +355 -0
- package/skills/tableau-next-asset-create/references/shared-gates.md +322 -0
- package/skills/tableau-next-asset-create/references/sharing-and-promotion.md +306 -0
- package/skills/tableau-next-asset-create/references/tasks/ai-readiness-audit.md +51 -0
- package/skills/tableau-next-asset-create/references/tasks/analyze-data.md +48 -0
- package/skills/tableau-next-asset-create/references/tasks/build-dashboard.md +66 -0
- package/skills/tableau-next-asset-create/references/tasks/build-end-to-end.md +73 -0
- package/skills/tableau-next-asset-create/references/tasks/create-data-transform.md +51 -0
- package/skills/tableau-next-asset-create/references/tasks/create-viz.md +58 -0
- package/skills/tableau-next-asset-create/references/tasks/edit-dashboard.md +80 -0
- package/skills/tableau-next-asset-create/references/tasks/edit-sdm.md +106 -0
- package/skills/tableau-next-asset-create/references/tasks/edit-viz.md +40 -0
- package/skills/tableau-next-asset-create/references/tasks/enrich-model.md +68 -0
- package/skills/tableau-next-asset-create/references/tasks/ingest-flat-file.md +68 -0
- package/skills/tableau-next-asset-create/references/tasks/manage-alert.md +35 -0
- package/skills/tableau-next-asset-create/references/tasks/profile-dataset.md +47 -0
- package/skills/tableau-next-asset-create/references/tasks/promote-or-reuse.md +46 -0
- package/skills/tableau-next-asset-create/references/tasks/provision-user.md +39 -0
- package/skills/tableau-next-asset-create/references/tasks/query-model.md +92 -0
- package/skills/tableau-next-asset-create/references/tasks/review-verified-questions.md +42 -0
- package/skills/tableau-next-asset-create/references/tasks/share-asset.md +37 -0
- package/skills/tableau-next-asset-create/references/verified-questions.md +125 -0
- package/skills/tableau-next-asset-create/references/viz-authoring.md +992 -0
- package/skills/service-de-channel-consent-configure/SKILL.md +0 -230
- 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`.*
|