@salesforce/afv-skills 1.45.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.
Files changed (171) hide show
  1. package/package.json +1 -1
  2. package/skills/agentforce-observe/SKILL.md +32 -4
  3. package/skills/agentforce-observe/references/ahm-alerts.md +719 -0
  4. package/skills/automation-flow-generate/SKILL.md +11 -5
  5. package/skills/consumer-goods-promotion-bo-api-deploy/SKILL.md +275 -0
  6. package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/README.md +32 -0
  7. package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/SetCommentValue.cls +75 -0
  8. package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/SetCommentValue.cls-meta.xml +5 -0
  9. package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/interview-answers.json +13 -0
  10. package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/payloads/copy.json +10 -0
  11. package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/payloads/create.json +20 -0
  12. package/skills/consumer-goods-promotion-bo-api-deploy/assets/set-comment-value/payloads/update.json +16 -0
  13. package/skills/consumer-goods-promotion-bo-api-deploy/references/conventions-and-payload-rules.md +273 -0
  14. package/skills/consumer-goods-promotion-bo-api-deploy/references/generate-and-wire.md +236 -0
  15. package/skills/consumer-goods-promotion-bo-api-deploy/references/reference-example-set-comment-value.md +132 -0
  16. package/skills/consumer-goods-promotion-bo-api-deploy/references/smoke-and-verify.md +211 -0
  17. package/skills/dx-code-analyzer-configure/scripts/validate-config.sh +14 -10
  18. package/skills/dx-code-analyzer-run/scripts/apply-fixes.js +45 -4
  19. package/skills/dx-code-analyzer-run/scripts/describe-rule.js +52 -32
  20. package/skills/dx-devops-project-manage/SKILL.md +197 -0
  21. package/skills/dx-devops-project-manage/examples/common-workflows.md +197 -0
  22. package/skills/dx-devops-project-manage/references/cli-commands.md +295 -0
  23. package/skills/dx-devops-project-manage/scripts/create-project.sh +48 -0
  24. package/skills/dx-devops-project-manage/scripts/list-projects.sh +51 -0
  25. package/skills/dx-devops-project-manage/scripts/update-project.sh +96 -0
  26. package/skills/education-cloud-academic-calendar-generate/SKILL.md +225 -0
  27. package/skills/education-cloud-academic-calendar-generate/examples/quarter-calendar.json +47 -0
  28. package/skills/education-cloud-academic-calendar-generate/examples/sample-output.md +57 -0
  29. package/skills/education-cloud-academic-calendar-generate/examples/semester-calendar.json +54 -0
  30. package/skills/education-cloud-academic-calendar-generate/references/calendar-systems.md +127 -0
  31. package/skills/education-cloud-academic-calendar-generate/references/date-validation.md +222 -0
  32. package/skills/education-cloud-academic-calendar-generate/references/foundation_prerequisites.md +40 -0
  33. package/skills/education-cloud-academic-calendar-generate/scripts/validate_calendar_dates.py +143 -0
  34. package/skills/education-cloud-course-catalog-migrate/SKILL.md +321 -0
  35. package/skills/education-cloud-course-catalog-migrate/references/gotchas-detail.md +16 -0
  36. package/skills/education-cloud-course-catalog-migrate/references/gotchas.md +16 -0
  37. package/skills/education-cloud-course-catalog-migrate/references/large-catalog-handling.md +42 -0
  38. package/skills/education-cloud-course-catalog-migrate/scripts/batch_courses.py +36 -0
  39. package/skills/education-cloud-course-catalog-migrate/scripts/detect_linked_courses.py +51 -0
  40. package/skills/education-cloud-course-catalog-migrate/scripts/detect_modality_variants.py +48 -0
  41. package/skills/education-cloud-course-catalog-migrate/scripts/resolve_api_version.py +43 -0
  42. package/skills/education-cloud-course-catalog-migrate/scripts/split_course_code.py +39 -0
  43. package/skills/education-cloud-course-catalog-migrate/scripts/validate_completeness.py +54 -0
  44. package/skills/education-cloud-multi-campus-configure/references/foundation_prerequisites.md +3 -5
  45. package/skills/education-cloud-student-recruitment-agent-configure/SKILL.md +177 -0
  46. package/skills/education-cloud-student-recruitment-agent-configure/references/agent-and-subagents.md +151 -0
  47. package/skills/education-cloud-student-recruitment-agent-configure/references/customer-narration.md +34 -0
  48. package/skills/education-cloud-student-recruitment-agent-configure/references/execution-model.md +54 -0
  49. package/skills/education-cloud-student-recruitment-agent-configure/references/flows.md +82 -0
  50. package/skills/education-cloud-student-recruitment-agent-configure/references/grounding.md +199 -0
  51. package/skills/education-cloud-student-recruitment-agent-configure/references/permissions.md +183 -0
  52. package/skills/education-cloud-student-recruitment-agent-configure/references/platform-enablement.md +82 -0
  53. package/skills/education-cloud-student-recruitment-agent-configure/references/prerequisites.md +158 -0
  54. package/skills/education-cloud-student-recruitment-agent-configure/references/routing.md +141 -0
  55. package/skills/experience-cms-brand-apply/SKILL.md +5 -5
  56. package/skills/experience-cms-brand-create/SKILL.md +2 -2
  57. package/skills/experience-cms-content-generate/SKILL.md +1 -0
  58. package/skills/experience-cms-content-render/SKILL.md +173 -0
  59. package/skills/experience-cms-content-render/assets/angular/DetailPage.component.ts +25 -0
  60. package/skills/experience-cms-content-render/assets/angular/MediaRenderer.component.ts +133 -0
  61. package/skills/experience-cms-content-render/assets/angular/TypeList.component.ts +38 -0
  62. package/skills/experience-cms-content-render/assets/angular/TypeRenderer.component.ts +90 -0
  63. package/skills/experience-cms-content-render/assets/angular/cms-content.component.ts +248 -0
  64. package/skills/experience-cms-content-render/assets/angular/cms-item.service.ts +100 -0
  65. package/skills/experience-cms-content-render/assets/react/DetailPage.tsx +20 -0
  66. package/skills/experience-cms-content-render/assets/react/MediaRenderer.tsx +129 -0
  67. package/skills/experience-cms-content-render/assets/react/TypeList.tsx +40 -0
  68. package/skills/experience-cms-content-render/assets/react/TypeRenderer.tsx +64 -0
  69. package/skills/experience-cms-content-render/assets/react/heuristicRenderer.tsx +310 -0
  70. package/skills/experience-cms-content-render/assets/react/useCmsItem.ts +129 -0
  71. package/skills/experience-cms-content-render/assets/shared/cmsContentType.ts +49 -0
  72. package/skills/experience-cms-content-render/assets/shared/cmsCore.types.ts +96 -0
  73. package/skills/experience-cms-content-render/assets/shared/externalRefs.ts +55 -0
  74. package/skills/experience-cms-content-render/references/bulk-loading.md +60 -0
  75. package/skills/experience-cms-content-render/references/codegen-guardrails.md +111 -0
  76. package/skills/experience-cms-content-render/references/detail-pages.md +87 -0
  77. package/skills/experience-cms-content-render/references/embed-recipes.md +127 -0
  78. package/skills/experience-cms-content-render/references/failure-modes.md +96 -0
  79. package/skills/experience-cms-content-render/references/heuristic-render-rules.md +131 -0
  80. package/skills/experience-cms-content-render/references/init-scaffold.md +122 -0
  81. package/skills/experience-cms-content-render/references/interaction-model.md +173 -0
  82. package/skills/experience-cms-content-render/references/package-api.md +106 -0
  83. package/skills/experience-cms-content-render/references/schema-sync.md +114 -0
  84. package/skills/experience-cms-content-render/references/styling-scopes.md +65 -0
  85. package/skills/experience-cms-content-render/references/verify.md +49 -0
  86. package/skills/experience-cms-content-type-generate/SKILL.md +2 -2
  87. package/skills/experience-content-media-stock-image-search/SKILL.md +5 -4
  88. package/skills/experience-search-coordinate/SKILL.md +198 -0
  89. package/skills/experience-search-coordinate/assets/search-payload-template.json +25 -0
  90. package/skills/experience-search-coordinate/references/content-route.md +313 -0
  91. package/skills/experience-search-coordinate/references/content-type-discovery.md +57 -0
  92. package/skills/experience-search-coordinate/references/media-route.md +172 -0
  93. package/skills/experience-search-coordinate/references/scope-resolution.md +14 -0
  94. package/skills/experience-ui-bundle-localize/SKILL.md +1 -1
  95. package/skills/experience-ui-bundle-localize/references/i18n-setup.md +5 -3
  96. package/skills/experience-ui-bundle-project-generate/SKILL.md +18 -14
  97. package/skills/experience-ui-bundle-project-generate/references/angular-project-generate.md +22 -0
  98. package/skills/experience-ui-bundle-project-generate/references/react-project-generate.md +20 -0
  99. package/skills/experience-ui-bundle-salesforce-data-access/SKILL.md +58 -54
  100. package/skills/experience-ui-bundle-salesforce-data-access/references/caching.md +6 -0
  101. package/skills/experience-ui-bundle-salesforce-data-access/references/graphiti-cli.md +2 -2
  102. package/skills/experience-ui-bundle-salesforce-data-access/references/migration.md +6 -0
  103. package/skills/experience-ui-bundle-salesforce-data-access/references/rest-and-integration.md +2 -1
  104. package/skills/experience-ui-bundle-salesforce-data-access/references/sdk-api.md +6 -0
  105. package/skills/experience-ui-bundle-site-generate/SKILL.md +59 -8
  106. package/skills/experience-ui-bundle-site-generate/references/configure-metadata-digital-experience.md +8 -3
  107. package/skills/experience-ui-bundle-site-generate/references/configure-metadata-language-settings.md +120 -0
  108. package/skills/life-sciences-fieldsalesrep-coordinate/SKILL.md +336 -0
  109. package/skills/life-sciences-fieldsalesrep-coordinate/references/orchestration-flow.md +143 -0
  110. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-application-flexipage-mapping.md +127 -0
  111. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-deploy-commands.md +116 -0
  112. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-lifesci-metadata-deploy.md +111 -0
  113. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-overview.md +312 -0
  114. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-profile-layout-assignments.md +171 -0
  115. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-state-tracking.md +64 -0
  116. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-2-starter-config-trigger-handlers.md +122 -0
  117. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-4-user-provisioning-overview.md +335 -0
  118. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-4-user-provisioning-user-provisioning-details.md +140 -0
  119. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-execution-state-and-recovery.md +196 -0
  120. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-metadata-cache-generation.md +155 -0
  121. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-overview.md +307 -0
  122. package/skills/life-sciences-fieldsalesrep-coordinate/references/stage-5-visit-creation-visit-creation-data.md +211 -0
  123. package/skills/life-sciences-fieldsalesrep-coordinate/references/state-machine-and-changes.md +108 -0
  124. package/skills/life-sciences-kam-coordinate/SKILL.md +241 -0
  125. package/skills/life-sciences-kam-coordinate/references/orchestration-flow.md +152 -0
  126. package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-application-flexipage-mapping.md +79 -0
  127. package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-deploy-commands.md +131 -0
  128. package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-kam-config-records.md +85 -0
  129. package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-lifesci-metadata-deploy.md +112 -0
  130. package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-overview.md +202 -0
  131. package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-profile-layout-assignments.md +67 -0
  132. package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-state-tracking.md +65 -0
  133. package/skills/life-sciences-kam-coordinate/references/stage-2-starter-config-trigger-handlers.md +123 -0
  134. package/skills/life-sciences-kam-coordinate/references/stage-4-participant-role-and-sprint.md +89 -0
  135. package/skills/life-sciences-kam-coordinate/references/stage-5-data-and-plan-templates-overview.md +337 -0
  136. package/skills/life-sciences-kam-coordinate/references/stage-5-data-creation-data.md +248 -0
  137. package/skills/life-sciences-kam-coordinate/references/stage-6-ipad-validation-script.md +35 -0
  138. package/skills/life-sciences-kam-coordinate/references/stage-6-metadata-cache-generation.md +155 -0
  139. package/skills/life-sciences-kam-coordinate/references/stage-6-user-provisioning-details.md +146 -0
  140. package/skills/life-sciences-kam-coordinate/references/stage-6-user-provisioning-overview.md +89 -0
  141. package/skills/life-sciences-kam-coordinate/references/state-machine-and-changes.md +114 -0
  142. package/skills/life-sciences-prerequisites-validate/SKILL.md +138 -0
  143. package/skills/life-sciences-prerequisites-validate/references/checks-org-settings.md +190 -0
  144. package/skills/life-sciences-prerequisites-validate/references/checks-user-and-package.md +211 -0
  145. package/skills/life-sciences-territory-configure/SKILL.md +217 -0
  146. package/skills/life-sciences-territory-configure/references/territory-metadata.md +262 -0
  147. package/skills/platform-apex-logs-debug/SKILL.md +7 -7
  148. package/skills/platform-custom-application-generate/SKILL.md +4 -4
  149. package/skills/platform-custom-object-generate/SKILL.md +7 -7
  150. package/skills/platform-custom-tab-generate/SKILL.md +1 -1
  151. package/skills/platform-dsar-policy-manage/SKILL.md +272 -0
  152. package/skills/platform-dsar-policy-manage/references/configure.md +106 -0
  153. package/skills/platform-dsar-policy-manage/references/export-and-history.md +123 -0
  154. package/skills/platform-dsar-policy-manage/references/gap-analysis-guide.md +150 -0
  155. package/skills/platform-dsar-policy-manage/references/gap-scan.md +129 -0
  156. package/skills/platform-dsar-policy-manage/references/headless-sor.md +59 -0
  157. package/skills/platform-dsar-policy-manage/references/report-format.md +59 -0
  158. package/skills/platform-dsar-policy-manage/scripts/tests/__init__.py +0 -0
  159. package/skills/platform-dsar-policy-manage/scripts/tests/test_validate_policy_tree.py +76 -0
  160. package/skills/platform-dsar-policy-manage/scripts/validate-policy-tree.py +130 -0
  161. package/skills/platform-flexipage-generate/SKILL.md +4 -0
  162. package/skills/platform-list-view-generate/SKILL.md +1 -0
  163. package/skills/platform-salesforce-connect-adapter-generate/SKILL.md +359 -0
  164. package/skills/platform-salesforce-connect-adapter-generate/references/official-examples.md +69 -0
  165. package/skills/platform-salesforce-connect-adapter-generate/references/scenarios.md +187 -0
  166. package/skills/platform-soql-query/SKILL.md +8 -8
  167. package/skills/platform-value-set-generate/SKILL.md +2 -2
  168. package/skills/service-itsm-agentic-setup-cmdb-coordinate/SKILL.md +20 -27
  169. package/skills/service-native-voice-recording-transcription-configure/SKILL.md +47 -27
  170. package/skills/service-native-voice-recording-transcription-configure/references/thunderbird-voice-settings.md +13 -9
  171. package/skills/service-native-voice-recording-transcription-configure/scripts/enable-recording-transcription.sh +104 -45
@@ -0,0 +1,222 @@
1
+ # Academic Calendar Date Validation Rules
2
+
3
+ ## Basic Date Range Validation
4
+
5
+ ### Rule: Term End After Start
6
+
7
+ **Requirement**: Every term's end date must be strictly after its start date.
8
+
9
+ **Check**: `END_DATE > START_DATE`
10
+
11
+ **Example Valid**:
12
+ - Start: 2025-08-18
13
+ - End: 2025-12-12
14
+ - Valid (117 days)
15
+
16
+ **Example Invalid**:
17
+ - Start: 2025-08-18
18
+ - End: 2025-08-15
19
+ - Invalid (end before start)
20
+
21
+ **Error Message**: "Term end date must be after start date"
22
+
23
+ ---
24
+
25
+ ### Rule: Academic Year Covers All Terms
26
+
27
+ **Requirement**: The Academic Year start date must be <= the earliest term start, and the Academic Year end date must be >= the latest term end.
28
+
29
+ **Check**:
30
+ - `YEAR_START <= min(TERM_START_DATES)`
31
+ - `YEAR_END >= max(TERM_END_DATES)`
32
+
33
+ **Example Valid**:
34
+ - Academic Year: 2025-08-18 to 2026-08-14
35
+ - Fall 2025: 2025-08-18 to 2025-12-12
36
+ - Spring 2026: 2026-01-12 to 2026-05-15
37
+ - Summer 2026: 2026-05-26 to 2026-08-14
38
+ - Valid (year spans all terms)
39
+
40
+ **Example Invalid**:
41
+ - Academic Year: 2025-08-18 to 2026-05-31
42
+ - Summer 2026: 2026-05-26 to 2026-08-14
43
+ - Invalid (Summer extends beyond year end)
44
+
45
+ **Error Message**: "Academic Year must span all child terms"
46
+
47
+ ---
48
+
49
+ ## Registration and Deadline Validation
50
+
51
+ ### Rule: Registration Opens Before Term Starts
52
+
53
+ **Requirement**: Registration open date must be before term start date.
54
+
55
+ **Check**: `REGISTRATION_OPEN < TERM_START`
56
+
57
+ **Typical Lead Time**: 3-4 months for Fall, 2-3 months for Spring, 1-2 months for Summer
58
+
59
+ **Example Valid**:
60
+ - Registration Opens: 2025-04-01
61
+ - Term Starts: 2025-08-18
62
+ - Valid (4.5 months lead time)
63
+
64
+ **Example Invalid**:
65
+ - Registration Opens: 2025-08-20
66
+ - Term Starts: 2025-08-18
67
+ - Invalid (opens after term starts)
68
+
69
+ **Error Message**: "Registration window must open before term start date"
70
+
71
+ ---
72
+
73
+ ### Rule: Add/Drop Deadline After or Equal to Session Start
74
+
75
+ **Requirement**: AcademicTerm has NO add/drop field — add/drop deadlines live on AcademicSession only (see `SKILL.md` Gotchas). A deadline must be on or after its session's start date, and before its session's end date.
76
+
77
+ **Check**: `SESSION_START <= ADD_DROP_DEADLINE < SESSION_END`
78
+
79
+ **Typical Timing**: 1-2 weeks after session start
80
+
81
+ **Example Valid**:
82
+ - Session Start: 2025-08-18
83
+ - Add/Drop Deadline: 2025-09-05
84
+ - Session End: 2025-12-12
85
+ - Valid (18 days after start, well before end)
86
+
87
+ **Example Invalid**:
88
+ - Session Start: 2025-08-18
89
+ - Add/Drop Deadline: 2025-08-10
90
+ - Invalid (deadline before session starts)
91
+
92
+ **No natural session exists**: If the user gives a per-term add/drop deadline but no session subdivisions, ask the user how to represent it (e.g. propose a single session per term to carry the deadline) — never write the date onto AcademicTerm and never silently drop it.
93
+
94
+ **Error Message**: "Add/drop deadline must be on or after session start date"
95
+
96
+ ---
97
+
98
+ ## Session Overlap Validation
99
+
100
+ ### Rule: Overlapping Sessions Within Same Term Are Allowed
101
+
102
+ **Requirement**: Sessions within the same parent term CAN have overlapping date ranges. This is a common pattern for Summer sessions.
103
+
104
+ **Check**: Flag overlaps for user confirmation, but do not block creation.
105
+
106
+ **Example Valid (Intentional Overlap)**:
107
+ - Summer 2026 Term: 2026-05-26 to 2026-08-14
108
+ - Summer Session 1: 2026-05-26 to 2026-07-03 (6 weeks)
109
+ - Summer Session 2: 2026-06-26 to 2026-08-14 (7 weeks)
110
+ - Overlap: 2026-06-26 to 2026-07-03 (1 week overlap is intentional)
111
+
112
+ **User Confirmation Prompt**:
113
+ > "Summer Session 1 and Summer Session 2 overlap from [START] to [END]. This is common for summer sessions where students can take courses in both. Is this overlap intentional?"
114
+
115
+ **Example Invalid (Likely Error)**:
116
+ - Fall 2025 Term: 2025-08-18 to 2025-12-12
117
+ - Fall Session 1: 2025-08-18 to 2025-10-15
118
+ - Fall Session 2: 2025-08-20 to 2025-12-12
119
+ - Suspicious: Fall terms rarely have overlapping sessions — confirm with user
120
+
121
+ ---
122
+
123
+ ### Rule: Sessions Must Be Contained Within Parent Term
124
+
125
+ **Requirement**: Every session's start and end dates must fall within its parent term's date range.
126
+
127
+ **Check**:
128
+ - `SESSION_START >= TERM_START`
129
+ - `SESSION_END <= TERM_END`
130
+
131
+ **Example Valid**:
132
+ - Summer 2026 Term: 2026-05-26 to 2026-08-14
133
+ - Summer Session 1: 2026-05-26 to 2026-07-03
134
+ - Valid (session fully contained within term)
135
+
136
+ **Example Invalid**:
137
+ - Summer 2026 Term: 2026-05-26 to 2026-08-14
138
+ - Summer Session 1: 2026-05-15 to 2026-07-03
139
+ - Invalid (session starts before term)
140
+
141
+ **Error Message**: "Session dates must fall within parent term date range"
142
+
143
+ ---
144
+
145
+ ## Duplicate Detection
146
+
147
+ ### Rule: No Duplicate Term Names for Same Academic Year
148
+
149
+ **Requirement**: Within a single Academic Year, term names must be unique.
150
+
151
+ **Check**: `AcademicTerm.Name` is not unique-constrained — a duplicate name will NOT error on insert. List-scan for existing terms with matching names via SOQL `/query` (routable via headless-360) before creating: `GET /services/data/<ver>/query` with `queryParams: {"q": "SELECT Id, Name, StartDate, EndDate FROM AcademicTerm WHERE Name = 'Fall 2025'"}` — pass the SOQL via `queryParams`, not inlined as `?q=` in the URL (inlining 404s); otherwise reuse Ids captured earlier in the same session. If the read fails, stop and ask the user before creating — do not risk a silent duplicate.
152
+
153
+ **Example Valid**:
154
+ - Academic Year 2025-2026
155
+ - Fall 2025
156
+ - Spring 2026
157
+ - Summer 2026
158
+ - Valid (all unique names)
159
+
160
+ **Example Invalid (Duplicate Name)**:
161
+ - Academic Year 2025-2026
162
+ - Fall 2025 (existing, created on 2025-03-15)
163
+ - Fall 2025 (new, attempting to create on 2025-07-20)
164
+ - Duplicate (same name, same year)
165
+
166
+ **Resolution**:
167
+ 1. If dates match existing record: Skip creation, report "Fall 2025 already exists with same dates"
168
+ 2. If dates differ: Ask user whether to update existing or skip
169
+
170
+ ---
171
+
172
+ ### Rule: Duplicate with Different Dates Requires User Confirmation
173
+
174
+ **Requirement**: If a term with the same name exists but has different dates, stop and ask user how to proceed.
175
+
176
+ **Example Conflict**:
177
+ - **Existing**: Fall 2025 (2025-08-25 to 2025-12-19)
178
+ - **New**: Fall 2025 (2025-08-18 to 2025-12-12)
179
+
180
+ **User Prompt**:
181
+ > "A Fall 2025 term already exists with dates 2025-08-25 to 2025-12-19. Your new data has dates 2025-08-18 to 2025-12-12. Would you like to:
182
+ > 1. Update the existing term with new dates
183
+ > 2. Skip creating Fall 2025
184
+ > 3. Cancel and review the existing term first"
185
+
186
+ ---
187
+
188
+ ## Date Format Validation
189
+
190
+ ### Rule: Date Strings Must Be in ISO 8601 Format
191
+
192
+ **Requirement**: All dates parsed from user input or documents must be converted to YYYY-MM-DD format before inserting.
193
+
194
+ **Valid Formats**:
195
+ - `2025-08-18`
196
+ - `08/18/2025` → Convert to `2025-08-18`
197
+ - `August 18, 2025` → Convert to `2025-08-18`
198
+
199
+ **Invalid Formats**:
200
+ - `8/18/25` → Ambiguous (is it 2025 or 1925?)
201
+ - `18-08-2025` → Day-first format (common in Europe, ambiguous)
202
+
203
+ **Resolution**: If date format is ambiguous, ask user to clarify which format convention they're using.
204
+
205
+ ---
206
+
207
+ ## Validation Workflow
208
+
209
+ ### Step-by-Step Validation Process
210
+
211
+ 1. **Parse all dates from input** → Convert to YYYY-MM-DD format
212
+ 2. **Validate basic ranges** → Check end > start for all terms and sessions
213
+ 3. **Validate Academic Year coverage** → Check year spans all terms
214
+ 4. **Validate registration windows** → Check open date < term start
215
+ 5. **Validate add/drop deadlines** → Check start <= deadline < end
216
+ 6. **Check for overlapping sessions** → Flag for user confirmation (but allow)
217
+ 7. **Check for duplicate terms** → Query existing, ask user if conflict found
218
+ 8. **Validate session containment** → Check sessions within parent term bounds
219
+
220
+ **If any validation fails**: Stop, report specific error with field values, and ask user to provide corrected dates.
221
+
222
+ **If all validations pass**: Proceed to record creation.
@@ -0,0 +1,40 @@
1
+ # Foundation Prerequisites (Step 0)
2
+
3
+ Run before Step 1 on every standalone invocation. Shared across the EDU skill family (also required by `education-cloud-multi-campus-configure`). Do not create any Academic Year/Term/Session record until every check below passes.
4
+
5
+ All checks use the org REST transport resolved in Execution Transport — no `sf`/`sfdx`/`curl`.
6
+
7
+ ## 1. Org edition
8
+
9
+ `GET /services/data/<ver>/query` with `queryParams: {"q": "SELECT OrganizationType FROM Organization"}`.
10
+
11
+ Education Cloud requires Enterprise, Performance, Unlimited, or Developer edition. If the org is a different edition (e.g. Group, Professional), STOP — this is not fixable via API. Tell the user the org edition doesn't support Education Cloud.
12
+
13
+ ## 2. Lightning Experience enabled
14
+
15
+ No dedicated read API for this. Infer it from other calls in this checklist succeeding normally. If a call unexpectedly errors in a way consistent with Classic-mode restrictions, tell the user to enable Lightning Experience manually (Setup → Lightning Experience → turn it on) and re-run this checklist.
16
+
17
+ ## 3. Education Cloud license provisioned
18
+
19
+ `GET /services/data/<ver>/query` with `queryParams: {"q": "SELECT Id, Name FROM PermissionSet WHERE Name LIKE '%EducationCloud%'"}`.
20
+
21
+ - One or more rows → license is provisioned, continue to check 4.
22
+ - Zero rows → cross-check before concluding it's absent: `GET /services/data/<ver>/sobjects/BusinessProfile/describe`. If the describe succeeds and `createable:true`, the license is present despite the empty query (permission set naming varies by org) — continue to check 4.
23
+ - Still zero rows AND describe fails/`createable:false` → genuinely absent. STOP — this is not fixable via API. Tell the user Education Cloud needs to be provisioned on this org (Setup → licenses).
24
+
25
+ ## 4. Running user has EDU access
26
+
27
+ Resolve the running user's Id first — `GET /services/oauth2/userinfo` and read `user_id`/`sub`, or ask for their username if that endpoint isn't reachable through the resolved transport. Then:
28
+
29
+ `GET /services/data/<ver>/query` with `queryParams: {"q": "SELECT Id FROM PermissionSetAssignment WHERE AssigneeId = '<resolved running user Id>' AND PermissionSet.Name LIKE '%EducationCloud%'"}`.
30
+
31
+ - One or more rows → continue to check 5.
32
+ - Zero rows → tell the user the running user needs an Education Cloud permission set assigned (Setup → Users → Permission Set Assignments), wait for confirmation, then re-run this query before proceeding.
33
+
34
+ ## 5. Education Cloud Foundation enabled
35
+
36
+ `GET /headless/invoke/platform/education-cloud-settings` → read `educationCloudEnabled`.
37
+
38
+ - `true` → all checks pass, proceed to Step 1.
39
+ - `false` → this flips the org into Education Cloud mode and is **IRREVERSIBLE**. Warn the user explicitly and ask for confirmation before enabling. On confirmation: `PATCH /headless/invoke/platform/education-cloud-settings/enable-education-cloud`, then cold-verify with a fresh `GET /headless/invoke/platform/education-cloud-settings` (a `success:true` on the PATCH is not proof — re-read `educationCloudEnabled`). If the API path fails, fall back to the manual path: Setup → "Set Up Education Cloud" → toggle → Save, wait for the user to confirm, then re-verify with the same GET.
40
+ - If the user declines to enable it, STOP — do not proceed to Step 1.
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/env python3
2
+ """Deterministic date validation for Academic Year / Term / Session records.
3
+
4
+ Reads calendar data as JSON on stdin, shape:
5
+ {
6
+ "academicYear": {"startDate": "YYYY-MM-DD", "endDate": "YYYY-MM-DD"},
7
+ "terms": [
8
+ {"name": "Fall 2025", "startDate": "...", "endDate": "...", "registrationOpenDate": "..."}
9
+ ],
10
+ "sessions": [
11
+ {"name": "Summer Session 1", "termName": "Summer 2026",
12
+ "classStartDate": "...", "classEndDate": "...", "addDropDeadline": "..."}
13
+ ]
14
+ }
15
+
16
+ Writes JSON to stdout:
17
+ {
18
+ "valid": bool,
19
+ "errors": [{"field": str, "message": str}],
20
+ "warnings": [{"field": str, "message": str}],
21
+ "computedRegistrationCloseDates": {"<termName>": "YYYY-MM-DD"}
22
+ }
23
+
24
+ Exit code 0 if no errors (warnings, e.g. intentional session overlaps, do not fail the run), 1 otherwise.
25
+ """
26
+
27
+ import json
28
+ import sys
29
+ from datetime import date, timedelta
30
+
31
+
32
+ def parse_date(value):
33
+ return date.fromisoformat(value)
34
+
35
+
36
+ def safe_parse(value, field, errors):
37
+ """Parse an ISO date, routing missing/malformed values into `errors` instead of raising."""
38
+ if value is None:
39
+ errors.append({"field": field, "message": "Required date value is missing"})
40
+ return None
41
+ try:
42
+ return parse_date(value)
43
+ except (TypeError, ValueError):
44
+ errors.append({"field": field, "message": f"Invalid date value: {value!r}"})
45
+ return None
46
+
47
+
48
+ def validate(data):
49
+ errors = []
50
+ warnings = []
51
+ computed_close_dates = {}
52
+
53
+ year = data.get("academicYear", {})
54
+ terms = data.get("terms", [])
55
+ sessions = data.get("sessions", [])
56
+
57
+ year_start = safe_parse(year.get("startDate"), "academicYear.startDate", errors)
58
+ year_end = safe_parse(year.get("endDate"), "academicYear.endDate", errors)
59
+
60
+ term_bounds = {}
61
+
62
+ for term in terms:
63
+ name = term.get("name", "<unnamed term>")
64
+ start = safe_parse(term.get("startDate"), f"terms[{name}].startDate", errors)
65
+ end = safe_parse(term.get("endDate"), f"terms[{name}].endDate", errors)
66
+ if start is None or end is None:
67
+ continue
68
+ term_bounds[name] = (start, end)
69
+
70
+ if end <= start:
71
+ errors.append({"field": f"terms[{name}].endDate", "message": "Term end date must be after start date"})
72
+
73
+ if year_start and start < year_start:
74
+ errors.append({"field": f"terms[{name}].startDate", "message": "Term starts before Academic Year start date"})
75
+ if year_end and end > year_end:
76
+ errors.append({"field": f"terms[{name}].endDate", "message": "Term ends after Academic Year end date"})
77
+
78
+ reg_open = term.get("registrationOpenDate")
79
+ if reg_open:
80
+ reg_open_date = safe_parse(reg_open, f"terms[{name}].registrationOpenDate", errors)
81
+ if reg_open_date is not None and reg_open_date >= start:
82
+ errors.append({"field": f"terms[{name}].registrationOpenDate", "message": "Registration window must open before term start date"})
83
+
84
+ computed_close_dates[name] = (start - timedelta(days=1)).isoformat()
85
+
86
+ sessions_by_term = {}
87
+ for session in sessions:
88
+ name = session.get("name", "<unnamed session>")
89
+ term_name = session.get("termName")
90
+ start = safe_parse(session.get("classStartDate"), f"sessions[{name}].classStartDate", errors)
91
+ end = safe_parse(session.get("classEndDate"), f"sessions[{name}].classEndDate", errors)
92
+ if start is None or end is None:
93
+ continue
94
+
95
+ if end <= start:
96
+ errors.append({"field": f"sessions[{name}].classEndDate", "message": "Session end date must be after start date"})
97
+
98
+ bounds = term_bounds.get(term_name)
99
+ if bounds is None:
100
+ errors.append({"field": f"sessions[{name}].termName", "message": f"No term named '{term_name}' found for this session"})
101
+ else:
102
+ term_start, term_end = bounds
103
+ if start < term_start or end > term_end:
104
+ errors.append({"field": f"sessions[{name}]", "message": "Session dates must fall within parent term date range"})
105
+
106
+ deadline = session.get("addDropDeadline")
107
+ if deadline:
108
+ deadline_date = safe_parse(deadline, f"sessions[{name}].addDropDeadline", errors)
109
+ if deadline_date is not None:
110
+ if deadline_date < start or deadline_date >= end:
111
+ errors.append({"field": f"sessions[{name}].addDropDeadline", "message": "Add/drop deadline must be on or after session start and before session end"})
112
+
113
+ sessions_by_term.setdefault(term_name, []).append((name, start, end))
114
+
115
+ for term_name, entries in sessions_by_term.items():
116
+ for i in range(len(entries)):
117
+ for j in range(i + 1, len(entries)):
118
+ name_a, start_a, end_a = entries[i]
119
+ name_b, start_b, end_b = entries[j]
120
+ if start_a <= end_b and start_b <= end_a:
121
+ warnings.append({
122
+ "field": f"sessions[{name_a},{name_b}]",
123
+ "message": f"'{name_a}' and '{name_b}' overlap within term '{term_name}' — confirm with user whether intentional",
124
+ })
125
+
126
+ return {
127
+ "valid": len(errors) == 0,
128
+ "errors": errors,
129
+ "warnings": warnings,
130
+ "computedRegistrationCloseDates": computed_close_dates,
131
+ }
132
+
133
+
134
+ def main():
135
+ data = json.load(sys.stdin)
136
+ result = validate(data)
137
+ json.dump(result, sys.stdout, indent=2)
138
+ sys.stdout.write("\n")
139
+ sys.exit(0 if result["valid"] else 1)
140
+
141
+
142
+ if __name__ == "__main__":
143
+ main()