@salesforce/afv-skills 1.46.0 → 1.47.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-observe/SKILL.md +32 -4
- package/skills/agentforce-observe/references/ahm-alerts.md +719 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/SKILL.md +275 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/README.md +32 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/SetCommentValue.cls +75 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/SetCommentValue.cls-meta.xml +5 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/interview-answers.json +13 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/payloads/copy.json +10 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/payloads/create.json +20 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/payloads/update.json +16 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/references/conventions-and-payload-rules.md +273 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/references/generate-and-wire.md +236 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/references/reference-example-set-comment-value.md +132 -0
- package/skills/consumer-goods-promotion-bo-api-deploy/references/smoke-and-verify.md +211 -0
- package/skills/dx-devops-project-manage/SKILL.md +197 -0
- package/skills/dx-devops-project-manage/examples/common-workflows.md +197 -0
- package/skills/dx-devops-project-manage/references/cli-commands.md +295 -0
- package/skills/dx-devops-project-manage/scripts/create-project.sh +48 -0
- package/skills/dx-devops-project-manage/scripts/list-projects.sh +51 -0
- package/skills/dx-devops-project-manage/scripts/update-project.sh +96 -0
- package/skills/education-cloud-academic-calendar-generate/SKILL.md +225 -0
- package/skills/education-cloud-academic-calendar-generate/examples/quarter-calendar.json +47 -0
- package/skills/education-cloud-academic-calendar-generate/examples/sample-output.md +57 -0
- package/skills/education-cloud-academic-calendar-generate/examples/semester-calendar.json +54 -0
- package/skills/education-cloud-academic-calendar-generate/references/calendar-systems.md +127 -0
- package/skills/education-cloud-academic-calendar-generate/references/date-validation.md +222 -0
- package/skills/education-cloud-academic-calendar-generate/references/foundation_prerequisites.md +40 -0
- package/skills/education-cloud-academic-calendar-generate/scripts/validate_calendar_dates.py +143 -0
- package/skills/education-cloud-course-catalog-migrate/SKILL.md +321 -0
- package/skills/education-cloud-course-catalog-migrate/references/gotchas-detail.md +16 -0
- package/skills/education-cloud-course-catalog-migrate/references/gotchas.md +16 -0
- package/skills/education-cloud-course-catalog-migrate/references/large-catalog-handling.md +42 -0
- package/skills/education-cloud-course-catalog-migrate/scripts/batch_courses.py +36 -0
- package/skills/education-cloud-course-catalog-migrate/scripts/detect_linked_courses.py +51 -0
- package/skills/education-cloud-course-catalog-migrate/scripts/detect_modality_variants.py +48 -0
- package/skills/education-cloud-course-catalog-migrate/scripts/resolve_api_version.py +43 -0
- package/skills/education-cloud-course-catalog-migrate/scripts/split_course_code.py +39 -0
- package/skills/education-cloud-course-catalog-migrate/scripts/validate_completeness.py +54 -0
- package/skills/education-cloud-multi-campus-configure/references/foundation_prerequisites.md +3 -5
- package/skills/education-cloud-student-recruitment-agent-configure/SKILL.md +177 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/agent-and-subagents.md +151 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/customer-narration.md +34 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/execution-model.md +54 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/flows.md +82 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/grounding.md +199 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/permissions.md +183 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/platform-enablement.md +82 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/prerequisites.md +158 -0
- package/skills/education-cloud-student-recruitment-agent-configure/references/routing.md +141 -0
- package/skills/experience-cms-brand-apply/SKILL.md +5 -5
- package/skills/experience-cms-brand-create/SKILL.md +2 -2
- package/skills/experience-cms-content-generate/SKILL.md +1 -0
- package/skills/experience-cms-content-render/SKILL.md +173 -0
- package/skills/experience-cms-content-render/assets/angular/DetailPage.component.ts +25 -0
- package/skills/experience-cms-content-render/assets/angular/MediaRenderer.component.ts +133 -0
- package/skills/experience-cms-content-render/assets/angular/TypeList.component.ts +38 -0
- package/skills/experience-cms-content-render/assets/angular/TypeRenderer.component.ts +90 -0
- package/skills/experience-cms-content-render/assets/angular/cms-content.component.ts +248 -0
- package/skills/experience-cms-content-render/assets/angular/cms-item.service.ts +100 -0
- package/skills/experience-cms-content-render/assets/react/DetailPage.tsx +20 -0
- package/skills/experience-cms-content-render/assets/react/MediaRenderer.tsx +129 -0
- package/skills/experience-cms-content-render/assets/react/TypeList.tsx +40 -0
- package/skills/experience-cms-content-render/assets/react/TypeRenderer.tsx +64 -0
- package/skills/experience-cms-content-render/assets/react/heuristicRenderer.tsx +310 -0
- package/skills/experience-cms-content-render/assets/react/useCmsItem.ts +129 -0
- package/skills/experience-cms-content-render/assets/shared/cmsContentType.ts +49 -0
- package/skills/experience-cms-content-render/assets/shared/cmsCore.types.ts +96 -0
- package/skills/experience-cms-content-render/assets/shared/externalRefs.ts +55 -0
- package/skills/experience-cms-content-render/references/bulk-loading.md +60 -0
- package/skills/experience-cms-content-render/references/codegen-guardrails.md +111 -0
- package/skills/experience-cms-content-render/references/detail-pages.md +87 -0
- package/skills/experience-cms-content-render/references/embed-recipes.md +127 -0
- package/skills/experience-cms-content-render/references/failure-modes.md +96 -0
- package/skills/experience-cms-content-render/references/heuristic-render-rules.md +131 -0
- package/skills/experience-cms-content-render/references/init-scaffold.md +122 -0
- package/skills/experience-cms-content-render/references/interaction-model.md +173 -0
- package/skills/experience-cms-content-render/references/package-api.md +106 -0
- package/skills/experience-cms-content-render/references/schema-sync.md +114 -0
- package/skills/experience-cms-content-render/references/styling-scopes.md +65 -0
- package/skills/experience-cms-content-render/references/verify.md +49 -0
- package/skills/experience-cms-content-type-generate/SKILL.md +2 -2
- package/skills/experience-content-media-stock-image-search/SKILL.md +5 -4
- package/skills/experience-search-coordinate/SKILL.md +198 -0
- package/skills/experience-search-coordinate/assets/search-payload-template.json +25 -0
- package/skills/experience-search-coordinate/references/content-route.md +313 -0
- package/skills/experience-search-coordinate/references/content-type-discovery.md +57 -0
- package/skills/experience-search-coordinate/references/media-route.md +172 -0
- package/skills/experience-search-coordinate/references/scope-resolution.md +14 -0
- package/skills/experience-ui-bundle-localize/SKILL.md +1 -1
- package/skills/experience-ui-bundle-localize/references/i18n-setup.md +5 -3
- package/skills/experience-ui-bundle-project-generate/SKILL.md +18 -14
- package/skills/experience-ui-bundle-project-generate/references/angular-project-generate.md +22 -0
- package/skills/experience-ui-bundle-project-generate/references/react-project-generate.md +20 -0
- package/skills/experience-ui-bundle-salesforce-data-access/SKILL.md +58 -54
- package/skills/experience-ui-bundle-salesforce-data-access/references/caching.md +6 -0
- package/skills/experience-ui-bundle-salesforce-data-access/references/graphiti-cli.md +2 -2
- package/skills/experience-ui-bundle-salesforce-data-access/references/migration.md +6 -0
- package/skills/experience-ui-bundle-salesforce-data-access/references/rest-and-integration.md +2 -1
- package/skills/experience-ui-bundle-salesforce-data-access/references/sdk-api.md +6 -0
- package/skills/experience-ui-bundle-site-generate/SKILL.md +59 -8
- package/skills/experience-ui-bundle-site-generate/references/configure-metadata-digital-experience.md +8 -3
- package/skills/experience-ui-bundle-site-generate/references/configure-metadata-language-settings.md +120 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/SKILL.md +336 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/orchestration-flow.md +143 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-application-flexipage-mapping.md +127 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-deploy-commands.md +116 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-lifesci-metadata-deploy.md +111 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-overview.md +312 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-profile-layout-assignments.md +171 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-state-tracking.md +64 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-trigger-handlers.md +122 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-4-user-provisioning-overview.md +335 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-4-user-provisioning-user-provisioning-details.md +140 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-execution-state-and-recovery.md +196 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-metadata-cache-generation.md +155 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-overview.md +307 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-visit-creation-data.md +211 -0
- package/skills/life-sciences-fieldsalesrep-coordinate/references/state-machine-and-changes.md +108 -0
- package/skills/life-sciences-kam-coordinate/SKILL.md +241 -0
- package/skills/life-sciences-kam-coordinate/references/orchestration-flow.md +152 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-application-flexipage-mapping.md +79 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-deploy-commands.md +131 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-kam-config-records.md +85 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-lifesci-metadata-deploy.md +112 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-overview.md +202 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-profile-layout-assignments.md +67 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-state-tracking.md +65 -0
- package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-trigger-handlers.md +123 -0
- package/skills/life-sciences-kam-coordinate/references/stage-4-participant-role-and-sprint.md +89 -0
- package/skills/life-sciences-kam-coordinate/references/stage-5-data-and-plan-templates-overview.md +337 -0
- package/skills/life-sciences-kam-coordinate/references/stage-5-data-creation-data.md +248 -0
- package/skills/life-sciences-kam-coordinate/references/stage-6-ipad-validation-script.md +35 -0
- package/skills/life-sciences-kam-coordinate/references/stage-6-metadata-cache-generation.md +155 -0
- package/skills/life-sciences-kam-coordinate/references/stage-6-user-provisioning-details.md +146 -0
- package/skills/life-sciences-kam-coordinate/references/stage-6-user-provisioning-overview.md +89 -0
- package/skills/life-sciences-kam-coordinate/references/state-machine-and-changes.md +114 -0
- package/skills/life-sciences-prerequisites-validate/SKILL.md +138 -0
- package/skills/life-sciences-prerequisites-validate/references/checks-org-settings.md +190 -0
- package/skills/life-sciences-prerequisites-validate/references/checks-user-and-package.md +211 -0
- package/skills/life-sciences-territory-configure/SKILL.md +217 -0
- package/skills/life-sciences-territory-configure/references/territory-metadata.md +262 -0
- package/skills/platform-dsar-policy-manage/SKILL.md +272 -0
- package/skills/platform-dsar-policy-manage/references/configure.md +106 -0
- package/skills/platform-dsar-policy-manage/references/export-and-history.md +123 -0
- package/skills/platform-dsar-policy-manage/references/gap-analysis-guide.md +150 -0
- package/skills/platform-dsar-policy-manage/references/gap-scan.md +129 -0
- package/skills/platform-dsar-policy-manage/references/headless-sor.md +59 -0
- package/skills/platform-dsar-policy-manage/references/report-format.md +59 -0
- package/skills/platform-dsar-policy-manage/scripts/tests/__init__.py +0 -0
- package/skills/platform-dsar-policy-manage/scripts/tests/test_validate_policy_tree.py +76 -0
- package/skills/platform-dsar-policy-manage/scripts/validate-policy-tree.py +130 -0
- package/skills/platform-salesforce-connect-adapter-generate/SKILL.md +359 -0
- package/skills/platform-salesforce-connect-adapter-generate/references/official-examples.md +69 -0
- package/skills/platform-salesforce-connect-adapter-generate/references/scenarios.md +187 -0
- package/skills/service-itsm-agentic-setup-cmdb-coordinate/SKILL.md +20 -27
- package/skills/service-native-voice-recording-transcription-configure/SKILL.md +47 -27
- package/skills/service-native-voice-recording-transcription-configure/references/thunderbird-voice-settings.md +13 -9
- package/skills/service-native-voice-recording-transcription-configure/scripts/enable-recording-transcription.sh +104 -45
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Admissions flows — inventory & clone recipe
|
|
2
|
+
|
|
3
|
+
Read at Workflow step 11 (the last of the agent-build block, after the agent is committed in step 9a and right before channel deploy — unauthenticated/Service path only, **not** the foundation phase). Package flows live in the `eduadmissions` namespace.
|
|
4
|
+
|
|
5
|
+
The whole flow lifecycle is **tier 1**: inventory, retrieve, clone (as override or from template), and edit run over the Connect-REST `/flowbuilder/*` surface (`dispatch`/`dispatch_readonly`); **activation** is the tier-1 Tooling `FlowDefinition` PATCH (step 4 below), and verify is a tier-1 `/query`. No tier-2/tier-3 fallback is needed for the clone itself.
|
|
6
|
+
|
|
7
|
+
> **WARNING: Sequencing — do this with the unauthenticated-agent build, not in the foundation phase.** This entire file is **unauthenticated/Service (ASA) agent path ONLY** — skip it entirely if you're only building the authenticated/Employee agent. The flows have no ordering dependency on the *agent* build (the override links by reference to the managed original, so packaged subagent actions keep resolving through it, and the only user-ish input is `DefaultUserOwnerId` = a System Admin Id, resolved inline from the running user (below) — not any agent's running user). But do not front-load it into the early permissions/OWD foundation block — it belongs with unauthenticated-agent creation and channel deploy. In a partial build, defer it until the ASA agent is actually being built.
|
|
8
|
+
|
|
9
|
+
## The two clone mechanisms (one endpoint, one linkage key apart)
|
|
10
|
+
|
|
11
|
+
Every clone is the **same** call — `POST /services/data/vXX/flowbuilder/flow/actions/save` (note the `/actions/` segment — the bare `/flowbuilder/flow/save` path 405s `METHOD_NOT_ALLOWED`, GET/HEAD only) with `saveType:"createNewFlow"`, fed the platform's own retrieved flow JSON. The mechanisms differ **only** by which key you add to `metadata`:
|
|
12
|
+
|
|
13
|
+
| Mechanism | Source flow property | Key you add to `metadata` | Populates | Runtime behavior |
|
|
14
|
+
|---|---|---|---|---|
|
|
15
|
+
| **Override** | `isOverridable:true` | `overriddenFlow: "<managed ApiName>"` **and set `isOverridable:false`** on the clone itself | `FlowDefinitionView.OverriddenFlowId` | Intercepts the managed original at runtime |
|
|
16
|
+
| **Create-from-template** | `isTemplate:true` | `sourceTemplate: "<template ApiName>"` + `isTemplate:false` | `FlowDefinitionView.SourceTemplateId` | Standalone new flow seeded from the template |
|
|
17
|
+
|
|
18
|
+
> **CRITICAL: an override clone must flip `isOverridable` to `false` on itself.** The retrieved managed source carries `isOverridable:true`; saving the clone as-is with that flag still `true` fails `FLOW_OVERRIDABLE_CANNOT_BE_OVERRIDE` ("a flow that overrides another can't itself be overridable"). Only the managed original stays overridable — the override clone must set `isOverridable:false` alongside adding `overriddenFlow`.
|
|
19
|
+
|
|
20
|
+
Do **not** use `POST /flowbuilder/flow/actions/clone` on a managed source — it tries to modify the managed `InteractionDefinitionVersion` and fails `MANAGED_INSTALLED`. `save createNewFlow` creates a *new* unmanaged flow and is the one recipe for both buckets.
|
|
21
|
+
|
|
22
|
+
> **CRITICAL: An overridable flow accepts only ONE override — a pre-existing override blocks a fresh clone.** If the managed original is already overridden (by a customer's own customization, or a prior/partial SRA build), a second override `save` still returns `201 isSuccess:true` but with `status:"InvalidDraft"` and a `FLOW_ALREADY_OVERRIDDEN` warning — activation-blocking, not a hard error. **WARNING: The step-1 `FlowDefinitionView.OverriddenFlowId` read does NOT reliably detect this** — an inactive/Draft override leaves `OverriddenFlowId` null yet still blocks a new one. So on a `FLOW_ALREADY_OVERRIDDEN` warning, the existing override named in the message is the one to reuse or remove — do not create a duplicate; adopt the existing override clone (or delete it first if it's a stale partial), rather than assuming a null `OverriddenFlowId` means the slot is free.
|
|
23
|
+
|
|
24
|
+
## Flow inventory & the 2-wave ordering
|
|
25
|
+
|
|
26
|
+
The 6 flows fall into three roles: **reusable subflows** (called by others), **consumers** (which call those subflows), and one **standalone** (`GetPlnCampaigns` — no dependencies, rides in wave 1 only because nothing blocks it). The dependency is between subflows and consumers, creating a hard producer→consumer ordering: **clone AND activate the wave-1 flows first** (the 2 subflows + the standalone), then clone the wave-2 consumers and re-point them at the active subflow clones. (If a consumer references a still-Draft subflow clone, its save returns a `SUBFLOW_NO_ACTIVE_VERSION` warning that blocks activation.)
|
|
27
|
+
|
|
28
|
+
| Wave | Flow (label) | Package API name | Role | Mechanism |
|
|
29
|
+
|---|---|---|---|---|
|
|
30
|
+
| **1** | EDU Admissions: Get Planned Campaigns | `GetPlnCampaigns` | standalone (unauthenticated campaign read) | override, no edits |
|
|
31
|
+
| **1** | EDU Admissions: Process Person Account | `ProcPersAcct` | reusable subflow | create-from-template |
|
|
32
|
+
| **1** | EDU Cloud: Inquiry: Academic Interest Processing | `InquiryAIProcessing` | reusable subflow | create-from-template |
|
|
33
|
+
| **2** | EDU Admissions: Create Campus Tour Registration | `CreateCampusTourRgstr` | consumer | override + subflow edits |
|
|
34
|
+
| **2** | EDU Admissions: Process Academic Interest | `ProcessAcademicInterest` | consumer | override + subflow edits |
|
|
35
|
+
| **2** | EDU Admissions: Create Inquiry | `CreateInquiry` | consumer | override + subflow edits |
|
|
36
|
+
|
|
37
|
+
> The two create-from-template flows ship **Draft-only** (no active version) — expected, since a template is instantiated rather than run directly. Their clones must be activated for wave 2 to reference them.
|
|
38
|
+
>
|
|
39
|
+
> `CreateInquiry` ships wired to **no** packaged subagent by design — it gets wired into the customer-built escalation subagent, added by the customer in step 9a (see `agent-and-subagents.md`). It creates Case + Educational Info Request + Academic Interest.
|
|
40
|
+
|
|
41
|
+
## The clone lifecycle (tier 1, per flow)
|
|
42
|
+
|
|
43
|
+
All calls over Headless `dispatch`/`dispatch_readonly`; substitute the org's current API version for `vXX` (the flow verify `/query` needs **v62.0+** as a minimum — see the API version policy in `execution-model.md`).
|
|
44
|
+
|
|
45
|
+
1. **Retrieve the managed source** (`dispatch_readonly`): `GET /services/data/vXX/flowbuilder/flow/{activeVersionId}` (or `{ApiName}-{versionNumber}`, or `LatestVersionId` for a Draft-only template) → `body.flow.metadata` is the flow body to edit. (Managed flows ARE readable here.)
|
|
46
|
+
2. **Edit the metadata** (no call — mutate `flow.metadata` in place): add the linkage key for the mechanism (`overriddenFlow`, or `sourceTemplate` + `isTemplate:false`). **For an override clone, also set `isOverridable:false`** on the clone's own metadata — the retrieved source carries `isOverridable:true`, and saving it unchanged fails `FLOW_OVERRIDABLE_CANNOT_BE_OVERRIDE`. For every cloned flow set the run mode to system context (below). Consumers additionally swap subflows + set the owner constant (below).
|
|
47
|
+
3. **Save as new flow** (`dispatch`): `POST /services/data/vXX/flowbuilder/flow/actions/save` (the `/actions/` segment is required — the bare `/flowbuilder/flow/save` path returns `405 METHOD_NOT_ALLOWED`, GET/HEAD only) body `{ "saveType":"createNewFlow", "builderType":"FlowBuilder", "flow": { "fullName":"{newUnmanagedApiName}", "metadata": { ...edited... } } }` → `201 { isSuccess:true, status:"Draft", versionNumber:1, definitionId, flowId, errors:[], warnings:[...] }`. Inspect `warnings[]` even on success — `SUBFLOW_NO_ACTIVE_VERSION` / `SUBFLOW_DIFFERENT_RUNMODE` are activation-blocking and mean a wave-1 clone isn't active yet or a run-mode is mismatched; `FLOW_RUN_AS_SYSTEM_MODE_WITH*_CONTEXT_WARNING` is benign for a system-context flow on the unauthenticated path. (To edit an existing clone → same call with `"saveType":"saveAsNewVersion"` + `"currentFlowId":"{priorVersionFlowId}"`.)
|
|
48
|
+
4. **Activate** (`dispatch`): the working path is a Tooling `FlowDefinition` PATCH — query `FlowDefinition` by DeveloperName for the `300`-prefix def Id, then `PATCH /services/data/vXX/tooling/sobjects/FlowDefinition/{defId}` `{"Metadata":{"activeVersionNumber":N}}` (N = version number, 0 to deactivate) → **204**. This activates a version *by number* without rewriting it. **WARNING:** Do **not** use `PATCH /tooling/sobjects/Flow/{flowId}` `{Metadata:{status:'Active'}}` — it overwrites the version body and 400s `INVALID_STATUS` once a version has been active. The native `POST /flowbuilder/flow/{flowId}/actions/activate` endpoint is not reliably available (it 404s on some orgs) — attempt only if preferred, falling back to the FlowDefinition PATCH on a 404. Read `FlowDefinitionView.IsActive` first and only activate if false.
|
|
49
|
+
5. **Cold-verify** (`dispatch_readonly`): re-run the inventory query (below), don't trust the activate response alone.
|
|
50
|
+
|
|
51
|
+
### Common edit for every cloned flow — system-context run mode
|
|
52
|
+
|
|
53
|
+
Set `metadata.runInMode = "SystemModeWithoutSharing"` (the UI's *Show Advanced → How to Run the Flow → "System Context Without Sharing – Access All Data"*). **Mandatory** for the unauthenticated/Service path, not optional polish.
|
|
54
|
+
|
|
55
|
+
### Consumer edits (wave 2) — subflow swap + owner constant
|
|
56
|
+
|
|
57
|
+
For each wave-2 consumer, before `save`: repoint each subflow element's `flowName` to your **active** wave-1 clone (keep the element `name` unchanged so downstream dotted references like `{subflowName}.incomingPersonAccount.PersonContactId` stay valid), and set the `DefaultUserOwnerId` constant's `value.stringValue` to a System Admin user Id. **Always ask the customer** whether that owner should be the current (running) user or someone else — don't silently default. The running user is already required to be a System Administrator (see `prerequisites.md`), so offer it as the default: reuse the Id already resolved at Step 4a (`permissions.md`) — no need to re-derive it. If that Id isn't available, resolve it via `SELECT Id FROM User WHERE Username='<current user>'` over `dispatch_readonly`, then confirm before using it; if they name a different owner, look that user up instead (`SELECT Id FROM User WHERE …`).
|
|
58
|
+
|
|
59
|
+
- **`CreateCampusTourRgstr`** — swap the one `Process Person Account` subflow → active `ProcPersAcct` clone; input `incomingPersonAccount = {!PersonAccountDetails}`.
|
|
60
|
+
- **`ProcessAcademicInterest`** — swap TWO subflows: (a) `Process Person Account` → active `ProcPersAcct` clone, `incomingPersonAccount = {!PersonAccountDetails}`; (b) `Academic Interest Processing` → active `InquiryAIProcessing` clone, inputs `academicTermId = {!AcademicInterestDetails}`, `incomingPersonAccount = {!ProcessPersonAccount.incomingPersonAccount}`, `isNewPersonAccount = {!ProcessPersonAccount.isNewPersonAccount}`, `selectedLearningProgramIds = {!learningProgramIds}`; manually-assigned output `newAcademicInterestIds = {!academicInterestIds}`.
|
|
61
|
+
- **`CreateInquiry`** — same pattern as `ProcessAcademicInterest`.
|
|
62
|
+
|
|
63
|
+
## Verify (concrete call — run after cloning each wave)
|
|
64
|
+
|
|
65
|
+
Use the Data-API **`FlowDefinitionView`** (backs the Setup → Flows list) over `dispatch_readonly` — **not** Tooling `FlowDefinition`, which is blind to package-namespaced flows and returns 0 rows for these. Presence of the packaged flows is gated on `NamespacePrefix='eduadmissions'`, **not** on `InstalledSubscriberPackage` (which does not register these feature packages).
|
|
66
|
+
|
|
67
|
+
```text
|
|
68
|
+
GET /services/data/vXX/query/?q=
|
|
69
|
+
SELECT ApiName, Label, NamespacePrefix, ManageableState, IsActive,
|
|
70
|
+
ActiveVersionId, LatestVersionId, OverriddenFlowId, SourceTemplateId
|
|
71
|
+
FROM FlowDefinitionView
|
|
72
|
+
WHERE NamespacePrefix='eduadmissions'
|
|
73
|
+
AND ApiName IN ('GetPlnCampaigns','ProcPersAcct','InquiryAIProcessing',
|
|
74
|
+
'CreateCampusTourRgstr','ProcessAcademicInterest','CreateInquiry')
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Notes: `FlowDefinitionView` has **no `Status` field** (use `IsActive`); the field is **`ApiName`**, not `DeveloperName`; the WHERE clause does **not** support OR/disjunctions — use `IN (...)` or split into separate queries. To confirm a clone: query it by its new `ApiName` and check `ManageableState='unmanaged'`, `IsActive=true`, and the linkage field (`OverriddenFlowId` = the managed original, or `SourceTemplateId` = the template). This `/query` read is the tier-1 normal path; keep the `sf data query` (tier 2) / UI fallback ready for a transient `/query` outage window (see `execution-model.md` on the query-family caveat).
|
|
78
|
+
|
|
79
|
+
## Notes
|
|
80
|
+
|
|
81
|
+
- Help ref: `sfdo.ec_inquiry_flows_setup.htm`.
|
|
82
|
+
- These flows run in system context (`SystemModeWithoutSharing`, above) under the ASA's **Einstein Agent User** service account — there is no guest user (see `permissions.md`). Object access (e.g. Create on `Account`) comes from that account's `SRA_AI_Agent_Access` clone (of `EducationCloudAiAgentAccess`, built step 4b), not a guest profile.
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# Grounding — two independent mechanisms
|
|
2
|
+
|
|
3
|
+
Read at Workflow steps 7–8 and 9. SRA grounding is **two separate mechanisms — do not conflate them.**
|
|
4
|
+
|
|
5
|
+
1. **Knowledge grounding** (FAQ subagent) — answers over published Knowledge articles via a Knowledge-sourced **data library**. No Data Cloud involved. **Fully TIER 1** — article create/publish/verify and data-library create/index/verify all route over `dispatch`/`dispatch_readonly`.
|
|
6
|
+
2. **Data Cloud grounding** — structured Education Cloud data (Learning Program, Academic Term, PTAT) surfaced through **Data Cloud + hybrid-search indexes + retrievers + prompt templates**: 3 grounded objects/DMOs/prompt templates, but only **2** physical search-index/retriever builds (Academic Term shares the PTAT build; Application Timeline rounds out the data spine as a relationship source only, with no index/retriever/prompt template of its own — see Mechanism 2 below). **The data spine is TIER 1** (Connect REST over `dispatch`); the **index build, retriever create, and prompt-template edit are TIER 3 (Setup UI)** — no supported public API. There is **NO tier-2 `sf`-deploy path** for grounding metadata.
|
|
7
|
+
|
|
8
|
+
> **WARNING: Two mechanisms, do not conflate.** Mechanism 1 (Knowledge) is **fully tier 1** — article create/publish + data-library create over `dispatch`. Mechanism 2 (Data Cloud) is **three prompt-template-backed retrieval actions** (Learning Program, Academic Term, Program Term Application Timeline), not one — but only 2 of those 3 have their own search-index/retriever build; Academic Term grounds on the PTAT retriever. The data spine underneath maps a 4th DMO, Application Timeline, alongside those three — it backs 3 of the PTAT retriever's return fields by relationship but is never itself indexed or retrieved. Every verify below is a **tier-1 `/query`/`/tooling/query` attempt first**, `sf` as fallback — both route over `dispatch_readonly`.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Mechanism 1 — Knowledge grounding (step 7 + step 9 wiring) — FULLY TIER 1
|
|
13
|
+
|
|
14
|
+
The `AnswerQuestionsWithKnowledge` action (`EmployeeCopilot__AnswerQuestionsWithKnowledge`) answers over **published Knowledge articles** surfaced through a **Knowledge-sourced data library**. Articles must be **Published (Online)** and cover the 4 areas: **learning programs, support programs, application processes, campus & support information**. No specific article type/data category is mandated.
|
|
15
|
+
|
|
16
|
+
- **WARNING:** The FAQ action is **public** → articles must contain **NO non-public data**.
|
|
17
|
+
- Draft articles cause the agent to answer "no information" — the classic failure mode. Publish before testing.
|
|
18
|
+
- A Knowledge-sourced library indexes the **live** Knowledge object: once wired (step 9), newly **Published (Online)** articles matching the library's filters are picked up automatically on the index's refresh cycle — **no re-wiring or re-publish of the agent**. Drafts, archived, and filtered-out articles are excluded. So after the one-time wiring, ongoing article authoring alone keeps the agent current — tell the customer this so they don't expect to re-touch the agent per article.
|
|
19
|
+
|
|
20
|
+
### 7a — Author + publish a Knowledge article — 🟢 TIER 1
|
|
21
|
+
|
|
22
|
+
Content authoring is a human decision — **never auto-create articles.** Check first, then ask:
|
|
23
|
+
|
|
24
|
+
1. **Check for existing coverage first.** Query `Knowledge__kav` for `PublishStatus='Online'` (and `'Draft'`, to show what's in progress) and skim titles against the 4 required areas — **learning programs, support programs, application processes, campus & support information**. Do this **before** proposing any create.
|
|
25
|
+
2. **Always ask the customer — do not draft anything until they answer.** Report what you found (e.g. "You already have 6 Online articles; they look like they cover learning programs and application processes, but I don't see anything on campus & support info") and offer exactly one of three choices per gap, resolved on the spot rather than left open:
|
|
26
|
+
- **Draft placeholder content now**, for the customer to edit and finalize later.
|
|
27
|
+
- **The customer supplies the content now**, for you to create as draft articles.
|
|
28
|
+
- **Defer entirely** — the customer authors and publishes their own articles later, on their own schedule.
|
|
29
|
+
|
|
30
|
+
Only proceed to step 3 for the gaps where the customer picked the first or second option. Whichever way each gap resolves, **move on to 7b (the data library) immediately after** — library creation never waits on article content existing; anything published later is picked up automatically (see the Wiring note below).
|
|
31
|
+
3. **Prereq (once the customer has asked for a draft):** Lightning Knowledge enabled. Read the createable fields off `GET /services/data/vXX/sobjects/Knowledge__kav/describe` first — the required, non-defaulted fields are `Title` (string 255), `UrlName` (string 255, must be **unique**), and `Language` (picklist, e.g. `en_US`); the **body field API names are org/article-type-specific** (e.g. `Answer__c`, `Question__c`, `Detail__c`) — take them from describe (§7a step 1), do **not** hardcode. If the org's Knowledge object is a custom type, the `*__kav` API name differs too.
|
|
32
|
+
4. **Create the draft** — `POST /services/data/vXX/sobjects/Knowledge__kav` body `{"Title":"...","UrlName":"<unique-slug>","Language":"en_US","<bodyField__c>":"<html>"}` → 201 `{"id":"ka0…","success":true}`. New article starts `PublishStatus:"Draft"`, `VersionNumber:0`, with a `KnowledgeArticleId` (the `kA0…` durable id grouping versions).
|
|
33
|
+
5. **Publish** — `PATCH /services/data/vXX/knowledgeManagement/articleVersions/masterVersions/<articleVersionId>` body `{"publishStatus":"Online"}` → **204 No Content** (success). The `<articleVersionId>` is the `ka0…` `Knowledge__kav` row Id (the draft version), **NOT** the durable `KnowledgeArticleId`. (Add `"flagAsNew":true` for a major version on republish.) Publishing mutates the version row in place (Draft/v0 → Online/v1). Draft→Online is fully API-driven over `dispatch`; no UI needed. (Do **not** use a `publishKnowledgeArticles` invocable/Apex action — use the masterVersions PATCH.)
|
|
34
|
+
6. **Verify (attempt T1 `/query` → `sf` fallback):**
|
|
35
|
+
```bash
|
|
36
|
+
# Tier 1 first (dispatch_readonly): GET /services/data/vXX/query?q=<URL-encoded SOQL below>
|
|
37
|
+
# Tier 2 fallback:
|
|
38
|
+
sf data query -q "SELECT PublishStatus, COUNT(Id) total FROM Knowledge__kav GROUP BY PublishStatus" --target-org <alias>
|
|
39
|
+
sf data query -q "SELECT Id, Title, PublishStatus, KnowledgeArticleId FROM Knowledge__kav WHERE PublishStatus='Online'" --target-org <alias>
|
|
40
|
+
```
|
|
41
|
+
`PublishStatus='Online'` = live articles the FAQ action can read; `Draft` articles are invisible to the agent. (The aggregate `GROUP BY` read routes at tier 1.)
|
|
42
|
+
|
|
43
|
+
### 7b — Create the Knowledge-sourced data library — 🟢 TIER 1
|
|
44
|
+
|
|
45
|
+
The FAQ subagent grounds on a **data library** whose source is Knowledge (NOT Data Cloud — do not conflate with Mechanism 2). Creating it **auto-provisions the backing stream + search index + retriever** — no separate index-build step, no agent needed until the wiring at step 9.
|
|
46
|
+
|
|
47
|
+
- **Preconditions:** Data 360 (Data Cloud) set up; the running user needs **Data Cloud Admin AND System Administrator**; the org-access check on the create op is "Generative AI Setup enabled." Library indexing consumes Data 360 credits.
|
|
48
|
+
- **Tier 1 create:** `POST /services/data/vXX/einstein/data-libraries` over `dispatch`, body:
|
|
49
|
+
```json
|
|
50
|
+
{
|
|
51
|
+
"masterLabel": "Student Recruitment Knowledge Library",
|
|
52
|
+
"developerName": "SRA_Knowledge_Library",
|
|
53
|
+
"description": "<desc>",
|
|
54
|
+
"groundingSource": {
|
|
55
|
+
"sourceType": "KNOWLEDGE",
|
|
56
|
+
"knowledgeConfig": {
|
|
57
|
+
"primaryIndexField1": "Title",
|
|
58
|
+
"primaryIndexField2": "Summary",
|
|
59
|
+
"contentFields": ["<bodyField__c>"]
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
→ **201** returning `libraryId`, `dataSpaceScopeId`, `sourceType:"KNOWLEDGE"`, `status:"IN_PROGRESS"`, `featureAssignments:[]`. Use the **fixed `developerName` `SRA_Knowledge_Library`** (never a per-org-run name) so the step-9 wiring can re-find the library deterministically — by that exact developerName, not by `sourceType` (the org may already have other KNOWLEDGE libraries) and never by asking the customer what it was called. Re-running create with the same developerName returns a `DUPLICATE`-class error → treat as "already exists, read it back," not a failure. **BOTH `primaryIndexField1` AND `primaryIndexField2` are mandatory** (the UI's two "identifying fields" — text/textarea, keep ≤255 chars to stay under the 512-token cap); the create 400s naming each missing field in turn until both are present. **Content is the array key `contentFields`** (NOT `contentField1` — a singular key is silently dropped); the `*__kav` field API names come from describe (§7a step 1). Optional Knowledge filters live in `knowledgeConfig`: `isRestrictToPublicArticle`, `isDataCategoryRuleEnabled` + `dataCategorySelectionIds`/`Names` (SRA needs neither for the basic path). The KNOWLEDGE source skips the file-upload chain (`file-upload-urls`/`add-files`) — create alone kicks indexing.
|
|
65
|
+
- **Tier 3 fallback:** Setup → Agentforce → Data Library → New → Knowledge source, if the Connect call 403/404/501s on the org.
|
|
66
|
+
- **Verify (T1 readonly):** `GET .../einstein/data-libraries/<libraryId>` returns the full config — confirm `sourceType:"KNOWLEDGE"` and `contentFields` is non-empty; `GET .../einstein/data-libraries` lists all. **Don't wait on indexing to finish here** — wiring the library into the agent (step 9) only needs the `libraryId`, not a completed index, and indexing runs on its own async schedule (large sets can take 30 min–several hours) with nothing in this flow gated on it.
|
|
67
|
+
|
|
68
|
+
### Wiring (step 9) — attach the data library to the agent — 🟢 TIER 1 (T3 fallback)
|
|
69
|
+
|
|
70
|
+
Grounding is **one agent-level field**, not a per-subagent link. In the agent's NGA bundle AFScript, the top-level `knowledge:` block carries the binding:
|
|
71
|
+
|
|
72
|
+
```yaml
|
|
73
|
+
knowledge:
|
|
74
|
+
rag_feature_config_id = "ARFPC_<libraryId>"
|
|
75
|
+
citations_enabled: True
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
- **`rag_feature_config_id`** = the literal string `ARFPC_` + the data library's `libraryId` verbatim (e.g. library `1JDVW000001KWPl4AO` → `ARFPC_1JDVW000001KWPl4AO`). `ARFPC` = *Agent RAG Feature Config*; the id is derived from the library itself — you do **not** query a separate config object for it (`GenAiFeatureConfig`/`AiFeatureConfiguration` etc. are not valid sobjects here). **Re-read the `libraryId` here, don't rely on carrying it from step 7** — `GET /services/data/vXX/einstein/data-libraries` (readonly) lists all libraries with their Ids; match the row by the **exact `developerName` `SRA_Knowledge_Library`** (the fixed name from step 7b — not `sourceType`, which won't disambiguate from pre-existing KNOWLEDGE libraries).
|
|
79
|
+
- This one field grounds Knowledge **agent-wide**: every packaged subagent's existing `AnswerQuestionsWithKnowledge` action (source `EmployeeCopilot__AnswerQuestionsWithKnowledge`, target `standardInvocableAction://streamKnowledgeSearch`) reads `@knowledge.rag_feature_config_id`. There is **nothing to add per subagent** — the FAQ subagent's action list is byte-identical before and after wiring.
|
|
80
|
+
- **Fold it into step 9's initial bundle-create pass** — set the two lines above in the AFScript alongside the subagent edits. No `validate`/`publish` needed here (no new action target is being added); publish stays reserved for the customer's single Commit Version at step 9a (see `agent-and-subagents.md`).
|
|
81
|
+
- **The data library's own `featureAssignments` stays `[]`** — it is **not** the link (it's an output-only feature-family field; `PATCH /einstein/data-libraries/{id}` won't accept it). Do not treat a link as a `featureAssignments` write, and don't promise a `DataLibrary` MDAPI `sf project deploy` — there is no such metadata type.
|
|
82
|
+
- **Tier 3 fallback:** the Agentforce Builder data-library UI on the agent (Setup → the agent → connect data library) sets the same field — use it if the NGA lifecycle 403/404/501s.
|
|
83
|
+
- **Verify wiring:** re-GET the bundle version and confirm `knowledge.rag_feature_config_id` is `ARFPC_<libraryId>` (was `""`); the step-13 conversational smoke test (user-run in the deployed channel) then returns article-grounded, cited answers.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Mechanism 2 — Data Cloud grounding (step 8) — 3 retrieval actions, 2 retriever builds
|
|
88
|
+
|
|
89
|
+
Step 8 wires **3** prompt-template-backed retrieval actions — one per grounded object/DMO/prompt template, genuinely 3 of each. But only **2 physical search-index/retriever builds** back them, because Academic Term has no search index or retriever of its own — its prompt template grounds directly on the PTAT retriever:
|
|
90
|
+
|
|
91
|
+
| # | Retrieval action (retriever-backed) | Grounded object | DMO | Base prompt template (dev name) | Search index / retriever build |
|
|
92
|
+
|---|---|---|---|---|---|
|
|
93
|
+
| 1 | Get Learning Program Data | Learning Program | `ssot__LearningProgram__dlm` | `sturecruitment__getLearningPrograms` | **Learning Program build** |
|
|
94
|
+
| 2 | Get Academic Term Data | Academic Term | `ssot__AcademicTerm__dlm` | `sturecruitment__getAcademicTerms` | **PTAT build** (shared — see below) |
|
|
95
|
+
| 3 | Get Application Timeline Data | **Program Term Application Timeline** | `ssot__ProgramTermApplicationTimeline__dlm` | `sturecruitment__getApplicationTimelines` | **PTAT build** (shared — see below) |
|
|
96
|
+
|
|
97
|
+
> **CRITICAL: Academic Term has no dedicated search index/retriever — by design, not a gap to fill.** Its prompt template's own Data Model Object field is **Program Term Application Timeline**, not Academic Term, and its `GROUNDING_DATA` line reads `Retrievers:Program Term Application Timeline Retriever`. Do not build a separate Academic Term index/retriever — there's nothing to wire it to; both the Academic Term and PTAT rows above share the one PTAT retriever build (see the retriever field config below).
|
|
98
|
+
>
|
|
99
|
+
> **CRITICAL: Row 3's retrieval action grounds Program Term Application Timeline (PTAT), NOT ApplicationTimeline.** `GetApplicationTimelineData` (template `sturecruitment__getApplicationTimelines`) has **source object/DMO `ssot__ProgramTermApplicationTimeline__dlm`** — a different object than `ApplicationTimeline`. The retriever/index may be **labeled** "Application Timeline" or "PTAT" (matches the template's user-facing output), but the underlying object/DMO is PTAT. If a target org's field set ever looks off, the authoritative source is the base template's own `resultFieldApiKey=` references (`GET /einstein/prompt-templates/<devName>`), not the step's display name. **`ApplicationTimeline` itself still needs its own DMO mapped into the spine below** — the PTAT retriever's return fields reach 3 of its fields (Application Open/Close Date, Application Category) through a relationship lookup, and that lookup only resolves once `ApplicationTimeline` is mapped; it never gets a search index or retriever of its own.
|
|
100
|
+
|
|
101
|
+
> **CRITICAL: `GetProgramTermApplicationTimelineData` is a SEPARATE, deterministic STANDARD_INVOCABLE action — do NOT confuse it with the retriever-backed `GetApplicationTimelineData` above.** It invokes packaged flow/apex (complex active-program-term filtering retrievers don't support) and is **not** retriever-backed — do not wire a retriever/prompt template for it. (Naming collision, not a typo: the retriever action is `GetApplicationTimelineData`; the invocable action is `GetProgramTermApplicationTimelineData`.) The perm-set/OWD API name for the object is the truncated `ProgramTermApplnTimeline` — different surface, different spelling again.
|
|
102
|
+
|
|
103
|
+
> **Prompt-template scope — exactly 3 base-SRA templates, exclude the 2 Transfer Credit ones.** Five `promptTemplate/sturecruitment/` YAMLs exist, but namespace path alone does NOT prove SRA membership. In scope: `getLearningPrograms`, `getAcademicTerms`, `getApplicationTimelines`. **Out of scope (Transfer Credit Agent only, gated `orgHasTransferCreditEquivalencyAccess`): `getInstitutionData`, `getExternalLearningData`** — do NOT wire retrievers for these (consistent with the "do NOT include TransferCreditEquivalency" rule in `agent-and-subagents.md`).
|
|
104
|
+
|
|
105
|
+
> **Create-action types (not all flows):** `CreateAdmissionsApplication` is an **API** action (ConnectApi `Industries-Education.postPreliminaryApplicationReferences`) — currently deleted from the Admissions Application subagent at step 9a, see `agent-and-subagents.md`; `CreateCampusTourRegistration`, `CreateAcademicInterest`, and `GetCampusTourCampaigns` are **FLOW**. These read/grounding actions apply to **both** agent paths (auth/AEA + unauth/ASA), unlike the flow surgery (unauth-only — see `flows.md`).
|
|
106
|
+
|
|
107
|
+
> **Scope — the Learning Program *source records* are not this skill's job.** The programs, courses, academic calendar/terms, and campus hierarchy the agent retrieves are created by other Education Cloud skills (Academic Operations setup) and seeded as demo/customer data (same rule as the R&A domain objects — see `prerequisites.md` F-iii). This skill wires the *grounding* over that data; it does **not** create those records. Confirm the source data exists, but do not create it here.
|
|
108
|
+
|
|
109
|
+
### The tier split — data spine is T1, index/retriever/prompt are T3
|
|
110
|
+
|
|
111
|
+
- 🟢 **TIER 1 — prereq (separate from the agent-user perm-set clone) — grant 3 EDU system perms to the Data Cloud *Salesforce Connector* permission set.** The **Platform Integration User** that Data 360 uses to read CRM data authenticates through this permission set — it comes bundled with connecting the org to Data Cloud itself, not a separate grant to create. Before any EDU object ingests, enable System Permissions **Access Education Cloud Components** (`PermissionsUseEducationCloudComp`), **Access Education Cloud Objects** (`PermissionsAccessEducationCloud`), and **Access Education Cloud Review Features** (`PermissionsAccessEducationCloudReview`) — ref KB 001983512. This is separate from the Einstein-Agent-User clone in `permissions.md`; without it, the data streams silently ingest nothing for EDU objects.
|
|
112
|
+
- **CRITICAL:** `PATCH /services/data/vXX/sobjects/PermissionSet/<Id>` (standard Data API — `/tooling/sobjects/` accepts the same body but doesn't apply these 3 fields) body `{"PermissionsAccessEducationCloud":true, "PermissionsUseEducationCloudComp":true, "PermissionsAccessEducationCloudReview":true}`. Find `<Id>` by **Label**, not a hardcoded `Name` — some orgs show a legacy label (*Salesforce CDP Salesforce Connector Integration*, *Customer Data Platform Salesforce Connector Integration*, *Customer 360 Audiences Salesforce Connector Integration*) instead of "Data Cloud Salesforce Connector."
|
|
113
|
+
- **Verify (T1 `/query` → `sf` fallback):** `SELECT Id, Label, PermissionsAccessEducationCloud, PermissionsUseEducationCloudComp, PermissionsAccessEducationCloudReview FROM PermissionSet WHERE Label LIKE '%Salesforce Connector%'` — confirm all three read `true`.
|
|
114
|
+
- 🟢 **TIER 1 (Connect REST over `dispatch`) — the Data Cloud data spine, in order:**
|
|
115
|
+
1. **Verify the 4 standard DMOs are pre-provisioned** — do NOT create them. `GET /services/data/vXX/ssot/data-model-objects/ssot__<Object>__dlm` (`ssot__LearningProgram__dlm`, `ssot__AcademicTerm__dlm`, `ssot__ProgramTermApplicationTimeline__dlm`, `ssot__ApplicationTimeline__dlm`) → 200, `creationType:Standard`. An unmapped DMO reads `isEnabled:false`; a mapped one flips to `isEnabled:true` with `isMapped:true` on each mapped field.
|
|
116
|
+
2. **Create the CRM data stream per object** — `POST /services/data/vXX/ssot/data-streams`, one object per call. Body:
|
|
117
|
+
```json
|
|
118
|
+
{
|
|
119
|
+
"name": "<Object>_Home",
|
|
120
|
+
"connectorInfo": {
|
|
121
|
+
"connectorType": "SalesforceDotCom",
|
|
122
|
+
"connectorDetails": { "name": "SalesforceDotCom_Home", "sourceObject": "<CRMObjectApiName>" }
|
|
123
|
+
},
|
|
124
|
+
"dataLakeObjectInfo": {
|
|
125
|
+
"category": "Other",
|
|
126
|
+
"label": "<Object>_Home",
|
|
127
|
+
"name": "<Object>_Home__dll",
|
|
128
|
+
"dataspaceInfo": [ { "name": "default" } ]
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
→ 201; the DLO field list **auto-derives** from `sourceObject` (do not author fields). **Naming: use the connector default `<Object>_Home` / DLO `<Object>_Home__dll`** (NOT a custom `_SRA` suffix) so API-created and UI-created streams match and downstream references line up. **PTAT is the one exception — create it as `"name": "ProgramTermApplnTimeline_Home"`, `dataLakeObjectInfo.label`/`name`: `"ProgramTermApplnTimeline_Home"` / `"ProgramTermApplnTimeline_Home__dll"`** (the truncated CRM object spelling, not the full `ProgramTermApplicationTimeline`): Data Cloud provisions this one DLO under the truncated spelling regardless of what's requested, so asking for it directly keeps the name it's created under identical to the name every later step needs to reference. **Attempt create directly for each of the 4 objects — don't page through the existing streams list first.** The fixed naming above makes this safe: a duplicate-name error on create means that stream already exists → read it back, don't treat it as a failure (same idiom as the PermissionSet clone and Knowledge-library create above). **Body gotchas (each is a real 400):** no top-level `dataStreams` array wrapper; `connectorType` goes **inside** `connectorInfo`; do NOT include `connectorDetails.type` (output-only); exactly one of `dataLakeObjectInfo`/`existingDataLakeObjectInfo`; the dataspace key is `dataspaceInfo` (input side, entries take only `name` — the read side spells it `dataSpaceInfo` with `{label,name}`). `connectorDetails.name` `"SalesforceDotCom_Home"` is the OOTB CRM connector — if the org's differs, `GET /ssot/data-streams` and copy an existing SalesforceDotCom stream's `connectorDetails.name`.
|
|
133
|
+
3. **Never wait on `PROCESSING` here — always defer, unconditionally, with no status check at all yet.** A stream can only be PATCHed (step 4) while Active/Error/Inactive — `PROCESSING` happens right after creation, and again briefly after the step-4 PATCH — and how long it takes to clear scales with how much data is already in the org, so it's never safe to assume a quick check will catch it ready. **Move straight into step 9 (agent creation) without checking status even once here** — checking now only means polling in a loop or gambling on timing, and neither is worth it; narrate the deferral to the customer first, per SKILL.md's *Talking to the user*. **Steps 9 and 9a are one continuous unit for the customer (headless creation straight into their one Builder session) — don't break that up to check in between.** Check status for the first time at the end of step 9a, once that whole unit is done; if still `PROCESSING`, check again at the end of each subsequent step (10, 11, 12) — this must be complete, including the T3 index/retriever/prompt-template build, before step 13's final verify, which depends on it. Once all 4 read Active/Error/Inactive at one of those checkpoints, continue to step 4 then. **Do not attempt step 4 or step 5 in the same pass as creation, even opportunistically** — a stream created this turn is almost always still `PROCESSING`, and jumping ahead to `auto-map-dlos` (step 5) against it returns a not-found on the DLO. That not-found means "not ready yet," never "wrong name" — see the note on step 5 below.
|
|
134
|
+
4. **Fix Boolean/Date type mismatches (`AcademicTerm_Home` / `LearningProgram_Home` / `ApplicationTimeline_Home`).** The CRM connector always derives a source Boolean field as DLO type **Text** and a source Date field as DLO type **DateTime** — never the DMO's actual Boolean/Date. DMO mapping needs an exact type match, so these fields won't map until cast. Fix: `PATCH /services/data/vXX/ssot/data-streams/<streamName>` (write `dispatch`) with body `{"mappings":[{"targetFieldName":"<Name>__c","transformationFormula":"<expr>","targetFieldReturntype":"Boolean"|"Date"}, ...]}` — batch every field for a stream into the one array in a single PATCH. Two templates cover every field: **Boolean** — `IF(ISEMPTY(sourceField['<SourceField>']),null,IF(sourceField['<SourceField>'] == 'true', true, false))`; **Date** — `IF(ISEMPTY(sourceField['<SourceField>']),null,DAYPRECISION(sourceField['<SourceField>']))`. Apply:
|
|
135
|
+
|
|
136
|
+
| Stream | Target field | Type | Source field |
|
|
137
|
+
|---|---|---|---|
|
|
138
|
+
| `AcademicTerm_Home` | `Is_Active_Transformed__c` | Boolean | `IsActive` |
|
|
139
|
+
| `AcademicTerm_Home` | `Start_Date_Transformed__c` | Date | `StartDate` |
|
|
140
|
+
| `AcademicTerm_Home` | `End_Date_Transformed__c` | Date | `EndDate` |
|
|
141
|
+
| `LearningProgram_Home` | `Is_Active_Transformed__c` | Boolean | `IsActive` |
|
|
142
|
+
| `LearningProgram_Home` | `Is_Top_Level_Program_Transformed__c` | Boolean | `IsTopLevelProgram` |
|
|
143
|
+
| `LearningProgram_Home` | `Does_Create_Opportunity_Transformed__c` | Boolean | `DoesCreateOpportunityRecord` |
|
|
144
|
+
| `LearningProgram_Home` | `Active_From_Date_Transformed__c` | Date | `ActiveFromDate` |
|
|
145
|
+
| `LearningProgram_Home` | `Active_To_Date_Transformed__c` | Date | `ActiveToDate` |
|
|
146
|
+
| `ApplicationTimeline_Home` | `Application_Open_Date_Transformed__c` | Date | `ApplicationOpenDate` |
|
|
147
|
+
| `ApplicationTimeline_Home` | `Application_Close_Date_Transformed__c` | Date | `ApplicationCloseDate` |
|
|
148
|
+
| `ApplicationTimeline_Home` | `Decision_Release_Date_Transformed__c` | Date | `DecisionReleaseDate` |
|
|
149
|
+
| `ApplicationTimeline_Home` | `Graduation_Appln_Deadline_Transformed__c` | Date | `GraduationApplnDeadline` |
|
|
150
|
+
| `ApplicationTimeline_Home` | `Early_Action_Open_Date_Transformed__c` | Date | `EarlyActionOpenDate` |
|
|
151
|
+
| `ApplicationTimeline_Home` | `Early_Action_Close_Date_Transformed__c` | Date | `EarlyActionCloseDate` |
|
|
152
|
+
| `ApplicationTimeline_Home` | `Early_Application_Open_Date_Transforme__c` | Date | `EarlyApplicationOpenDate` |
|
|
153
|
+
| `ApplicationTimeline_Home` | `Early_Application_Close_Date_Transform__c` | Date | `EarlyApplicationCloseDate` |
|
|
154
|
+
| `ApplicationTimeline_Home` | `Early_Appln_Dec_Rel_Date_Transformed__c` | Date | `EarlyApplnDecisionRelDate` |
|
|
155
|
+
|
|
156
|
+
`ApplicationTimeline_Home` has no Boolean fields needing the cast — Date only, 9 fields, same template. **Only `Application_Open_Date_Transformed__c` and `Application_Close_Date_Transformed__c` back a return field the PTAT retriever actually uses** (Application Category, the 3rd return field sourced from Application Timeline, is a picklist and needs no cast); the other 7 exist on the object but aren't wired to a retriever field today — fix all 9 in the one PATCH regardless, so the mapping isn't left partially fragile.
|
|
157
|
+
|
|
158
|
+
`auto-map-dlos` (step 5) needs no further wait once this PATCH itself returns 200 — the brief `PROCESSING` it triggers (per step 3) clears on its own.
|
|
159
|
+
5. **Map the DLO to its standard DMO — all 4 in one pass, after step 4 lands for `AcademicTerm_Home`/`LearningProgram_Home`/`ApplicationTimeline_Home`.** Two Connect calls over the **write `dispatch`** (they are writes, so NOT `dispatch_readonly` — a `ROUTE_NOT_FOUND` here means the readonly dispatcher, not a version issue; use the org's current `vXX`, unless it's below this surface's documented floor — see `execution-model.md`):
|
|
160
|
+
1. Precheck: `POST /services/data/vXX/ssot/simple-start/sobject-recommendations` body `{"sobjectNames":["LearningProgram","AcademicTerm","ProgramTermApplnTimeline","ApplicationTimeline"]}` → 201, each returns `category:"Related"` (confirms they're opinionated/auto-mappable objects). **WARNING:** `sobjectNames` takes **source platform sObject** API names (like `LearningProgram`/`AcademicTerm`), so the PTAT entry is the **truncated** `ProgramTermApplnTimeline` — NOT the DMO's full-spelling `ssot__ProgramTermApplicationTimeline__dlm` (different surface — see `permissions.md`); `ApplicationTimeline` needs no such truncation. The full spelling does **not** error: the call still returns 201 but that object comes back **without a `category` field** (unrecognized), so it silently drops from the mapping. Send the truncated PTAT name and confirm every object echoes `category:"Related"`.
|
|
161
|
+
2. Map: `POST /services/data/vXX/ssot/simple-start/auto-map-dlos` body `{"dloNames":["<Object>_Home__dll"]}` → 201 `{"mappedDloNames":[...]}`. **CRITICAL: ONE DLO per call** — passing 2+ names → 500 INTERNAL_ERROR. Loop the objects, including `ProgramTermApplnTimeline` — it needs no step 4, but map it in this same pass rather than earlier; nothing downstream needs it before the other three are ready. **If a call 404s / reports the DLO not found, use the exact name each stream was created under in step 2** (PTAT's is the truncated `ProgramTermApplnTimeline_Home__dll` — see step 2) — there's nothing else to look up. A not-found on the right name means that stream is still `PROCESSING` (step 3) and hasn't materialized yet; re-verify with `GET /ssot/data-streams/<name>` if needed, then defer this one DLO's mapping to the next status checkpoint (per step 3) rather than paging through the streams list.
|
|
162
|
+
- Together, steps 1–5 = the **complete T1 grounding spine** — the bundle-deploy UI path is NOT required for these opinionated EDU objects. The mapping lands as `isMapped:true` on the DMO field metadata (the stream's `mappings[]` stays `[]` even when fully mapped — don't check there).
|
|
163
|
+
- **CRITICAL: TIER 3 (Setup UI) — no supported public API.** This whole leg is a hand-off: walk the customer through the Setup UI screen by screen rather than handing them this section's API/DMO vocabulary — name what a screen or field does for the agent ("this is the field the agent uses to tell students the term name"), not its dev name, matching the register in SKILL.md's *Talking to the user*. The exact field lists and filter below are what to translate live, not a script to read verbatim.
|
|
164
|
+
- **Build the hybrid-search index — 2 builds, not 3.** Data Cloud → Search Index → New → **Easy Setup** → pick the DMO → Save. Easy Setup ⇒ Search Type = **HYBRID**, auto-creates the chunk DMO + vector/index DMO, and auto-selects the fields to chunk — but **filtering fields are a separate, manual pick on this same Easy Setup screen, not automatic**, and there is no supported way to add one after Save. Set every filtering field listed below for the DMO being built, in this pass — an index saved without one can't be retro-fitted, so the retriever built on top of it will be permanently missing that filter option. Builds on Save — no separate build action. (A raw `POST /ssot/search-index` route exists but requires pre-authoring the chunk DMO/vector DMO/chunking config/embedding model Easy Setup derives in one click, and 500s if those don't pre-exist — not worth it vs. the 3-click UI; treat index create as **T3 Easy Setup**.) Field configuration, both hybrid, both embedding model **E5 Large V2**:
|
|
165
|
+
- **Learning Program** (DMO `ssot__LearningProgram__dlm`): 8 chunked/text fields (Passage Extraction) — Name, Academic Level, Cip Code, Description, Duration Unit, Learning, Provider, Short Description; 1 filtering field — Data Source.
|
|
166
|
+
- **Program Term Application Timeline** (DMO `ssot__ProgramTermApplicationTimeline__dlm`): 4 chunked fields — Academic Term Id, Application Timeline Id, Learning Program Id, Name; 3 filtering fields — Academic Term > Is Active, Learning Program > Learning Program Id, Data Source.
|
|
167
|
+
- Academic Term gets **no index of its own** — do not build one; its template grounds on the PTAT retriever below.
|
|
168
|
+
- **Create the retriever — 2 builds, not 3 — Einstein Studio Individual Retriever.** Retriever create/read has **NO API surface** (every `/ssot/retrievers`, `/connect/retrievers`, and `AiRetriever` tooling route 404s), so this is T3-only with no API existence check. Click-path: Data Cloud → **Einstein Studio → Retrievers tab → New Retriever → Individual Retriever** → Next → source = **Data Cloud**, data space = **default**, DMO + search index from the matching build above → Next → filters (see below) → **Configure Retriever Results** (see below) → Save → name it → **Activate** (quick action, upper-right). **CRITICAL: Retriever API names are auto-generated and non-deterministic** (pattern `<Name>_1Cx_yZB<8 hex>`) — cannot be hardcoded; read the API Name back from the retriever detail page (Overview → Retriever Details → API Name) to wire the template. Configuration:
|
|
169
|
+
- **Learning Program Retriever**: DMO Learning Program, no filters, 20 results, citations Off. Return fields (Direct Attributes → Learning Program): **Name, Learning Program Id**.
|
|
170
|
+
- **Program Term Application Timeline Retriever**: DMO PTAT, filter **`Is Active` Equal To `TRUE`** AND **`Learning Program Id` Equal To `{!$Learning_Program_Id}`** (the 2nd condition is marked **Dynamic**), 20 results, citations Off. Return fields (6, several via related-object lookups): **Academic Term Name** (Academic Term → Name), **Program Term Application Timeline Id**, **Application Open Date** (Application Timeline → Application Open Date), **Application Close Date** (Application Timeline → Application Close Date), **Application Category** (Application Timeline → Application Category), **Academic Term Id** (Academic Term → Academic Term Id). This one retriever backs both the Academic Term and PTAT rows in the table above.
|
|
171
|
+
- **Edit the prompt template to wire each retriever (T3 manual) — 3 templates, 2 retrievers.** Open each of the 3 base templates in Prompt Builder — **editing a managed `sturecruitment` template spawns a local `*_Override` template** (e.g. `getLearningPrograms_Override`, `IsOverride:true`, `ManageableState:unmanaged`); the base stays managed/read-only and gets `OverriddenTemplates:[...]`. Wire the retriever at its `PLACEHOLDER_FOR_DATA_RETRIEVER` line via **Insert Resource → Search → `<object>` → `<retriever>`** — the picker resolves the retriever **by label** so the customer never types the non-deterministic API suffix. If the picker isn't used, paste the API Name copied from the retriever detail page. Wiring per template:
|
|
172
|
+
- `getLearningPrograms` → **Learning Program Retriever**; Data Model Object **Learning Program**; search text `Input:learningProgramName`.
|
|
173
|
+
- `getAcademicTerms` → **Program Term Application Timeline Retriever**; Data Model Object **Program Term Application Timeline**; search text `Input:academicTermName`; pre-filter `Learning Program.Learning Program Id` Equal `Input:learningProgramId`.
|
|
174
|
+
- `getApplicationTimelines` → same retriever, DMO, and pre-filter as `getAcademicTerms`; search text `Input:academicTermName`.
|
|
175
|
+
Save & Activate. The wiring produces two changes: the Content placeholder becomes `{!$EinsteinSearch:<RetrieverApiName>.results}`, and a `GenAiPromptTemplateDataProvider` child is added (params `searchText` = `{!$Input:<inputParam>}`, `outputFieldNames` = JSON array of the retriever's return fields, `resultCount`). Sequence: build+activate the retriever FIRST, then wire the template(s) that use it — build the PTAT retriever once, then wire both `getAcademicTerms` and `getApplicationTimelines` to it. (No viable clone/create-automation for the override — the create body schema is unpublished; do it in Prompt Builder.)
|
|
176
|
+
|
|
177
|
+
> **WARNING: There is NO tier-2 `sf project deploy` path for grounding metadata** — `DataLibrary`/`SearchIndex`/`AIRetrieverDefinition`/`DataSemanticSearch`/`GenAiPromptTemplate`-retriever-wiring are not in the supported-metadata-type index, and MDAPI deploy does NOT build the index. Do not present tier 2 as a path for Mechanism 2. Attempt T1 for the data spine; everything downstream is T1-verify-only around a T3 UI build.
|
|
178
|
+
|
|
179
|
+
### T1 verify — per leg (each has a different surface; the retriever has NONE)
|
|
180
|
+
|
|
181
|
+
Most of the spine is verifiable at tier 1, but the surfaces differ by leg — do not use one query for all:
|
|
182
|
+
|
|
183
|
+
- **DMO mapped (data spine):** `GET /services/data/vXX/ssot/data-model-objects/ssot__<Object>__dlm` (dispatch_readonly) → assert `isEnabled:true` and the required fields show `isMapped:true`.
|
|
184
|
+
- **Search index built:** `GET /services/data/vXX/ssot/search-index` (list) or `GET .../ssot/search-index/<developerName>` (dispatch_readonly) → poll `runtimeStatus == "READY"` before wiring the retriever (a just-created index reads `null` while indexing, then flips to READY on its own).
|
|
185
|
+
- **Retriever: NO API existence check exists** (create/read have no API surface — see the T3 bullet). The retriever is **not** a Tooling sObject; do **not** attempt `SELECT ... FROM AIRetrieverDefinition`/`AiRetriever` (404s). Verify in the UI: Einstein Studio → Retrievers list shows it **Active**, and copy its API Name from the detail page.
|
|
186
|
+
- **Prompt template wired (T1):** `GET /services/data/vXX/einstein/prompt-templates/<devName>` (dispatch_readonly) → assert the active version's `Content` contains `{!$EinsteinSearch:<RetrieverApiName>.results}` and that a `GenAiPromptTemplateDataProviders` child with `Definition: invocable://getEinsteinRetrieverResults/<RetrieverApiName>` is present. (`GET .../einstein/prompt-templates` lists all.)
|
|
187
|
+
|
|
188
|
+
### Caveats
|
|
189
|
+
|
|
190
|
+
- **Keep the two index surfaces separate.** The Knowledge-sourced library (Mechanism 1) and the Data Cloud hybrid index (Mechanism 2) are distinct — do not attempt to share one index across both.
|
|
191
|
+
- **The prompt-template retriever wiring is a T3 UI step, but its verify is T1.** There is no supported Connect endpoint/body for inserting a retriever into a prompt template (the create-body schema is unpublished) — do the wiring in Prompt Builder. **Verify** it, though, with the tier-1 `GET /einstein/prompt-templates/<devName>` assertion above (Content merge field + `GenAiPromptTemplateDataProviders` child), not just a retriever read-back.
|
|
192
|
+
- **CRITICAL: DLO→DMO mapping of Boolean/Date fields is fragile — verify it, don't assume auto-map alone is enough.** `auto-map-dlos` leaves Boolean- and Date-sourced fields unmapped until the formula-field fix above is applied (the CRM connector derives them as Text/DateTime on the DLO, which won't type-match the DMO). Verify these mappings hold:
|
|
193
|
+
- **AcademicTerm (`ssot__AcademicTerm__dlm`)** — `ssot__Name__c` (= `Academic_Term_Name`) plus season map correctly out of the box; `IsActive`, `StartDate`, `EndDate` do not (Text/DateTime on the DLO) — need the Boolean/Date formula-field fix above before they'll map.
|
|
194
|
+
- **LearningProgram (`ssot__LearningProgram__dlm`)** — same story, 5 fields (`IsActive`, `IsTopLevelProgram`, `DoesCreateOpportunityRecord`, `ActiveFromDate`, `ActiveToDate`) — same fix, see the table above.
|
|
195
|
+
- **ApplicationTimeline (`ssot__ApplicationTimeline__dlm`)** — same story, 9 Date fields (no Booleans on this object) — `ApplicationOpenDate`, `ApplicationCloseDate`, `DecisionReleaseDate`, `GraduationApplnDeadline`, `EarlyActionOpenDate`, `EarlyActionCloseDate`, `EarlyApplicationOpenDate`, `EarlyApplicationCloseDate`, `EarlyApplnDecisionRelDate` — same fix, see the table above.
|
|
196
|
+
|
|
197
|
+
### Verify (either mechanism)
|
|
198
|
+
|
|
199
|
+
Step-13 conversational smoke test (user-run in the deployed channel) — the agent returns a Learning-Program-grounded (and Academic-Term / PTAT-grounded) answer to a program query, and a Knowledge-grounded answer to an FAQ query.
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# Permissions & sharing foundation
|
|
2
|
+
|
|
3
|
+
Read at Workflow steps 4–6, 9 & 10. Each create/change here walks the **three-tier ladder** (see `execution-model.md`): **tier 1** `/headless/metadata` if the type is allowlisted, else **tier 2** `sf`-deploy of local source (needs a shell), else **tier 3** Setup-UI. The tier per operation is called out below. Every step ends in a **concrete verify call** so the agent confirms org state after the change — do not assume success.
|
|
4
|
+
|
|
5
|
+
> **Tiering summary.** **OWD** (`CustomObject` `sharingModel`), **RecordType** create, `ApplicationRecordTypeConfig`, and the **Campaign Type picklist value** all have a **tier-1** path; only **sharing-rule create** and **record-type→profile visibility** are tier-3 gaps. **SOQL `/query`/`/tooling/query` route over `dispatch_readonly` (tier 1)** — so every verify below is a tier-1 attempt first, with `sf` (tier 2) / UI as the fallback (including for a transient `/query` outage window — see `execution-model.md`).
|
|
6
|
+
|
|
7
|
+
- **`PermissionSet` is on the `/headless/metadata` allowlist (CRUD).** But the **cloning path used here is plain sObject REST + the collections API, not `/headless/metadata`** (see Step 4b): `POST /sobjects/PermissionSet` → `POST /composite/sobjects` for the object/field-perm rows → `POST /sobjects/PermissionSetAssignment`. All tier 1, granular, per-row errors.
|
|
8
|
+
- **OWD (`CustomObject` `sharingModel`) — TIER 1.** `PUT /services/data/vXX/headless/metadata` with `{type:"CustomObject", fullName:"<obj>", xmlRep:"<CustomObject …><sharingModel>Read</sharingModel></CustomObject>"}` → `200 {success:true}`. Only **internal** OWD is set (external stays Private). **`ActionPlanTemplate` is the one exception — tier-3-UI-only**; see Step 5 for the root cause and the don't-hardcode rule.
|
|
9
|
+
- **`RecordType` create — TIER 1.** `POST /services/data/vXX/tooling/sobjects/RecordType` with the **nested** body `{FullName:"<Obj>.<DevName>", Metadata:{active:true, label:"<Label>"}}` → 201. A `400 JSON_PARSER_ERROR` means the body used flat columns instead of the nested `Metadata` object — fix the shape, don't fall back. See Step 6.
|
|
10
|
+
- **Campaign `Type` picklist value — TIER 1.** `PUT /headless/metadata type:StandardValueSet fullName:CampaignType` (whole-set replace). See Step 6.
|
|
11
|
+
- **Sharing-rule create is a tier-1/2 gap → tier-3 UI.** See Step 6b for the exact error and why the sibling deploy skills can't help either.
|
|
12
|
+
- **Record-type→profile visibility is a tier-3-UI step** (tooling PATCH on `Profile` is unsupported; the only API write is a whole-file `Profile` MDAPI replace, too high-blast-radius for a 3-click task). See Step 6.
|
|
13
|
+
|
|
14
|
+
## Configure vs. verify at a glance
|
|
15
|
+
|
|
16
|
+
| Step | Operation | Best tier | **Verify call** |
|
|
17
|
+
|---|---|---|---|
|
|
18
|
+
| 4a | Assign the **Admin/builder** persona's sets (the AI Agent Access **clone** goes to the Einstein Agent User at **step 9**, not here — that user doesn't exist until Claude creates it as part of step 9) | **T1** — `PermissionSetAssignment` sObject POST | query PSA (below) — attempt T1 `/query`, `sf` fallback |
|
|
19
|
+
| 4b | Clone + customize `EducationCloudAiAgentAccess` (**required** — OOTB set is an empty shell) | **T1** — sObject `POST /sobjects/PermissionSet` + `/composite/sobjects` for obj/field perms + PSA POST (NOT `/headless/metadata`) | query the cloned PS + its perms (below) |
|
|
20
|
+
| 5 | OWD → Public Read Only on 6 objects | **T1** — `PUT /headless/metadata CustomObject sharingModel` (5/6); **`ActionPlanTemplate` is T3-UI-only** (`IsCustomizable=false`) | tooling query `EntityDefinition.InternalSharingModel` — attempt T1 `/tooling/query`, `sf` fallback |
|
|
21
|
+
| 6a | Campaign "Recruitment Event" picklist value | **T1** — `PUT /headless/metadata StandardValueSet:CampaignType` (whole-set replace) | query picklist value (below) |
|
|
22
|
+
| 6b | Campaign "Campus Tours" sharing rule | **T3** — `SharingCriteriaRule`/`SharingOwnerRule` CREATE is a real MDAPI gap (fails at T1 AND `sf`) → UI | query sharing rule (below) |
|
|
23
|
+
| 6c | Individual Application record type | **T1** — `POST /tooling/sobjects/RecordType` nested `{FullName,Metadata}` | query `RecordType` (below) |
|
|
24
|
+
| 6d | `ApplicationRecordTypeConfig` (registers the RT into the admissions feature) | **T1** — `POST /tooling/sobjects/ApplicationRecordTypeConfig` | tooling query `ApplicationRecordTypeConfig` (below) |
|
|
25
|
+
| 6e | Record type → profile visibility (3 profiles) | **T3** — tooling PATCH on `Profile` unsupported; only API write is whole-file `Profile` MDAPI replace (too risky) → UI | tooling GET Profile `recordTypeVisibilities` (below) |
|
|
26
|
+
|
|
27
|
+
## Step 4 — Clone, customize, and assign the Education Cloud AI Agent Access permission set
|
|
28
|
+
|
|
29
|
+
> **CRITICAL: DO NOT assign the OOTB `EducationCloudAiAgentAccess` — it is an EMPTY SHELL.** The standard set (one of the 5 canonical EDU standard permission sets) ships with **0 object perms, 0 field perms, every `Permissions*` boolean false**. Assigning it to the Einstein Agent User grants **nothing**, yet reads like the agent has Education Cloud access — actively misleading. It exists to be **cloned FROM**. So **4b (clone + customize) is REQUIRED, not optional.** **CRITICAL: Order within Step 4: assign the Admin/builder persona to the running user FIRST (4a), THEN build the clone (4b)** — the builder sets (especially *Education Cloud Full Access* + *Einstein for Education Cloud Access*) are what make the **license-gated EDU object fields visible** to the running admin; without them the objects show **0 fields** in Object Manager and 4b's field-matrix build has nothing to reference. Only the clone's **Einstein-Agent-User** assignment is deferred to step 9 (that running user doesn't exist until agent creation at that step).
|
|
30
|
+
|
|
31
|
+
**Step 4a (DO THIS FIRST — before building the clone in 4b) — Assign the Admin/builder persona to the running user; DEFER the clone's Einstein-Agent-User assignment to step 9 (T1, concrete call):**
|
|
32
|
+
|
|
33
|
+
The clone's assignment to the **Einstein Agent User** cannot happen at step 4 — that running user doesn't exist yet, and Claude creates + grants it directly at **step 9** (see `agent-and-subagents.md` step 9, points 2–3; the full mechanism lives there, not here). What to do **here, before building the clone**: assign the **Admin/builder** persona's sets to the running user so the human can build — critically *Education Cloud Full Access* and *Einstein for Education Cloud Access*, which unlock visibility of the **license-gated EDU object fields**. **CRITICAL: Symptom if 4b runs first:** see the note above — assign these two sets first, then clone.
|
|
34
|
+
|
|
35
|
+
- **Tier 1 (headless):** `POST /services/data/vXX/sobjects/PermissionSetAssignment` via the write-enabled `dispatch` tool, one row per builder set, body `{ "AssigneeId": "<builderUserId>", "PermissionSetId": "<psId>" }`. sObject REST routes over dispatch. (`DUPLICATE_VALUE` on re-assign is benign/idempotent.)
|
|
36
|
+
- **Tier 2 (`sf` CLI, if a shell is present):** `sf org assign permset --name <builder set> --target-org <alias>`.
|
|
37
|
+
- **Tier 3 (manual):** Setup → Permission Sets → the set → Manage Assignments → Add Assignment → the builder user.
|
|
38
|
+
|
|
39
|
+
**Verify (attempt T1 `/query` over `dispatch_readonly` → `sf` fallback):**
|
|
40
|
+
```bash
|
|
41
|
+
# Tier 2 fallback:
|
|
42
|
+
sf data query -q "SELECT PermissionSet.Name, PermissionSet.Label FROM PermissionSetAssignment WHERE AssigneeId = '<builderUserId>'" --target-org <alias>
|
|
43
|
+
```
|
|
44
|
+
Confirm a row for each Admin/builder set from the persona table below (Agentforce Default Admin, Education Cloud Full Access, Einstein for Education Cloud Access, etc.) — a set missing from the results means the assignment didn't take.
|
|
45
|
+
|
|
46
|
+
**Step 4b — Clone + customize the full matrix (T1, sObject REST — NOT `/headless/metadata`):**
|
|
47
|
+
|
|
48
|
+
The tier-1 path is plain sObject REST + the collections API — cleaner and more granular than a metadata deploy:
|
|
49
|
+
|
|
50
|
+
1. **Create the clone** — `POST /services/data/vXX/sobjects/PermissionSet` body `{Name:"SRA_AI_Agent_Access", Label:"Education Cloud AI Agent Access (Student Recruitment Agent)", Description:"…", PermissionsAccessEducationCloud:true, PermissionsUseEducationCloudComp:true, PermissionsActionPlansUserAccess:true}` → 201. **Use the fixed `Name` `SRA_AI_Agent_Access`** (`Name` is the PermissionSet API/developer key — there is no separate `developerName` field on this sObject) so step 9 and the verify below re-find it by **exact `Name`, not a fuzzy `LIKE`**; a `DUPLICATE_DEVELOPER_NAME` on re-run means it already exists → read it back, don't treat as failure. **The three "system permissions" are boolean COLUMNS on the PermissionSet record**, set inline at create: *Access Education Cloud Objects* = `PermissionsAccessEducationCloud`; *Access Education Cloud Components* = `PermissionsUseEducationCloudComp`; *Access the Action Plans feature* = `PermissionsActionPlansUserAccess`. (Do NOT hunt for `CustomPermission`/`SetupEntityAccess` — wrong surface.)
|
|
51
|
+
2. **Object perms** — `POST /services/data/vXX/composite/sobjects` (`allOrNone:false`) with one `ObjectPermissions` row per object. Row shape: `{"attributes":{"type":"ObjectPermissions"}, "ParentId":"<clonePsId>", "SobjectType":"AcademicInterest", "PermissionsRead":true, "PermissionsCreate":true}` (omit or set `false` the perms you don't grant; `PermissionsRead` is required for any other object perm). **CRITICAL: `EducationalInfoRequest` Read requires `Case` Read** (`FIELD_INTEGRITY_EXCEPTION: depends on Read Case`) — the SRA help doc's object list omits `Case`; **add it**. Objects (all R unless noted): AcademicInterest (C,R), Account (C,R), AcademicTerm, ActionPlan, ActionPlanTemplate, ApplicationStageDefinition, ApplicationTimeline, Campaign, Contact, ContactRequest, EducationalInfoRequest, IndividualApplicationTask, Learning, LearningProgram, `PreliminaryApplicationRef`, `ProgramTermApplnTimeline`, **+ `Case` (R)**. (17 objects — the 16 above plus `Case`; `/composite/sobjects` caps at 200 rows/call, so one call covers all.)
|
|
52
|
+
3. **Field perms** — `POST /services/data/vXX/composite/sobjects` (`allOrNone:false`) with one `FieldPermissions` row per field. Row shape: `{"attributes":{"type":"FieldPermissions"}, "ParentId":"<clonePsId>", "SobjectType":"AcademicInterest", "Field":"AcademicInterest.ReceivedDate", "PermissionsRead":true, "PermissionsEdit":true}` — **CRITICAL: `SobjectType` is its own required column, even though `Field` is already fully-qualified** — omitting it 400s; it is not redundant with `Field`. `Field` is the fully-qualified `SobjectType.FieldName`, and `PermissionsEdit:true` requires `PermissionsRead:true` on the same row. Fields (R = `PermissionsRead` only; R+E = both): AcademicInterest{AcademicTermId, AccountId, ContactId, ContactRequestId, EducationalInfoRequestId, LearningProgramId, ReceivedDate, Subscription}=R+E; AcademicTerm{IsActive, StartDate}=R; ApplicationTimeline{ApplicationCategory, ApplicationCloseDate, ApplicationCloseDateTime, ApplicationOpenDate}=R; Contact{AccountId}=R; IndividualApplicationTask{ApplicationStageDefinitionId}=R; Learning{AcademicLevel}=R; LearningProgram{AcademicLevel, IsActive}=R; ProgramTermApplnTimeline{AcademicTermId, ActionPlanTemplateVersionId, ApplicationTimelineId, LearningProgramId}=R.
|
|
53
|
+
|
|
54
|
+
> **CRITICAL: API-name traps (fail hard on a wrong `SobjectType`/`Field`):** "Preliminary Application Reference" → **`PreliminaryApplicationRef`** and "Program Term Application Timeline" → **`ProgramTermApplnTimeline`** (both truncated — full spellings 404). Use exact API names; resolve field API names via `/sobjects/<Obj>/describe` (label→name) rather than `FieldDefinition` queries, which can 404 for metadata-entity lookups.
|
|
55
|
+
|
|
56
|
+
**Verify the clone was built correctly (attempt T1 `/query` over `dispatch_readonly` → `sf` fallback):**
|
|
57
|
+
```bash
|
|
58
|
+
# Tier 1 first (dispatch_readonly): GET /services/data/vXX/query?q=<the SOQL below, URL-encoded>
|
|
59
|
+
# Tier 2 fallback:
|
|
60
|
+
sf data query -q "SELECT Id, Name, Label, IsCustom, PermissionsAccessEducationCloud, PermissionsUseEducationCloudComp, PermissionsActionPlansUserAccess FROM PermissionSet WHERE Name = 'SRA_AI_Agent_Access'" --target-org <alias>
|
|
61
|
+
```
|
|
62
|
+
Confirm the clone is `IsCustom:true` with all three system booleans true (`PermissionsAccessEducationCloud`, `PermissionsUseEducationCloudComp`, `PermissionsActionPlansUserAccess`). The clone→Einstein-Agent-User PSA (and its `PermissionSetAssignment WHERE PermissionSet.Name = 'SRA_AI_Agent_Access'` verify) is confirmed at **step 9**.
|
|
63
|
+
|
|
64
|
+
That query only confirms the 3 system booleans on the parent `PermissionSet` row — it never checks that the 17 `ObjectPermissions` and 23 `FieldPermissions` child rows from points 2–3 above actually landed (the `/composite/sobjects` calls are `allOrNone:false`, so a partial failure on some rows returns `200` alongside the successful ones). Verify those too:
|
|
65
|
+
```bash
|
|
66
|
+
sf data query -q "SELECT SobjectType FROM ObjectPermissions WHERE ParentId='<clonePsId>'" --target-org <alias>
|
|
67
|
+
sf data query -q "SELECT Field FROM FieldPermissions WHERE ParentId='<clonePsId>'" --target-org <alias>
|
|
68
|
+
```
|
|
69
|
+
Expect **17** `ObjectPermissions` rows (the 16 objects + `Case`) and **23** `FieldPermissions` rows. A short count means some rows 400'd silently in the composite call — check each row's `errors[]` in the original response before re-sending just the missing ones.
|
|
70
|
+
|
|
71
|
+
> **Do NOT clone per-subagent.** Clone the permission set **once** and assign that single clone. A per-subagent object/field breakdown (Create Admissions Application / Campus Tours / FAQ / Escalation) is useful only as a field-level reference for what each subagent touches — it must never become a series of setup steps or separate clones.
|
|
72
|
+
|
|
73
|
+
### Three-persona permission model (this is the correct model to encode)
|
|
74
|
+
|
|
75
|
+
Permissions map to **three distinct user personas**, not one:
|
|
76
|
+
|
|
77
|
+
| Persona | Who | Permission sets |
|
|
78
|
+
|---|---|---|
|
|
79
|
+
| **Admin / builder** | The human setting up + owning the agent | Agentforce Default Admin; Education Cloud Full Access; Einstein for Education Cloud Access; **(Legacy) Data Cloud Marketing Admin** (API name `GenieMarketingAdmin` — the label resolves to the *Legacy* set; the non-legacy Data Cloud sets are Admin/Architect/Activation, a different grant); Prompt Template Manager + User; Knowledge (Lightning Knowledge Manager). PSL: Messaging — **WARNING:** the label "Messaging for In-App and Web User" is **ambiguous**: `EasyServiceMessagingSessionPsl` ("Messaging User") vs `EmbeddedServiceMessagingUserPsl` ("Enhanced Chat User"); pick per the org's messaging setup and assign by **API name**, not label. |
|
|
80
|
+
| **Einstein Agent User** (the Digital/Agent User the ASA agent runs as — **ASA/Service path only**; the AEA agent has no service account) | The service account the Service agent acts under | The full parity set required for this restricted-license user: **6 PermissionSetLicenses** (`PermissionSetLicenseAssign`, assign first — this restricted-license user's other assignments fail `FIELD_INTEGRITY_EXCEPTION` without them): `AgentforceServiceAgentUserPsl`, `EducationCloudAiAgentUserPsl`, `AgentforceServiceAgentBuilderPsl`, `EinsteinGPTPromptTemplatesPsl`, `EasyServiceKnowledgeBasePsl`, `GenieDataPlatformStarterPsl`. **8 PermissionSetAssignments**: `AgentforceServiceAgentBase`, `AgentforceServiceAgentUser`, `AgentforceServiceAgentBuilder`, `ServiceLightningKnowledgeManager`, `EinsteinGPTPromptTemplateUser`, `GenieUserEnhancedSecurity`, `RunFlow`, and the **`SRA_AI_Agent_Access` clone** from step 4b (NOT the OOTB `EducationCloudAiAgentAccess` set — see the CRITICAL note above). Without the full 14-grant set, `publish`/Commit Version fails with a generic *"User doesn't have access to agent"* — a sharing-flavored message that is actually a missing-license/permission-set problem. |
|
|
81
|
+
| **Community user** (authenticated Experience Cloud path — **AEA agent only**) | Authenticated prospective students on the community | Clone of `EducationCloudExprcCloudAccess` — create with the **fixed `Name` `SRA_Exprc_Cloud_Access`**, Label "Education Cloud for Experience Cloud Access (Student Recruitment Agent)" (re-find by exact `Name`, same rule as the AI Agent Access clone) — with the **Run Flows** app perm (`PermissionsRunFlow`) set on the clone; **plus separate PS assignments** (not clone settings): **Prompt Template User**, Knowledge, Data Cloud starter. **This whole persona is AEA/auth-path only** — the ASA/Service agent runs under the Einstein Agent User, so an ASA-only build skips it. **Clone + config at Step 4** (same as the Einstein clone); the **Enable Agent Access → AEA agent** selection and the assignment to community users happen at **step 10** (both need the agent to exist). **Clone target differs by path** (see below). |
|
|
82
|
+
|
|
83
|
+
Each persona's *assignment* is a tier-1 `PermissionSetAssignment` POST (or `sf org assign permset` at tier 2) once the sets exist. The community-user PSL *clone* (AEA path) uses the same **tier-1 sObject-REST path as step 4b** (`POST /sobjects/PermissionSet` + `/composite/sobjects`), with `sf`-deploy / UI as tier-2/3 fallbacks (see below).
|
|
84
|
+
|
|
85
|
+
**Before the Step 4a assignment above, self-resolve the running user's Id at tier 1 — don't presuppose you already know your own display name.** A `/query WHERE Name='<current user>'` only works once something has already told you that name; when nothing has, resolve identity via the current-user/"me" lookup your runtime exposes (discover it if you don't already know which one) rather than guessing at a `WHERE` clause you can't populate yet, then reuse that Id directly. (Routes over `dispatch_readonly`.)
|
|
86
|
+
|
|
87
|
+
**Resolve Ids, then select interactively — do NOT blindly assign all three personas.** Many orgs already carry some of these sets, and the two agent-dependent personas (whichever agent paths are in scope) can't be assigned at Step 4 — they're deferred until the agent exists (Einstein Agent User at step 9, community persona at step 10 — see the sequencing below):
|
|
88
|
+
|
|
89
|
+
1. **Resolve the Einstein Agent User's Id from `BotDefinition`, not by profile.** The **Einstein Agent User exists only for the ASA/Service agent** (the AEA/Employee agent has no service account — authenticated community users invoke it as themselves). Since step 9 creates this user itself and knows its Id directly, there's no need to query it back before granting — `SELECT BotUserId FROM BotDefinition WHERE DeveloperName='<ASA agent>'` is a **verify**, not a discovery step, once step 9 is done, and should read back the exact user Id step 9 created and granted. Do **not** resolve it by `WHERE Profile.Name='Einstein Agent User'` (it can match other digital-agent users in the org). A **null** `BotUserId` at verify time means the sentinel substitution was skipped in step 9 — fix that, not an expected state. (Routes over `dispatch_readonly`.)
|
|
90
|
+
2. **Ask the customer which personas/users to assign** — all three, or a subset — rather than assuming. Then assign per their selection.
|
|
91
|
+
3. **Skip duplicates** — a `DUPLICATE_VALUE` error on an existing assignment is benign/idempotent; don't treat it as a failure.
|
|
92
|
+
4. **Sequencing — only the Admin/builder persona is assigned at Step 4; the other two are agent-dependent.** Einstein Agent User grant → step 9; community agent-access-enable + user assignment → step 10. See the persona table above for what each grant is and why — same fact, not a new one.
|
|
93
|
+
|
|
94
|
+
### The unauthenticated path has NO guest user — do not build a guest PSL
|
|
95
|
+
|
|
96
|
+
The Service (ASA) path runs entirely under the **Einstein Agent User** service account (the three-persona model above); there is **no guest/unauthenticated Salesforce user in the SRA model**, so do **not** clone or assign a guest permission-set license (e.g. `EducationCloudGuestAccessPsl`). The ASA's Education Cloud access is the `SRA_AI_Agent_Access` clone of `EducationCloudAiAgentAccess` (built step 4b), assigned to the agent's running user at step 9. The only cloned community set is for the **authenticated** path:
|
|
97
|
+
|
|
98
|
+
- **Authenticated community (AEA only)** → clone `EducationCloudExprcCloudAccess`; its license is `EducationCloudExprcCloudAccessPsl`.
|
|
99
|
+
|
|
100
|
+
## Step 5 — OWD (internal) → Public Read Only on 6 objects
|
|
101
|
+
|
|
102
|
+
Set internal OWD to **Public Read Only** on exactly these 6:
|
|
103
|
+
|
|
104
|
+
1. Academic Interest
|
|
105
|
+
2. Academic Term
|
|
106
|
+
3. Action Plan Template
|
|
107
|
+
4. Application Timeline
|
|
108
|
+
5. Learning
|
|
109
|
+
6. Program Term Application Timeline
|
|
110
|
+
|
|
111
|
+
**NOT** Learning Program (it's `ControlledByParent` — can't set OWD on a child-controlled object), **NOT** Campaign (Campaign uses a sharing rule instead — step 6). **NOT** Individual Application (it gets RecordTypes at step 6, not OWD).
|
|
112
|
+
|
|
113
|
+
> **CRITICAL: API-name correction:** the 6th object's real API name is **`ProgramTermApplnTimeline`** ("Application"→"Appln"), NOT `ProgramTermApplicationTimeline`. The full spelling doesn't exist as an entity, so a verify query using it silently returns 0 rows for that object → the customer thinks OWD is set when the object was never touched. (Note the sibling `ApplicationTimeline` keeps its full spelling — only the compound name is truncated.)
|
|
114
|
+
|
|
115
|
+
- 🟡 **OWD (`CustomObject` `sharingModel`) — attempt Tier 1, but confirmed to fail org-wide on at least one gateway.** `PUT /services/data/vXX/headless/metadata` body `{type:"CustomObject", fullName:"<obj>", xmlRep:"<CustomObject xmlns=\"http://soap.sforce.com/2006/04/metadata\"><sharingModel>Read</sharingModel></CustomObject>"}` → attempt for all 6 objects. Only **internal** OWD is set; `ExternalSharingModel` stays Private (intended). Cold-verify the write with the read below — a `200`/`success:true` alone is not proof the value changed. ⚠️ **Confirmed failure mode:** on some orgs, ALL 6 — not just `ActionPlanTemplate` — return `400 UNSUPPORTED_OPERATION: MetadataCrud does not support UPDATE on type: CustomObject`, including a retry using `externalSharingModel` instead of `sharingModel`. On this exact error, drop straight to Setup UI for all 6 — don't retry reshaped, and don't assume the other 5 are fine just because they're not `ActionPlanTemplate`.
|
|
116
|
+
- **CRITICAL: `ActionPlanTemplate` has its own, separate, always-true exception → T3-UI-only regardless of the above.** Root cause: `EntityDefinition.IsCustomizable=false` (the other 5 are `true`) — MDAPI rejects it as a "non-customizable CustomObject" (`500`), and `sf` hits the identical wall. This is a *different* failure than the blanket `UNSUPPORTED_OPERATION` above — object-specific, not gateway-specific — so `IsCustomizable` alone doesn't predict the broader failure; check it only to confirm this one exception.
|
|
117
|
+
- **Tier 2 (`sf`, fallback):** deploy each object's `sharingModel = Read` via `sf project deploy start`.
|
|
118
|
+
- **Tier 3 (manual):** Setup → Sharing Settings → Edit → set the object's Default Internal Access to **Public Read Only** → Save.
|
|
119
|
+
|
|
120
|
+
**Verify OWD (attempt T1 `/tooling/query` over `dispatch_readonly` → `sf` fallback):** **WARNING:** `EntityDefinition` **rejects disjunctions** (`WHERE … OR …` → `400 MALFORMED_QUERY: Disjunctions not supported`) — use an `IN (...)` list (below) or separate filtered queries.
|
|
121
|
+
```bash
|
|
122
|
+
# Tier 1 first (dispatch_readonly): GET /services/data/vXX/tooling/query/?q=<the SOQL below, URL-encoded>
|
|
123
|
+
# Tier 2 fallback:
|
|
124
|
+
sf data query --use-tooling-api -q "SELECT QualifiedApiName, InternalSharingModel, ExternalSharingModel, IsCustomizable FROM EntityDefinition WHERE QualifiedApiName IN ('AcademicInterest','AcademicTerm','ActionPlanTemplate','ApplicationTimeline','Learning','ProgramTermApplnTimeline')" --target-org <alias>
|
|
125
|
+
```
|
|
126
|
+
Confirm `InternalSharingModel = 'Read'` for all 6. (Note `ProgramTermApplnTimeline` — the corrected API name.)
|
|
127
|
+
|
|
128
|
+
## Step 6 — Topic-specific prep
|
|
129
|
+
|
|
130
|
+
### 6a — Campaign "Recruitment Event" picklist value (Campus Tours) — 🟡 Tier 1 attempt (confirmed org-dependent)
|
|
131
|
+
Add a **"Recruitment Event"** value to Campaign **Type** (backing set `StandardValueSet:CampaignType`).
|
|
132
|
+
- **Tier 1:** `PUT /services/data/vXX/headless/metadata`, `type:StandardValueSet`, `fullName:CampaignType`, `xmlRep` listing **all existing values + Recruitment Event** → `200 {success:true}`. **WARNING: Whole-set-replace semantics — you MUST echo every existing value** or the omitted ones are deleted. Read current values first (e.g. `ui-api/object-info/Campaign/picklist-values/{rtId}/Type`). ⚠️ **Confirmed failure mode:** both this write and a direct read (`GET .../headless/metadata?type=StandardValueSet&fullName=CampaignType`) can 400 `UNSUPPORTED_OPERATION: MetadataCrud does not support READ/UPDATE on type: StandardValueSet` on some orgs. On that error, go straight to Tier 2/3 — don't retry reshaped.
|
|
133
|
+
- **Tier 2/3 fallback:** `sf`-deploy the `StandardValueSet` source, or Setup → Object Manager → Campaign → Fields → Type → New value.
|
|
134
|
+
|
|
135
|
+
**Verify (attempt T1 `/query` → `sf` fallback):**
|
|
136
|
+
```bash
|
|
137
|
+
# Tier 2 fallback:
|
|
138
|
+
sf data query --use-tooling-api -q "SELECT Metadata FROM FieldDefinition WHERE EntityDefinition.QualifiedApiName='Campaign' AND QualifiedApiName='Type'" --target-org <alias>
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### 6b — Campaign "Campus Tours" criteria sharing rule — CONFIRMED GENUINE MDAPI GAP → TIER 3 UI
|
|
142
|
+
Criteria-based Campaign sharing rule: `Type = Recruitment Event` → shared to a portal group, access **Read Only**.
|
|
143
|
+
- **CRITICAL: `SharingCriteriaRule`/`SharingOwnerRule` CREATE is unsupported through ANY MDAPI path.** Attempt: `POST /headless/metadata` with the correct `<SharingRules><sharingCriteriaRules>…</sharingCriteriaRules></SharingRules>` shape → `400 UNSUPPORTED_OPERATION: MetadataCrud does not support CREATE on type: SharingCriteriaRule`. Since the failure is on the CREATE *operation* (MetadataCrud), `sf`-deploy hits the **identical** wall — this is NOT a tier-1-vs-2 gap. `platform-sharing-rules-generate`/`platform-metadata-deploy` cannot help.
|
|
144
|
+
- **Tier 3 (the reliable path):** Setup → Sharing Settings → Campaign Sharing Rules → New. The target portal group/profile already exists in the org — Experience Cloud site setup does not create it, so this does **not** need to wait for Step 12. As a T3-UI step it's fine to defer to the end of the build if that's how the skill is sequencing its manual steps, but re-prompt for it there rather than treating step 12 as a hard dependency.
|
|
145
|
+
- **CRITICAL: This sharing rule's target audience is authenticated-community-only — largely an AEA concern.** The portal group it shares Campaign records to only has members on the authenticated (AEA) path; the ASA/Service agent already reads Campaign via its `SRA_AI_Agent_Access` clone's object perms (Step 4b) and doesn't need this rule. If the customer wants the AEA path, don't assume the portal group's underlying community licensing already exists — verify it first (`prerequisites.md`'s AEA community-licensing prerequisite: 4 UserLicenses + 4 Profiles). If those are missing, the "already exists in the org" assumption above doesn't hold and this step has nothing to share to yet.
|
|
146
|
+
- If attempting tier 1 first, use the exact router-expected shape — the `<SharingRules><sharingCriteriaRules>…</sharingCriteriaRules></SharingRules>` wrapper — so the attempt fails cleanly on the unsupported CREATE *operation* rather than on a malformed body, confirming the tier-3 fallback is genuinely required.
|
|
147
|
+
|
|
148
|
+
**Verify:** `GET /sharing/rules/Campaign` (tier 1, readonly) → confirm the `Campus_Tours` rule with `ruleType:Criteria`, `mainAccessLevel:"Read Only"`.
|
|
149
|
+
|
|
150
|
+
### 6c — Individual Application record type (Admissions Application) — 🟢 TIER 1
|
|
151
|
+
- **Tier 1:** `POST /services/data/vXX/tooling/sobjects/RecordType` with the **nested** body `{FullName:"IndividualApplication.Admissions_Application", Metadata:{active:true, label:"Admissions Application"}}` → 201. A `400 JSON_PARSER_ERROR` means the body used flat columns instead of the nested `Metadata` object — fix the shape, don't fall back to another tier.
|
|
152
|
+
- **CRITICAL: Do NOT prescribe a record-type description** — leave it to the customer (omit it). Watch the BusinessProcess coupling rule for other objects (Opportunity/Lead/Case/Solution need a paired `.businessProcess-meta.xml`; `IndividualApplication` does not).
|
|
153
|
+
- **Tier 2/3 fallback:** `sf project deploy start -m RecordType:IndividualApplication.<Name>`, or Setup → Object Manager → Individual Application → Record Types → New.
|
|
154
|
+
|
|
155
|
+
**Verify (attempt T1 `/query` → `sf` fallback):** use **data** `/query`, not tooling — see `execution-model.md`'s query-routing nuance (c) for why (`RecordType.DeveloperName` 400s `INVALID_FIELD` on the tooling surface).
|
|
156
|
+
```bash
|
|
157
|
+
# Tier 2 fallback:
|
|
158
|
+
sf data query -q "SELECT Id, Name, DeveloperName FROM RecordType WHERE SobjectType='IndividualApplication'" --target-org <alias>
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### 6d — `ApplicationRecordTypeConfig` (registers the RT into the admissions feature) — 🟢 TIER 1
|
|
162
|
+
Creating the record type (6c) is necessary but **not sufficient** — Education Cloud needs a **second** record that registers that record type into the admissions feature, or the SRA admissions flow won't recognize it.
|
|
163
|
+
- **It's a tooling-tier entity, invisible to the data API** (`KeyPrefix 0jJ`, `IsCustomizable=false`, `PublisherId=System`) — `/sobjects/ApplicationRecordTypeConfig/describe` returns 404 and data `/query` returns `400 INVALID_TYPE`. Read + write + verify all go through `/tooling/...`.
|
|
164
|
+
- **Tier 1 create:** `POST /services/data/vXX/tooling/sobjects/ApplicationRecordTypeConfig` body `{DeveloperName:"Admissions_Application", MasterLabel:"Admissions Application", ApplicationUsageType:"EDU", ObjectName:"IndividualApplication", RecordTypeName:"Admissions Application"}` → 201.
|
|
165
|
+
- **CRITICAL: `RecordTypeName` takes the record type's LABEL** ("Admissions Application"), NOT the DeveloperName and NOT the Id (despite `idLookup:true`) — both of those return `400 INVALID_API_INPUT: "Invalid record type"`. Picklist API values: `ApplicationUsageType` = **`EDU`**; `ObjectName` = **`IndividualApplication`** (only value).
|
|
166
|
+
|
|
167
|
+
**Verify (tooling `/query`; data `/query` throws `INVALID_TYPE`):**
|
|
168
|
+
```bash
|
|
169
|
+
# Tier 2 fallback:
|
|
170
|
+
sf data query --use-tooling-api -q "SELECT DeveloperName, ApplicationUsageType, ObjectName, RecordTypeName FROM ApplicationRecordTypeConfig WHERE DeveloperName='Admissions_Application'" --target-org <alias>
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### 6e — Record type → profile visibility — CRITICAL: TIER 3 UI
|
|
174
|
+
After 6c/6d, the record type must be made **visible to the profiles** that use it or the personas can't select it. Assign `IndividualApplication.Admissions_Application` to **Customer Community Plus User, Einstein Agent User, System Administrator**.
|
|
175
|
+
- **CRITICAL: The three PROFILES already exist, independent of whether their users do.** `Einstein Agent User` is a standard profile present in every org from the start — assigning record-type visibility to it does not wait on the specific running user Claude creates at step 9. Don't defer this step or tell the customer it'll come back around once that user exists; it's assignable now.
|
|
176
|
+
- **CRITICAL: Tooling PATCH on `Profile` is UNSUPPORTED** (`400 INSUFFICIENT_ACCESS_ON_CROSS_REFERENCE_ENTITY: Operation UPDATE cannot be applied on … Profile … through Tooling API`) — no surgical/merge write for `recordTypeVisibilities`. The only API write is `PUT /headless/metadata type:Profile` = **whole-file REPLACE** (profiles are enormous — 400+ fieldPermissions, userPermissions incl. `DigitalAgentUser`; a partial echo silently strips the rest). Too high blast radius for a 3-click task.
|
|
177
|
+
- **→ Tier 3 UI, per profile:** Setup → Profiles → open each of **Einstein Agent User**, **Customer Community Plus User**, **System Administrator** → Record Type Settings → Individual Application → move **Admissions Application** from Available Record Types to Selected Record Types → Save. Set default where appropriate.
|
|
178
|
+
|
|
179
|
+
> **WARNING: Page-layout *assignment* to a record type is a separate piece with no tier-1/2 path** — no `layoutAssignments` handling exists in any catalog skill. If needed, expect tier-3 UI.
|
|
180
|
+
|
|
181
|
+
**Verify (T1 readonly, one profile per call):** `GET /services/data/vXX/tooling/sobjects/Profile/{id}` → inspect `body.Metadata.recordTypeVisibilities` for the `Admissions_Application` entry (`visible:true`). **WARNING:** Two gotchas: (a) **one profile per call** (multi-row `Metadata`/`FullName` retrieval → `MALFORMED_QUERY`); (b) payload is **large** (System Administrator ~515KB) — extract only the `recordTypeVisibilities` slice.
|
|
182
|
+
|
|
183
|
+
> All five blocks (6a–6e) are hard prerequisites for their subagents. 6a/6c/6d are **T1**; 6b + 6e are tier-3-UI gaps.
|