crewly 1.20.133 → 1.20.134

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 (219) hide show
  1. package/dist/backend/backend/src/constants.d.ts +82 -0
  2. package/dist/backend/backend/src/constants.d.ts.map +1 -1
  3. package/dist/backend/backend/src/constants.js +87 -0
  4. package/dist/backend/backend/src/constants.js.map +1 -1
  5. package/dist/backend/backend/src/controllers/bundle/bundle.controller.d.ts +44 -0
  6. package/dist/backend/backend/src/controllers/bundle/bundle.controller.d.ts.map +1 -0
  7. package/dist/backend/backend/src/controllers/bundle/bundle.controller.js +122 -0
  8. package/dist/backend/backend/src/controllers/bundle/bundle.controller.js.map +1 -0
  9. package/dist/backend/backend/src/controllers/bundle/bundle.routes.d.ts +25 -0
  10. package/dist/backend/backend/src/controllers/bundle/bundle.routes.d.ts.map +1 -0
  11. package/dist/backend/backend/src/controllers/bundle/bundle.routes.js +33 -0
  12. package/dist/backend/backend/src/controllers/bundle/bundle.routes.js.map +1 -0
  13. package/dist/backend/backend/src/index.d.ts.map +1 -1
  14. package/dist/backend/backend/src/index.js +14 -0
  15. package/dist/backend/backend/src/index.js.map +1 -1
  16. package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
  17. package/dist/backend/backend/src/routes/api.routes.js +4 -0
  18. package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
  19. package/dist/backend/backend/src/services/bundle/bundle-apply.factory.d.ts +62 -0
  20. package/dist/backend/backend/src/services/bundle/bundle-apply.factory.d.ts.map +1 -0
  21. package/dist/backend/backend/src/services/bundle/bundle-apply.factory.js +173 -0
  22. package/dist/backend/backend/src/services/bundle/bundle-apply.factory.js.map +1 -0
  23. package/dist/backend/backend/src/services/bundle/bundle-apply.service.d.ts +359 -0
  24. package/dist/backend/backend/src/services/bundle/bundle-apply.service.d.ts.map +1 -0
  25. package/dist/backend/backend/src/services/bundle/bundle-apply.service.js +888 -0
  26. package/dist/backend/backend/src/services/bundle/bundle-apply.service.js.map +1 -0
  27. package/dist/backend/backend/src/services/bundle/bundle-catalog.d.ts +167 -0
  28. package/dist/backend/backend/src/services/bundle/bundle-catalog.d.ts.map +1 -0
  29. package/dist/backend/backend/src/services/bundle/bundle-catalog.js +248 -0
  30. package/dist/backend/backend/src/services/bundle/bundle-catalog.js.map +1 -0
  31. package/dist/backend/backend/src/services/bundle/bundle-connectors.d.ts +43 -0
  32. package/dist/backend/backend/src/services/bundle/bundle-connectors.d.ts.map +1 -0
  33. package/dist/backend/backend/src/services/bundle/bundle-connectors.js +57 -0
  34. package/dist/backend/backend/src/services/bundle/bundle-connectors.js.map +1 -0
  35. package/dist/backend/backend/src/services/bundle/bundle-deps.d.ts +37 -0
  36. package/dist/backend/backend/src/services/bundle/bundle-deps.d.ts.map +1 -0
  37. package/dist/backend/backend/src/services/bundle/bundle-deps.js +60 -0
  38. package/dist/backend/backend/src/services/bundle/bundle-deps.js.map +1 -0
  39. package/dist/backend/backend/src/services/bundle/bundle-manifest.d.ts +93 -0
  40. package/dist/backend/backend/src/services/bundle/bundle-manifest.d.ts.map +1 -0
  41. package/dist/backend/backend/src/services/bundle/bundle-manifest.js +532 -0
  42. package/dist/backend/backend/src/services/bundle/bundle-manifest.js.map +1 -0
  43. package/dist/backend/backend/src/services/bundle/bundle-placeholders.d.ts +100 -0
  44. package/dist/backend/backend/src/services/bundle/bundle-placeholders.d.ts.map +1 -0
  45. package/dist/backend/backend/src/services/bundle/bundle-placeholders.js +183 -0
  46. package/dist/backend/backend/src/services/bundle/bundle-placeholders.js.map +1 -0
  47. package/dist/backend/backend/src/services/bundle/bundle-runtime.d.ts +36 -0
  48. package/dist/backend/backend/src/services/bundle/bundle-runtime.d.ts.map +1 -0
  49. package/dist/backend/backend/src/services/bundle/bundle-runtime.js +38 -0
  50. package/dist/backend/backend/src/services/bundle/bundle-runtime.js.map +1 -0
  51. package/dist/backend/backend/src/services/bundle/bundle-skill-installer.d.ts +105 -0
  52. package/dist/backend/backend/src/services/bundle/bundle-skill-installer.d.ts.map +1 -0
  53. package/dist/backend/backend/src/services/bundle/bundle-skill-installer.js +144 -0
  54. package/dist/backend/backend/src/services/bundle/bundle-skill-installer.js.map +1 -0
  55. package/dist/backend/backend/src/services/bundle/bundle-state.store.d.ts +48 -0
  56. package/dist/backend/backend/src/services/bundle/bundle-state.store.d.ts.map +1 -0
  57. package/dist/backend/backend/src/services/bundle/bundle-state.store.js +90 -0
  58. package/dist/backend/backend/src/services/bundle/bundle-state.store.js.map +1 -0
  59. package/dist/backend/backend/src/services/bundle/bundle-team.builder.d.ts +81 -0
  60. package/dist/backend/backend/src/services/bundle/bundle-team.builder.d.ts.map +1 -0
  61. package/dist/backend/backend/src/services/bundle/bundle-team.builder.js +172 -0
  62. package/dist/backend/backend/src/services/bundle/bundle-team.builder.js.map +1 -0
  63. package/dist/backend/backend/src/services/bundle/bundle-time.d.ts +59 -0
  64. package/dist/backend/backend/src/services/bundle/bundle-time.d.ts.map +1 -0
  65. package/dist/backend/backend/src/services/bundle/bundle-time.js +98 -0
  66. package/dist/backend/backend/src/services/bundle/bundle-time.js.map +1 -0
  67. package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.d.ts.map +1 -1
  68. package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.js +5 -0
  69. package/dist/backend/backend/src/services/cloud/mobile-api-relay.service.js.map +1 -1
  70. package/dist/backend/backend/src/services/core/storage.service.js +1 -1
  71. package/dist/backend/backend/src/services/core/storage.service.js.map +1 -1
  72. package/dist/backend/backend/src/services/core/teams-backup.service.d.ts.map +1 -1
  73. package/dist/backend/backend/src/services/core/teams-backup.service.js +4 -2
  74. package/dist/backend/backend/src/services/core/teams-backup.service.js.map +1 -1
  75. package/dist/backend/backend/src/services/onboarding/onboarding-checklist.factory.d.ts.map +1 -1
  76. package/dist/backend/backend/src/services/onboarding/onboarding-checklist.factory.js +2 -0
  77. package/dist/backend/backend/src/services/onboarding/onboarding-checklist.factory.js.map +1 -1
  78. package/dist/backend/backend/src/services/onboarding/onboarding-checklist.service.d.ts +14 -2
  79. package/dist/backend/backend/src/services/onboarding/onboarding-checklist.service.d.ts.map +1 -1
  80. package/dist/backend/backend/src/services/onboarding/onboarding-checklist.service.js +37 -1
  81. package/dist/backend/backend/src/services/onboarding/onboarding-checklist.service.js.map +1 -1
  82. package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts +21 -0
  83. package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -1
  84. package/dist/backend/backend/src/services/slack/slack-team-channel.service.js +85 -0
  85. package/dist/backend/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
  86. package/dist/backend/backend/src/services/template/template.service.d.ts.map +1 -1
  87. package/dist/backend/backend/src/services/template/template.service.js +5 -2
  88. package/dist/backend/backend/src/services/template/template.service.js.map +1 -1
  89. package/dist/backend/backend/src/services/workflow/cron-task.service.d.ts.map +1 -1
  90. package/dist/backend/backend/src/services/workflow/cron-task.service.js +4 -2
  91. package/dist/backend/backend/src/services/workflow/cron-task.service.js.map +1 -1
  92. package/dist/backend/backend/src/types/solution-bundle.types.d.ts +367 -0
  93. package/dist/backend/backend/src/types/solution-bundle.types.d.ts.map +1 -0
  94. package/dist/backend/backend/src/types/solution-bundle.types.js +17 -0
  95. package/dist/backend/backend/src/types/solution-bundle.types.js.map +1 -0
  96. package/dist/backend/backend/src/types/team-template.types.d.ts +8 -0
  97. package/dist/backend/backend/src/types/team-template.types.d.ts.map +1 -1
  98. package/dist/backend/backend/src/types/team-template.types.js.map +1 -1
  99. package/dist/backend/build-info.json +2 -2
  100. package/dist/cli/backend/src/constants.d.ts +82 -0
  101. package/dist/cli/backend/src/constants.d.ts.map +1 -1
  102. package/dist/cli/backend/src/constants.js +87 -0
  103. package/dist/cli/backend/src/constants.js.map +1 -1
  104. package/dist/cli/backend/src/services/bundle/bundle-apply.service.d.ts +359 -0
  105. package/dist/cli/backend/src/services/bundle/bundle-apply.service.d.ts.map +1 -0
  106. package/dist/cli/backend/src/services/bundle/bundle-apply.service.js +888 -0
  107. package/dist/cli/backend/src/services/bundle/bundle-apply.service.js.map +1 -0
  108. package/dist/cli/backend/src/services/bundle/bundle-catalog.d.ts +167 -0
  109. package/dist/cli/backend/src/services/bundle/bundle-catalog.d.ts.map +1 -0
  110. package/dist/cli/backend/src/services/bundle/bundle-catalog.js +248 -0
  111. package/dist/cli/backend/src/services/bundle/bundle-catalog.js.map +1 -0
  112. package/dist/cli/backend/src/services/bundle/bundle-deps.d.ts +37 -0
  113. package/dist/cli/backend/src/services/bundle/bundle-deps.d.ts.map +1 -0
  114. package/dist/cli/backend/src/services/bundle/bundle-deps.js +60 -0
  115. package/dist/cli/backend/src/services/bundle/bundle-deps.js.map +1 -0
  116. package/dist/cli/backend/src/services/bundle/bundle-manifest.d.ts +93 -0
  117. package/dist/cli/backend/src/services/bundle/bundle-manifest.d.ts.map +1 -0
  118. package/dist/cli/backend/src/services/bundle/bundle-manifest.js +532 -0
  119. package/dist/cli/backend/src/services/bundle/bundle-manifest.js.map +1 -0
  120. package/dist/cli/backend/src/services/bundle/bundle-placeholders.d.ts +100 -0
  121. package/dist/cli/backend/src/services/bundle/bundle-placeholders.d.ts.map +1 -0
  122. package/dist/cli/backend/src/services/bundle/bundle-placeholders.js +183 -0
  123. package/dist/cli/backend/src/services/bundle/bundle-placeholders.js.map +1 -0
  124. package/dist/cli/backend/src/services/bundle/bundle-runtime.d.ts +36 -0
  125. package/dist/cli/backend/src/services/bundle/bundle-runtime.d.ts.map +1 -0
  126. package/dist/cli/backend/src/services/bundle/bundle-runtime.js +38 -0
  127. package/dist/cli/backend/src/services/bundle/bundle-runtime.js.map +1 -0
  128. package/dist/cli/backend/src/services/bundle/bundle-skill-installer.d.ts +105 -0
  129. package/dist/cli/backend/src/services/bundle/bundle-skill-installer.d.ts.map +1 -0
  130. package/dist/cli/backend/src/services/bundle/bundle-skill-installer.js +144 -0
  131. package/dist/cli/backend/src/services/bundle/bundle-skill-installer.js.map +1 -0
  132. package/dist/cli/backend/src/services/bundle/bundle-state.store.d.ts +48 -0
  133. package/dist/cli/backend/src/services/bundle/bundle-state.store.d.ts.map +1 -0
  134. package/dist/cli/backend/src/services/bundle/bundle-state.store.js +90 -0
  135. package/dist/cli/backend/src/services/bundle/bundle-state.store.js.map +1 -0
  136. package/dist/cli/backend/src/services/bundle/bundle-team.builder.d.ts +81 -0
  137. package/dist/cli/backend/src/services/bundle/bundle-team.builder.d.ts.map +1 -0
  138. package/dist/cli/backend/src/services/bundle/bundle-team.builder.js +172 -0
  139. package/dist/cli/backend/src/services/bundle/bundle-team.builder.js.map +1 -0
  140. package/dist/cli/backend/src/services/bundle/bundle-time.d.ts +59 -0
  141. package/dist/cli/backend/src/services/bundle/bundle-time.d.ts.map +1 -0
  142. package/dist/cli/backend/src/services/bundle/bundle-time.js +98 -0
  143. package/dist/cli/backend/src/services/bundle/bundle-time.js.map +1 -0
  144. package/dist/cli/backend/src/services/core/storage.service.js +1 -1
  145. package/dist/cli/backend/src/services/core/storage.service.js.map +1 -1
  146. package/dist/cli/backend/src/services/core/teams-backup.service.d.ts.map +1 -1
  147. package/dist/cli/backend/src/services/core/teams-backup.service.js +4 -2
  148. package/dist/cli/backend/src/services/core/teams-backup.service.js.map +1 -1
  149. package/dist/cli/backend/src/services/marketplace/marketplace-installer.service.d.ts +72 -0
  150. package/dist/cli/backend/src/services/marketplace/marketplace-installer.service.d.ts.map +1 -0
  151. package/dist/cli/backend/src/services/marketplace/marketplace-installer.service.js +389 -0
  152. package/dist/cli/backend/src/services/marketplace/marketplace-installer.service.js.map +1 -0
  153. package/dist/cli/backend/src/services/messaging/index.d.ts +15 -0
  154. package/dist/cli/backend/src/services/messaging/index.d.ts.map +1 -0
  155. package/dist/cli/backend/src/services/messaging/index.js +20 -0
  156. package/dist/cli/backend/src/services/messaging/index.js.map +1 -0
  157. package/dist/cli/backend/src/services/messaging/queue-processor.service.d.ts +224 -0
  158. package/dist/cli/backend/src/services/messaging/queue-processor.service.d.ts.map +1 -0
  159. package/dist/cli/backend/src/services/messaging/queue-processor.service.js +969 -0
  160. package/dist/cli/backend/src/services/messaging/queue-processor.service.js.map +1 -0
  161. package/dist/cli/backend/src/services/messaging/response-router.service.d.ts +110 -0
  162. package/dist/cli/backend/src/services/messaging/response-router.service.d.ts.map +1 -0
  163. package/dist/cli/backend/src/services/messaging/response-router.service.js +268 -0
  164. package/dist/cli/backend/src/services/messaging/response-router.service.js.map +1 -0
  165. package/dist/cli/backend/src/services/onboarding/onboarding-checklist.service.d.ts +14 -2
  166. package/dist/cli/backend/src/services/onboarding/onboarding-checklist.service.d.ts.map +1 -1
  167. package/dist/cli/backend/src/services/onboarding/onboarding-checklist.service.js +37 -1
  168. package/dist/cli/backend/src/services/onboarding/onboarding-checklist.service.js.map +1 -1
  169. package/dist/cli/backend/src/services/orchestrator/commitment-approval-guard.d.ts +133 -0
  170. package/dist/cli/backend/src/services/orchestrator/commitment-approval-guard.d.ts.map +1 -0
  171. package/dist/cli/backend/src/services/orchestrator/commitment-approval-guard.js +232 -0
  172. package/dist/cli/backend/src/services/orchestrator/commitment-approval-guard.js.map +1 -0
  173. package/dist/cli/backend/src/services/skill/skill-catalog.service.d.ts +277 -0
  174. package/dist/cli/backend/src/services/skill/skill-catalog.service.d.ts.map +1 -0
  175. package/dist/cli/backend/src/services/skill/skill-catalog.service.js +708 -0
  176. package/dist/cli/backend/src/services/skill/skill-catalog.service.js.map +1 -0
  177. package/dist/cli/backend/src/services/skill-setup/skill-install-job.service.d.ts +243 -0
  178. package/dist/cli/backend/src/services/skill-setup/skill-install-job.service.d.ts.map +1 -0
  179. package/dist/cli/backend/src/services/skill-setup/skill-install-job.service.js +439 -0
  180. package/dist/cli/backend/src/services/skill-setup/skill-install-job.service.js.map +1 -0
  181. package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts +21 -0
  182. package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -1
  183. package/dist/cli/backend/src/services/slack/slack-team-channel.service.js +85 -0
  184. package/dist/cli/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
  185. package/dist/cli/backend/src/services/template/template.service.d.ts.map +1 -1
  186. package/dist/cli/backend/src/services/template/template.service.js +5 -2
  187. package/dist/cli/backend/src/services/template/template.service.js.map +1 -1
  188. package/dist/cli/backend/src/services/workflow/cron-task.service.d.ts +316 -0
  189. package/dist/cli/backend/src/services/workflow/cron-task.service.d.ts.map +1 -0
  190. package/dist/cli/backend/src/services/workflow/cron-task.service.js +1043 -0
  191. package/dist/cli/backend/src/services/workflow/cron-task.service.js.map +1 -0
  192. package/dist/cli/backend/src/types/cron-task.types.d.ts +95 -0
  193. package/dist/cli/backend/src/types/cron-task.types.d.ts.map +1 -0
  194. package/dist/cli/backend/src/types/cron-task.types.js +10 -0
  195. package/dist/cli/backend/src/types/cron-task.types.js.map +1 -0
  196. package/dist/cli/backend/src/types/solution-bundle.types.d.ts +367 -0
  197. package/dist/cli/backend/src/types/solution-bundle.types.d.ts.map +1 -0
  198. package/dist/cli/backend/src/types/solution-bundle.types.js +17 -0
  199. package/dist/cli/backend/src/types/solution-bundle.types.js.map +1 -0
  200. package/dist/cli/backend/src/types/team-template.types.d.ts +8 -0
  201. package/dist/cli/backend/src/types/team-template.types.d.ts.map +1 -1
  202. package/dist/cli/backend/src/types/team-template.types.js.map +1 -1
  203. package/dist/cli/cli/src/commands/deploy-bundle.d.ts +160 -0
  204. package/dist/cli/cli/src/commands/deploy-bundle.d.ts.map +1 -0
  205. package/dist/cli/cli/src/commands/deploy-bundle.js +386 -0
  206. package/dist/cli/cli/src/commands/deploy-bundle.js.map +1 -0
  207. package/dist/cli/cli/src/commands/onboard.d.ts +32 -1
  208. package/dist/cli/cli/src/commands/onboard.d.ts.map +1 -1
  209. package/dist/cli/cli/src/commands/onboard.js +80 -3
  210. package/dist/cli/cli/src/commands/onboard.js.map +1 -1
  211. package/dist/cli/cli/src/constants.d.ts +5 -0
  212. package/dist/cli/cli/src/constants.d.ts.map +1 -1
  213. package/dist/cli/cli/src/constants.js +5 -0
  214. package/dist/cli/cli/src/constants.js.map +1 -1
  215. package/dist/cli/cli/src/index.js +15 -0
  216. package/dist/cli/cli/src/index.js.map +1 -1
  217. package/frontend/dist/assets/{index-e6c403b6.js → index-97bb17d4.js} +274 -274
  218. package/frontend/dist/index.html +1 -1
  219. package/package.json +1 -1
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Solution bundle manifest: validation and normalization.
3
+ *
4
+ * A bundle is an ordinary team template with a `bundle` section
5
+ * (types/solution-bundle.types.ts). This module checks everything the apply
6
+ * engine relies on, so a broken template is refused before anything is
7
+ * written: roles, a lead per team, member refs, placeholders, cron
8
+ * expressions, question shapes, connectors, file paths.
9
+ *
10
+ * @module services/bundle/bundle-manifest
11
+ */
12
+ import { type TemplateRole } from '../../types/team-template.types.js';
13
+ import type { BundleTemplate } from '../../types/solution-bundle.types.js';
14
+ /** Result of validating a template's bundle section. */
15
+ export interface BundleValidation {
16
+ ok: boolean;
17
+ /** One line per problem, with a JSON-ish path */
18
+ errors: string[];
19
+ }
20
+ /** A team of the bundle, normalized: the main team plus `bundle.teams`. */
21
+ export interface NormalizedBundleTeam {
22
+ key: string;
23
+ /** Name before placeholders are filled */
24
+ name: string;
25
+ description: string;
26
+ roles: TemplateRole[];
27
+ /** Role id of the team's lead (hierarchyLevel 1 + canDelegate) */
28
+ leadRole: string;
29
+ }
30
+ /** A parsed member reference (`role` or `team/role`). */
31
+ export interface MemberRef {
32
+ teamKey: string;
33
+ role: string;
34
+ }
35
+ /** Optional file access, to check `file` references of norms / SOPs. */
36
+ export interface BundleValidationOptions {
37
+ /** Directory relative `file` paths resolve against */
38
+ dir?: string;
39
+ /** Read a file (defaults to none: file contents are not checked) */
40
+ readFile?: (absolutePath: string) => string | null;
41
+ }
42
+ /**
43
+ * Whether a raw template carries a `bundle` section.
44
+ *
45
+ * @param raw - Parsed template JSON
46
+ * @returns True when `bundle` is an object
47
+ */
48
+ export declare function hasBundleSection(raw: unknown): boolean;
49
+ /**
50
+ * Check a five-field cron expression (`*`, numbers, ranges, lists, steps).
51
+ *
52
+ * @param expression - Cron expression
53
+ * @returns True when every field is valid and in range
54
+ *
55
+ * @example
56
+ * isValidCronExpression('0 9 * * 1-5') // true
57
+ */
58
+ export declare function isValidCronExpression(expression: string): boolean;
59
+ /**
60
+ * Parse a member reference.
61
+ *
62
+ * @param ref - `role` (main team) or `team/role`
63
+ * @returns The team key and role
64
+ */
65
+ export declare function parseMemberRef(ref: string): MemberRef;
66
+ /**
67
+ * The lead role of a list of roles: the first with hierarchyLevel 1 that can
68
+ * delegate, else the first role.
69
+ *
70
+ * @param roles - Roles
71
+ * @returns Role id, or '' when there are no roles
72
+ */
73
+ export declare function leadRoleOf(roles: TemplateRole[]): string;
74
+ /**
75
+ * Every team of a bundle: the template's own roles as the main team, then
76
+ * `bundle.teams`.
77
+ *
78
+ * @param template - A validated bundle template
79
+ * @returns Teams, main first
80
+ */
81
+ export declare function bundleTeams(template: BundleTemplate): NormalizedBundleTeam[];
82
+ /**
83
+ * Validate a template that carries a `bundle` section.
84
+ *
85
+ * @param raw - Parsed template JSON
86
+ * @param options - File access for `file` references (optional)
87
+ * @returns `{ ok, errors }`
88
+ *
89
+ * @example
90
+ * const { ok, errors } = validateBundleTemplate(JSON.parse(text), { dir, readFile });
91
+ */
92
+ export declare function validateBundleTemplate(raw: unknown, options?: BundleValidationOptions): BundleValidation;
93
+ //# sourceMappingURL=bundle-manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bundle-manifest.d.ts","sourceRoot":"","sources":["../../../../../../backend/src/services/bundle/bundle-manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAC5F,OAAO,KAAK,EAGV,cAAc,EAEf,MAAM,sCAAsC,CAAC;AAG9C,wDAAwD;AACxD,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,OAAO,CAAC;IACZ,iDAAiD;IACjD,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,2EAA2E;AAC3E,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB,kEAAkE;IAClE,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,yDAAyD;AACzD,MAAM,WAAW,SAAS;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wEAAwE;AACxE,MAAM,WAAW,uBAAuB;IACtC,sDAAsD;IACtD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,oEAAoE;IACpE,QAAQ,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;CACpD;AAeD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAEtD;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAgBjE;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAIrD;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,MAAM,CAExD;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,cAAc,GAAG,oBAAoB,EAAE,CAgB5E;AA2GD;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,GAAE,uBAA4B,GAAG,gBAAgB,CAgP5G"}
@@ -0,0 +1,532 @@
1
+ /**
2
+ * Solution bundle manifest: validation and normalization.
3
+ *
4
+ * A bundle is an ordinary team template with a `bundle` section
5
+ * (types/solution-bundle.types.ts). This module checks everything the apply
6
+ * engine relies on, so a broken template is refused before anything is
7
+ * written: roles, a lead per team, member refs, placeholders, cron
8
+ * expressions, question shapes, connectors, file paths.
9
+ *
10
+ * @module services/bundle/bundle-manifest
11
+ */
12
+ import * as path from 'path';
13
+ import { BUNDLE_CONSTANTS, RUNTIME_TYPES } from '../../constants.js';
14
+ import { isValidTemplateRole } from '../../types/team-template.types.js';
15
+ import { listPlaceholders } from './bundle-placeholders.js';
16
+ /** Kebab-case id (file names, keys). */
17
+ const SLUG_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
18
+ /** Question ids double as placeholder names. */
19
+ const QUESTION_ID_PATTERN = /^[a-z][a-z0-9_]*$/;
20
+ /** Session names are built from member names, so they stay ASCII. */
21
+ const ASCII_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9 _-]*$/;
22
+ /** `HH:MM`, 24-hour. */
23
+ const TIME_PATTERN = /^([01]\d|2[0-3]):[0-5]\d$/;
24
+ /** One cron list element: `*`, `n`, `a-b`, with an optional `/step`. */
25
+ const CRON_ELEMENT_PATTERN = /^(\*|\d+(-\d+)?)(\/\d+)?$/;
26
+ /** Allowed ranges of the five cron fields. */
27
+ const CRON_RANGES = [[0, 59], [0, 23], [1, 31], [1, 12], [0, 6]];
28
+ /**
29
+ * Whether a raw template carries a `bundle` section.
30
+ *
31
+ * @param raw - Parsed template JSON
32
+ * @returns True when `bundle` is an object
33
+ */
34
+ export function hasBundleSection(raw) {
35
+ return !!raw && typeof raw === 'object' && !!raw.bundle && typeof raw.bundle === 'object';
36
+ }
37
+ /**
38
+ * Check a five-field cron expression (`*`, numbers, ranges, lists, steps).
39
+ *
40
+ * @param expression - Cron expression
41
+ * @returns True when every field is valid and in range
42
+ *
43
+ * @example
44
+ * isValidCronExpression('0 9 * * 1-5') // true
45
+ */
46
+ export function isValidCronExpression(expression) {
47
+ const fields = expression.trim().split(/\s+/);
48
+ if (fields.length !== 5)
49
+ return false;
50
+ return fields.every((field, i) => {
51
+ const [min, max] = CRON_RANGES[i];
52
+ return field.split(',').every((element) => {
53
+ if (!CRON_ELEMENT_PATTERN.test(element))
54
+ return false;
55
+ const [range, step] = element.split('/');
56
+ if (step !== undefined && Number(step) < 1)
57
+ return false;
58
+ if (range === '*')
59
+ return true;
60
+ const [a, b] = range.split('-').map(Number);
61
+ if (a < min || a > max)
62
+ return false;
63
+ if (b !== undefined && (b < a || b > max))
64
+ return false;
65
+ return true;
66
+ });
67
+ });
68
+ }
69
+ /**
70
+ * Parse a member reference.
71
+ *
72
+ * @param ref - `role` (main team) or `team/role`
73
+ * @returns The team key and role
74
+ */
75
+ export function parseMemberRef(ref) {
76
+ const slash = ref.indexOf('/');
77
+ if (slash < 0)
78
+ return { teamKey: BUNDLE_CONSTANTS.MAIN_TEAM_KEY, role: ref };
79
+ return { teamKey: ref.slice(0, slash), role: ref.slice(slash + 1) };
80
+ }
81
+ /**
82
+ * The lead role of a list of roles: the first with hierarchyLevel 1 that can
83
+ * delegate, else the first role.
84
+ *
85
+ * @param roles - Roles
86
+ * @returns Role id, or '' when there are no roles
87
+ */
88
+ export function leadRoleOf(roles) {
89
+ return (roles.find((r) => r.hierarchyLevel === 1 && r.canDelegate) ?? roles[0])?.role ?? '';
90
+ }
91
+ /**
92
+ * Every team of a bundle: the template's own roles as the main team, then
93
+ * `bundle.teams`.
94
+ *
95
+ * @param template - A validated bundle template
96
+ * @returns Teams, main first
97
+ */
98
+ export function bundleTeams(template) {
99
+ const main = {
100
+ key: BUNDLE_CONSTANTS.MAIN_TEAM_KEY,
101
+ name: template.bundle.teamName ?? template.name,
102
+ description: template.description,
103
+ roles: template.roles,
104
+ leadRole: leadRoleOf(template.roles),
105
+ };
106
+ const extra = (template.bundle.teams ?? []).map((t) => ({
107
+ key: t.key,
108
+ name: t.name,
109
+ description: t.description ?? '',
110
+ roles: t.roles,
111
+ leadRole: leadRoleOf(t.roles),
112
+ }));
113
+ return [main, ...extra];
114
+ }
115
+ /**
116
+ * Whether a relative file path stays inside the template directory.
117
+ *
118
+ * @param file - Path from the manifest
119
+ * @returns True for a plain relative path without `..`
120
+ */
121
+ function isSafeRelativePath(file) {
122
+ if (!file || path.isAbsolute(file))
123
+ return false;
124
+ return !file.split(/[\\/]/).includes('..');
125
+ }
126
+ /**
127
+ * Validate a question list.
128
+ *
129
+ * @param questions - Questions (unknown shape)
130
+ * @param errors - Collects problems
131
+ * @returns The ids of the well-formed questions
132
+ */
133
+ function validateQuestions(questions, errors) {
134
+ const ids = new Set();
135
+ if (questions === undefined)
136
+ return ids;
137
+ if (!Array.isArray(questions)) {
138
+ errors.push('bundle.questions: must be an array');
139
+ return ids;
140
+ }
141
+ questions.forEach((raw, i) => {
142
+ const q = raw;
143
+ const at = `bundle.questions[${i}]`;
144
+ if (typeof q.id !== 'string' || !QUESTION_ID_PATTERN.test(q.id)) {
145
+ errors.push(`${at}.id: lower snake case required (it is the placeholder name)`);
146
+ return;
147
+ }
148
+ if (ids.has(q.id))
149
+ errors.push(`${at}.id: duplicate "${q.id}"`);
150
+ if (BUNDLE_CONSTANTS.BUILTIN_PLACEHOLDERS.includes(q.id))
151
+ errors.push(`${at}.id: "${q.id}" is a built-in placeholder`);
152
+ ids.add(q.id);
153
+ if (typeof q.label !== 'string' || q.label.trim() === '')
154
+ errors.push(`${at}.label: required`);
155
+ if (!['text', 'textarea', 'select', 'multiselect'].includes(q.type))
156
+ errors.push(`${at}.type: text | textarea | select | multiselect`);
157
+ if (typeof q.required !== 'boolean')
158
+ errors.push(`${at}.required: boolean required`);
159
+ const choice = q.type === 'select' || q.type === 'multiselect';
160
+ const values = new Set();
161
+ if (choice) {
162
+ if (!Array.isArray(q.options) || q.options.length === 0) {
163
+ errors.push(`${at}.options: a ${q.type} question needs options`);
164
+ }
165
+ else {
166
+ q.options.forEach((o, j) => {
167
+ if (!o || typeof o.value !== 'string' || o.value === '')
168
+ errors.push(`${at}.options[${j}].value: required`);
169
+ else
170
+ values.add(o.value);
171
+ });
172
+ }
173
+ }
174
+ if (q.required === false && q.default === undefined) {
175
+ errors.push(`${at}.default: an optional question needs a default (may be "")`);
176
+ }
177
+ if (q.default !== undefined) {
178
+ const defaults = Array.isArray(q.default) ? q.default : [q.default];
179
+ if (!defaults.every((d) => typeof d === 'string'))
180
+ errors.push(`${at}.default: string or string[]`);
181
+ if (choice && values.size > 0) {
182
+ const bad = defaults.filter((d) => d !== '' && !values.has(d));
183
+ if (bad.length > 0)
184
+ errors.push(`${at}.default: not an option: ${bad.join(', ')}`);
185
+ }
186
+ if (q.type === 'select' && Array.isArray(q.default))
187
+ errors.push(`${at}.default: a select default is one value`);
188
+ }
189
+ });
190
+ return ids;
191
+ }
192
+ /**
193
+ * Validate the roles of one team.
194
+ *
195
+ * @param roles - Roles (unknown shape)
196
+ * @param at - Path for messages
197
+ * @param errors - Collects problems
198
+ * @returns Role ids of the well-formed roles
199
+ */
200
+ function validateRoles(roles, at, errors) {
201
+ const ids = new Set();
202
+ if (!Array.isArray(roles) || roles.length === 0) {
203
+ errors.push(`${at}: at least one role (the "roles" format; members[] templates cannot be bundles)`);
204
+ return ids;
205
+ }
206
+ const names = new Set();
207
+ roles.forEach((raw, i) => {
208
+ if (!isValidTemplateRole(raw)) {
209
+ errors.push(`${at}[${i}]: needs role, label, defaultName, count ≥ 1, hierarchyLevel, canDelegate, defaultSkills`);
210
+ return;
211
+ }
212
+ if (ids.has(raw.role))
213
+ errors.push(`${at}[${i}].role: duplicate "${raw.role}"`);
214
+ ids.add(raw.role);
215
+ if (!ASCII_NAME_PATTERN.test(raw.defaultName)) {
216
+ errors.push(`${at}[${i}].defaultName: ASCII letters, digits, space, - or _ (session names are built from it); put the Chinese title in jobTitle`);
217
+ }
218
+ const lower = raw.defaultName.toLowerCase();
219
+ if (names.has(lower))
220
+ errors.push(`${at}[${i}].defaultName: duplicate "${raw.defaultName}"`);
221
+ names.add(lower);
222
+ });
223
+ const typed = roles.filter(isValidTemplateRole);
224
+ if (typed.length > 0 && !typed.some((r) => r.hierarchyLevel === 1 && r.canDelegate)) {
225
+ errors.push(`${at}: one role must be the lead (hierarchyLevel 1, canDelegate true)`);
226
+ }
227
+ for (const r of typed) {
228
+ if (r.reportsTo && !ids.has(r.reportsTo))
229
+ errors.push(`${at}: "${r.role}" reports to unknown role "${r.reportsTo}"`);
230
+ }
231
+ return ids;
232
+ }
233
+ /**
234
+ * Validate a template that carries a `bundle` section.
235
+ *
236
+ * @param raw - Parsed template JSON
237
+ * @param options - File access for `file` references (optional)
238
+ * @returns `{ ok, errors }`
239
+ *
240
+ * @example
241
+ * const { ok, errors } = validateBundleTemplate(JSON.parse(text), { dir, readFile });
242
+ */
243
+ export function validateBundleTemplate(raw, options = {}) {
244
+ const errors = [];
245
+ if (!raw || typeof raw !== 'object')
246
+ return { ok: false, errors: ['template: must be an object'] };
247
+ const t = raw;
248
+ if (typeof t.id !== 'string' || !SLUG_PATTERN.test(t.id))
249
+ errors.push('id: kebab-case required');
250
+ if (typeof t.name !== 'string' || t.name.trim() === '')
251
+ errors.push('name: required');
252
+ if (typeof t.description !== 'string')
253
+ errors.push('description: required');
254
+ if (!hasBundleSection(t)) {
255
+ errors.push('bundle: section required');
256
+ return { ok: false, errors };
257
+ }
258
+ const b = t.bundle;
259
+ if (b.schemaVersion !== BUNDLE_CONSTANTS.SCHEMA_VERSION)
260
+ errors.push(`bundle.schemaVersion: must be ${BUNDLE_CONSTANTS.SCHEMA_VERSION}`);
261
+ if (b.status !== undefined && !Object.values(BUNDLE_CONSTANTS.STATUS).includes(b.status))
262
+ errors.push('bundle.status: ready | draft');
263
+ for (const key of ['label', 'tagline', 'ownerSummary']) {
264
+ if (typeof b[key] !== 'string' || b[key].trim() === '')
265
+ errors.push(`bundle.${key}: required`);
266
+ }
267
+ const runtimes = Object.values(RUNTIME_TYPES);
268
+ if (!b.runtime || !runtimes.includes(b.runtime.recommended)) {
269
+ errors.push(`bundle.runtime.recommended: one of ${runtimes.join(', ')}`);
270
+ }
271
+ else if (b.runtime.compatible && !b.runtime.compatible.every((r) => runtimes.includes(r))) {
272
+ errors.push('bundle.runtime.compatible: unknown runtime');
273
+ }
274
+ if (!b.server || !BUNDLE_CONSTANTS.SERVER_TIERS.includes(b.server.tier)) {
275
+ errors.push(`bundle.server.tier: one of ${BUNDLE_CONSTANTS.SERVER_TIERS.join(', ')}`);
276
+ }
277
+ // Teams and roles
278
+ const mainRoles = validateRoles(t.roles, 'roles', errors);
279
+ const rolesByTeam = new Map([[BUNDLE_CONSTANTS.MAIN_TEAM_KEY, mainRoles]]);
280
+ if (b.teams !== undefined) {
281
+ if (!Array.isArray(b.teams))
282
+ errors.push('bundle.teams: must be an array');
283
+ else {
284
+ b.teams.forEach((team, i) => {
285
+ const at = `bundle.teams[${i}]`;
286
+ if (!team || typeof team.key !== 'string' || !SLUG_PATTERN.test(team.key)) {
287
+ errors.push(`${at}.key: kebab-case required`);
288
+ return;
289
+ }
290
+ if (rolesByTeam.has(team.key))
291
+ errors.push(`${at}.key: duplicate "${team.key}" (main is reserved)`);
292
+ if (typeof team.name !== 'string' || team.name.trim() === '')
293
+ errors.push(`${at}.name: required`);
294
+ rolesByTeam.set(team.key, validateRoles(team.roles, `${at}.roles`, errors));
295
+ });
296
+ }
297
+ }
298
+ const questionIds = validateQuestions(b.questions, errors);
299
+ const known = new Set([...questionIds, ...BUNDLE_CONSTANTS.BUILTIN_PLACEHOLDERS]);
300
+ const checkText = (text, at) => {
301
+ if (typeof text !== 'string')
302
+ return;
303
+ const unknown = listPlaceholders(text).filter((p) => !known.has(p));
304
+ if (unknown.length > 0)
305
+ errors.push(`${at}: placeholder without a question: {{${unknown.join('}}, {{')}}}`);
306
+ };
307
+ const checkRef = (ref, at, allowStar = false) => {
308
+ if (typeof ref !== 'string' || ref === '') {
309
+ errors.push(`${at}: member ref required`);
310
+ return;
311
+ }
312
+ if (allowStar && ref === '*')
313
+ return;
314
+ const { teamKey, role } = parseMemberRef(ref);
315
+ const roles = rolesByTeam.get(teamKey);
316
+ if (!roles)
317
+ errors.push(`${at}: unknown team "${teamKey}"`);
318
+ else if (!roles.has(role))
319
+ errors.push(`${at}: unknown role "${role}" in team "${teamKey}"`);
320
+ };
321
+ const checkTeamKey = (key, at) => {
322
+ if (key !== undefined && (typeof key !== 'string' || !rolesByTeam.has(key)))
323
+ errors.push(`${at}: unknown team "${String(key)}"`);
324
+ };
325
+ // Placeholders in prompts and names
326
+ checkText(b.teamName, 'bundle.teamName');
327
+ checkText(b.timezone, 'bundle.timezone');
328
+ checkText(t.mission, 'mission');
329
+ const allRoleLists = [['roles', t.roles], ...(Array.isArray(b.teams) ? b.teams.map((team, i) => [`bundle.teams[${i}].roles`, team?.roles]) : [])];
330
+ for (const [at, roles] of allRoleLists) {
331
+ if (!Array.isArray(roles))
332
+ continue;
333
+ roles.forEach((r, i) => {
334
+ checkText(r?.promptAdditions, `${at}[${i}].promptAdditions`);
335
+ checkText(r?.jobDescription, `${at}[${i}].jobDescription`);
336
+ });
337
+ }
338
+ if (Array.isArray(b.teams)) {
339
+ b.teams.forEach((team, i) => {
340
+ checkText(team?.name, `bundle.teams[${i}].name`);
341
+ checkText(team?.description, `bundle.teams[${i}].description`);
342
+ });
343
+ }
344
+ // Norms and SOPs
345
+ const docLists = [['norms', b.norms], ['sops', b.sops]];
346
+ for (const [kind, list] of docLists) {
347
+ if (list === undefined)
348
+ continue;
349
+ if (!Array.isArray(list)) {
350
+ errors.push(`bundle.${kind}: must be an array`);
351
+ continue;
352
+ }
353
+ const seen = new Set();
354
+ list.forEach((doc, i) => {
355
+ const at = `bundle.${kind}[${i}]`;
356
+ if (typeof doc?.id !== 'string' || !SLUG_PATTERN.test(doc.id))
357
+ errors.push(`${at}.id: kebab-case required`);
358
+ else {
359
+ const key = `${String(doc.team ?? BUNDLE_CONSTANTS.MAIN_TEAM_KEY)}/${String(doc.category ?? '')}/${doc.id}`;
360
+ if (seen.has(key))
361
+ errors.push(`${at}.id: duplicate "${doc.id}"`);
362
+ seen.add(key);
363
+ if (kind === 'norms' && doc.id === BUNDLE_CONSTANTS.REVIEW_POINTS_NORM_ID)
364
+ errors.push(`${at}.id: "${doc.id}" is written from reviewPoints`);
365
+ }
366
+ if (typeof doc?.title !== 'string' || doc.title.trim() === '')
367
+ errors.push(`${at}.title: required`);
368
+ if (doc?.category !== undefined && (typeof doc.category !== 'string' || !SLUG_PATTERN.test(doc.category)))
369
+ errors.push(`${at}.category: kebab-case`);
370
+ checkTeamKey(doc?.team, `${at}.team`);
371
+ const hasContent = typeof doc?.content === 'string' && doc.content.trim() !== '';
372
+ const hasFile = typeof doc?.file === 'string';
373
+ if (hasContent === hasFile) {
374
+ errors.push(`${at}: exactly one of content / file`);
375
+ return;
376
+ }
377
+ if (hasContent)
378
+ checkText(doc.content, `${at}.content`);
379
+ if (hasFile) {
380
+ const file = doc.file;
381
+ if (!isSafeRelativePath(file)) {
382
+ errors.push(`${at}.file: relative path inside the template directory`);
383
+ return;
384
+ }
385
+ if (options.dir && options.readFile) {
386
+ const body = options.readFile(path.join(options.dir, file));
387
+ if (body === null)
388
+ errors.push(`${at}.file: not found: ${file}`);
389
+ else
390
+ checkText(body, `${at}.file ${file}`);
391
+ }
392
+ }
393
+ });
394
+ }
395
+ // Review points
396
+ if (b.reviewPoints !== undefined) {
397
+ if (!Array.isArray(b.reviewPoints))
398
+ errors.push('bundle.reviewPoints: must be an array');
399
+ else
400
+ b.reviewPoints.forEach((rp, i) => {
401
+ const at = `bundle.reviewPoints[${i}]`;
402
+ if (typeof rp?.id !== 'string' || !SLUG_PATTERN.test(rp.id))
403
+ errors.push(`${at}.id: kebab-case required`);
404
+ if (typeof rp?.what !== 'string' || rp.what.trim() === '')
405
+ errors.push(`${at}.what: required`);
406
+ if (rp?.approver !== 'owner' && rp?.approver !== 'lead')
407
+ errors.push(`${at}.approver: owner | lead`);
408
+ checkText(rp?.what, `${at}.what`);
409
+ checkText(rp?.how, `${at}.how`);
410
+ checkTeamKey(rp?.team, `${at}.team`);
411
+ });
412
+ }
413
+ // Skills
414
+ if (b.skills !== undefined) {
415
+ const lists = [['required', b.skills?.required], ['optional', b.skills?.optional ?? []]];
416
+ for (const [key, list] of lists) {
417
+ if (!Array.isArray(list) || !list.every((s) => typeof s === 'string' && SLUG_PATTERN.test(s))) {
418
+ errors.push(`bundle.skills.${key}: array of skill ids`);
419
+ }
420
+ }
421
+ }
422
+ // Connectors
423
+ if (b.connectors !== undefined) {
424
+ if (!Array.isArray(b.connectors))
425
+ errors.push('bundle.connectors: must be an array');
426
+ else
427
+ b.connectors.forEach((c, i) => {
428
+ const at = `bundle.connectors[${i}]`;
429
+ if (!c || !BUNDLE_CONSTANTS.CONNECTOR_IDS.includes(c.id))
430
+ errors.push(`${at}.id: one of ${BUNDLE_CONSTANTS.CONNECTOR_IDS.join(', ')}`);
431
+ if (typeof c?.required !== 'boolean')
432
+ errors.push(`${at}.required: boolean required`);
433
+ if (typeof c?.why !== 'string' || c.why.trim() === '')
434
+ errors.push(`${at}.why: required (owner-facing)`);
435
+ if (c?.products !== undefined) {
436
+ if (c.id !== 'google-workspace')
437
+ errors.push(`${at}.products: only for google-workspace`);
438
+ else if (!Array.isArray(c.products) || !c.products.every((p) => BUNDLE_CONSTANTS.GOOGLE_PRODUCTS.includes(p))) {
439
+ errors.push(`${at}.products: ${BUNDLE_CONSTANTS.GOOGLE_PRODUCTS.join(' | ')}`);
440
+ }
441
+ }
442
+ });
443
+ }
444
+ // Slack
445
+ if (b.slack !== undefined) {
446
+ const channels = b.slack?.channels ?? [];
447
+ if (!Array.isArray(channels))
448
+ errors.push('bundle.slack.channels: must be an array');
449
+ else {
450
+ const keys = new Set();
451
+ channels.forEach((ch, i) => {
452
+ const at = `bundle.slack.channels[${i}]`;
453
+ if (typeof ch?.key !== 'string' || !SLUG_PATTERN.test(ch.key))
454
+ errors.push(`${at}.key: kebab-case required`);
455
+ else if (keys.has(ch.key))
456
+ errors.push(`${at}.key: duplicate "${ch.key}"`);
457
+ else
458
+ keys.add(ch.key);
459
+ if (typeof ch?.name !== 'string' || ch.name.trim() === '')
460
+ errors.push(`${at}.name: required`);
461
+ checkText(ch?.name, `${at}.name`);
462
+ checkText(ch?.purpose, `${at}.purpose`);
463
+ if (!Array.isArray(ch?.members) || ch.members.length === 0)
464
+ errors.push(`${at}.members: at least one ref or "*"`);
465
+ else
466
+ ch.members.forEach((ref, j) => checkRef(ref, `${at}.members[${j}]`, true));
467
+ });
468
+ }
469
+ }
470
+ // Schedules
471
+ if (b.schedules !== undefined) {
472
+ if (!Array.isArray(b.schedules))
473
+ errors.push('bundle.schedules: must be an array');
474
+ else {
475
+ const ids = new Set();
476
+ b.schedules.forEach((s, i) => {
477
+ const at = `bundle.schedules[${i}]`;
478
+ if (typeof s?.id !== 'string' || !SLUG_PATTERN.test(s.id))
479
+ errors.push(`${at}.id: kebab-case required`);
480
+ else if (ids.has(s.id))
481
+ errors.push(`${at}.id: duplicate "${s.id}"`);
482
+ else
483
+ ids.add(s.id);
484
+ if (typeof s?.title !== 'string' || s.title.trim() === '')
485
+ errors.push(`${at}.title: required`);
486
+ if (typeof s?.cron !== 'string' || !isValidCronExpression(s.cron))
487
+ errors.push(`${at}.cron: five-field cron expression`);
488
+ if (typeof s?.task !== 'string' || s.task.trim() === '')
489
+ errors.push(`${at}.task: required`);
490
+ checkText(s?.task, `${at}.task`);
491
+ checkText(s?.timezone, `${at}.timezone`);
492
+ if (s?.target !== undefined)
493
+ checkRef(s.target, `${at}.target`);
494
+ });
495
+ }
496
+ }
497
+ // First week
498
+ if (b.firstWeek !== undefined) {
499
+ if (!Array.isArray(b.firstWeek))
500
+ errors.push('bundle.firstWeek: must be an array');
501
+ else {
502
+ const ids = new Set();
503
+ b.firstWeek.forEach((task, i) => {
504
+ const at = `bundle.firstWeek[${i}]`;
505
+ if (typeof task?.id !== 'string' || !SLUG_PATTERN.test(task.id))
506
+ errors.push(`${at}.id: kebab-case required`);
507
+ else if (ids.has(task.id))
508
+ errors.push(`${at}.id: duplicate "${task.id}"`);
509
+ else
510
+ ids.add(task.id);
511
+ if (!Number.isInteger(task?.day) || task.day < 0 || task.day > BUNDLE_CONSTANTS.MAX_FIRST_WEEK_DAY) {
512
+ errors.push(`${at}.day: integer 0-${BUNDLE_CONSTANTS.MAX_FIRST_WEEK_DAY}`);
513
+ }
514
+ if (task?.time !== undefined && (typeof task.time !== 'string' || !TIME_PATTERN.test(task.time)))
515
+ errors.push(`${at}.time: HH:MM`);
516
+ if (typeof task?.title !== 'string' || task.title.trim() === '')
517
+ errors.push(`${at}.title: required`);
518
+ if (typeof task?.task !== 'string' || task.task.trim() === '')
519
+ errors.push(`${at}.task: required`);
520
+ checkText(task?.title, `${at}.title`);
521
+ checkText(task?.task, `${at}.task`);
522
+ if (task?.target !== undefined)
523
+ checkRef(task.target, `${at}.target`);
524
+ });
525
+ }
526
+ }
527
+ if (b.todo !== undefined && (!Array.isArray(b.todo) || !b.todo.every((x) => typeof x === 'string'))) {
528
+ errors.push('bundle.todo: array of strings');
529
+ }
530
+ return { ok: errors.length === 0, errors };
531
+ }
532
+ //# sourceMappingURL=bundle-manifest.js.map