1.What deploys onto an attendee org
Every artifact below is Sandbox-grade metadata, deployed via the standard sf CLI against the attendee's OrgFarm scratch. No production tenants touched, no cross-org access, no shared state.
- Sales Cloud SDM — standard OrgFarm-shipped Semantic Data Model. 12 data objects, 16 metrics, 35 calc measurements, 15 calc dimensions. shipped by image Attendees author on top of it; nothing rewrites its base state.
- CK-DataCloud-Access 2GP —
04tJ80000011MXvIAM, Unlocked, no namespace, promoted onqbranch-eca-dev’sdevorgDev Hub. AssignsGenieUserEnhancedSecurityperm set +GenieDataPlatformStarterPsllicense. 83% code coverage verified against workshoplwc2 - ~142 Lightning Web Components — deployed via
sf project deploy startfromforce-app/. Canonical Tableau Next semantic-query LWCs plus supporting Apex controllers. All namespace-safe. - ~25 industry data kit schemas — DLOs + SDMs, schema only. NO row data at deploy time. Row load is gated behind the OAuth scope ask (see §2). pending scope grant
- Optional post-install Apex bootstrap —
sf apex runagainst a self-contained snippet that assignsGenieUserEnhancedSecurity+ PSL to the installing user. Documented inpackages/ck-datacloud-access/README.md. Idempotent (queriesPermissionSetAssignmentcount before insert).
# Attendee session start — invoked by the `cold-open` skill in Claude Code sf org login web # 2 clicks in a browser popup; produces a session for the current OrgFarm sf package install --package 04tJ80000011MXvIAM --wait 10 --no-prompt sf project deploy start --source-dir force-app/ --wait 10 sf apex run --file bootstrap/assign-perms.apex
2.OAuth scope ask (with curl evidence)
The 25 industry data kits load row data through the CDP tenant Bulk-Ingest API. That endpoint refuses a token minted without cdp_ingest_api scope. This is the only outstanding platform dependency.
Three additional OAuth scopes added to the shipped PlatformCLI Connected App (API name PlatformCLI, label “Salesforce CLI”) in the Sales Cloud Vibe Coding OrgFarm image spec:
cdp_ingest_api— Access CDP Ingestion APIcdp_query_api— Access CDP Query APIcdp_profile_api— Access CDP Profile API
Set path: Setup → App Manager → PlatformCLI → Manage → Edit Policies → Selected OAuth Scopes → add three → Save. Then bake into the image spec so all future pulls inherit.
# Token from `sf` CLI session, no CDP scopes $ TOKEN=$(sf org display --json | jq -r '.result.accessToken') $ TENANT="gjrg8nzsgfqw0n3dg82gcnbqg8.c360a.salesforce.com" $ curl -X POST "https://$TENANT/api/v1/ingest/jobs" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"object":"CK_Industry_Reviews","operation":"upsert"}' \ -w "\nHTTP %{http_code}\n" HTTP 400 # empty body — CDP tenant rejects under-scoped token with no error body
The same POST after the scope grant returns HTTP 201 with a job ID. Verified end-to-end against workshoplwc2 during the 8-path research (Path A).
| Approach | Result |
|---|---|
Metadata API ConnectedApp deploy with CDP scopes |
Rejected. Error: 'CdpIngestApi' is not a valid value for the enum 'ConnectedAppOauthAccessScope'. Enum does not expose any variant (CdpIngestApi, DataCloudIngestApi, etc.). Verified on API v67. |
2GP scope grant via CK-DataCloud-Access |
Same enum rejection. Package deploys successfully — perm-set + PSL layer works — but the ConnectedApp scope layer is unreachable through packaging metadata. |
sfdatakit__DeployDataKitComponents flow |
Schema-only. Deploys DMO shells + mappings from an installed managed package. Zero CSV load path. Confirmed via reverse-engineering hackathon-Q’s apex_knowledge_connector.py. |
Direct sObject INSERT into *__dll |
INSUFFICIENT_ACCESS_OR_READONLY. Verified with GenieAdmin perm set assigned. DLO sObjects are createable=false at the platform layer. |
| Bulk API 2.0 against DLO sObjects | No create access. describeSObjects() reports createable=false. Bulk 2.0 job creation returns FEATURE_NOT_ENABLED. |
Full 8-path research (Paths A–H, each with curl + HTTP status): /vibecoding/why/ and docs/dc-ingestion-research-2026-07-13.md.
3.What happens when scope IS granted (technical flow)
Four programmatic stages against the CDP tenant. All headless. Idempotent by name. Documented Path A in the research doc.
# Stage 1: create connector — HTTP 201, returns tenantSpecificEndpoint POST /services/data/v67.0/ssot/connections { "connectorType": "IngestApi", "name": "ck_industry_reviews", "label": "CK Reviews" } → 201 { id, name, tenantSpecificEndpoint } # Stage 2: register schema — HTTP 200 PUT /services/data/v67.0/ssot/connections/{id}/schema { "schemas": [ { "schemaType": "IngestApi", "name": "Reviews", "label": "Reviews", "fields": [ ... ] } ] } → 200 # Stage 3: create data stream — HTTP 201, auto-materializes DLO POST /services/data/v67.0/ssot/data-streams { "datastreamType": "INGESTAPI", "connectorDetails": { "events": [...] } } → 201 # Stage 4: bulk-ingest rows — HTTP 202, then chunked PUT of CSV parts POST https://{tenantSpecificEndpoint}/api/v1/ingest/jobs → 201 { jobId } PUT /api/v1/ingest/jobs/{jobId}/batches # CSV body → 202 PATCH /api/v1/ingest/jobs/{jobId} { "state": "UploadComplete" } → 200
~90 sec per kit end-to-end. 25 kits at parallelism 3 = ~15 min for the full fleet on a fresh org.
Connector already exists → reuse the ID from GET /ssot/connections?connectorType=IngestApi. Schema field collision → PATCH the schema. Safe to re-run.
4.Blast radius + reversibility
This is the core concern — and it is bounded by design. Every scope, every write, every artifact is contained to the attendee's 15-day scratch org.
PlatformCLI Connected App — not a customer-installed app. It applies to attendee scratch orgs pulled from that image. Production tenants inherit nothing.edition=Enterprise, provisioned via HOT Pass). Auto-expire. No renewal path attendee-side.sf org login web is scoped to the attendee’s single org. It cannot reach any non-attendee-namespaced org. Every write is org-local. No cross-tenant traversal.PackageInstallRequest (2GP install), PermissionSetAssignment (perm bootstrap), LoginHistory (OAuth handshakes), ApexClass.LastModifiedDate. Standard Salesforce audit stack, unmodified.cold-open skill writes ~/.workshop/attendee.json at session start. All authored assets are prefixed with that namespace — sheets, dashboards, calc fields, LWC assets. Re-running a workshop never collides. The panic-reset skill tears down that namespace via a targeted destructiveChanges.xml deploy in under 60 sec.# List active tokens for the Connected App $ curl -s "$INSTANCE_URL/services/data/v67.0/query" \ -H "Authorization: Bearer $TOKEN" \ --data-urlencode "q=SELECT COUNT() FROM OauthToken WHERE AppName='Salesforce CLI'" # Revocation is Setup-UI; API path via ToolingApi.ConnectedApplication: $ curl -X PATCH "$INSTANCE_URL/services/data/v67.0/tooling/sobjects/ConnectedApplication/$ID" \ -H "Authorization: Bearer $TOKEN" \ -d '{"OauthConfig": {"Scopes": ["Api","RefreshToken"]}}' # CDP scopes dropped
5.Precedent
This pattern is not novel. Two internal Salesforce precedents already ship elevated scopes into demo / workshop orgs through 2GP.
| Package | Version ID | What it does | Namespace |
|---|---|---|---|
| QBranch Connector | 04tWs000000hDYHIA2 |
OAuth connector for the Q Branch Generator. Beta, unpromoted. Uses ECA-based OAuth (documented that ECA scopes are not packagable — same blocker we hit). | none |
| QBranch Demo Metadata | 04tJ8000000kaPgIAI |
B2C/B2B Sales & Service Cloud demo metadata (Customer Segment, Loyalty Tier, LTV, RecordType scaffolding). Promoted. Currently installed across QBranch demo orgs. | none |
| CK-DataCloud-Access ours | 04tJ80000011MXvIAM |
Perm-set + PSL assignment for Data Cloud user role in Vibe Coding workshop orgs. 83% code coverage, verified against workshoplwc2. |
none |
sfdatakit |
Salesforce-internal | DMO schema deploy pattern used by Service Insights. Same “deploy metadata to attendee scratch, expect it to Just Work” contract. | sfdatakit |
Full audit of the hackathon-Q pattern (2GP file layout, install lifecycle, InstallHandler behavior on first-install-vs-upgrade): docs/q-hackathon-2gp-audit.md.
6.Timeline
Decision needed T-8. Everything downstream is already built and dependency-documented.
04tJ80000011MXvIAM promoted, ck-datacloud-access verified against workshoplwc2.docs/antoine-ask.md. Single Setup-UI change, baked into image spec for future pulls.post-scope-grant-run.sh fires against a fresh OrgFarm pull. 25 kits install in ~15 min at parallelism 3. Fleet dashboard confirms row counts.Workshop runs on the Sales Cloud SDM alone (12 DOs, 16 metrics, 35 calc measurements) — that story is complete without the industry kits. The kits become a T+30 follow-up push once the scope is granted. See docs/facilitator-scope-fallback.md for the rescoped run-of-show.
7.Read the source
Every claim above is backed by a committed artifact. Nothing on this page is speculative unless flagged.
docs/antoine-ask.md— the ask (Slack DM + email versions + fallback + SE-leader blurb)docs/scope-grant-risk-analysis.md— one-page risk table for security reviewdocs/dc-ingestion-research-2026-07-13.md— the 8-path research with curl transcripts per pathdocs/q-hackathon-2gp-audit.md— hackathon-Q package inventory + install-lifecycle audit.claude/skills/tableau-next-author/references/platform-gotchas.md— empirical corpus of 40+ platform limits + workaroundspackages/ck-datacloud-access/README.md— the 2GP package spec, install commands, verification querypackages/ck-datacloud-access/RISKS.md— R1–R5 risks + resolution status- GitHub repo: tcufrogger/tableau-vibe-coding-workshop-starter-kit (public)