@lssm/lib.contracts 0.0.0-canary-20251217062943 → 0.0.0-canary-20251217072406

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 (237) hide show
  1. package/dist/app-config/app-config.feature.js +53 -1
  2. package/dist/app-config/contracts.d.ts +50 -50
  3. package/dist/app-config/contracts.js +396 -1
  4. package/dist/app-config/docs/app-config.docblock.js +22 -220
  5. package/dist/app-config/events.js +168 -1
  6. package/dist/app-config/index.js +8 -1
  7. package/dist/app-config/lifecycle-contracts.js +441 -1
  8. package/dist/app-config/runtime.js +617 -1
  9. package/dist/app-config/spec.js +36 -1
  10. package/dist/app-config/validation.js +538 -1
  11. package/dist/capabilities/docs/capabilities.docblock.js +22 -1
  12. package/dist/capabilities/openbanking.js +92 -1
  13. package/dist/capabilities.js +50 -1
  14. package/dist/client/index.js +9 -1
  15. package/dist/client/react/drivers/rn-reusables.js +21 -1
  16. package/dist/client/react/drivers/shadcn.js +11 -1
  17. package/dist/client/react/feature-render.js +43 -1
  18. package/dist/client/react/form-render.js +298 -1
  19. package/dist/client/react/index.js +8 -1
  20. package/dist/contract-registry/index.js +3 -1
  21. package/dist/contract-registry/schemas.js +61 -1
  22. package/dist/contracts-adapter-hydration.js +41 -1
  23. package/dist/contracts-adapter-input.js +77 -1
  24. package/dist/data-views/docs/data-views.docblock.js +22 -1
  25. package/dist/data-views/query-generator.js +48 -1
  26. package/dist/data-views/runtime.js +39 -1
  27. package/dist/data-views.js +35 -1
  28. package/dist/docs/PUBLISHING.docblock.js +17 -76
  29. package/dist/docs/accessibility_wcag_compliance_specs.docblock.js +17 -350
  30. package/dist/docs/index.js +33 -1
  31. package/dist/docs/meta.docs.js +15 -2
  32. package/dist/docs/presentations.js +77 -1
  33. package/dist/docs/registry.js +51 -1
  34. package/dist/docs/tech/PHASE_1_QUICKSTART.docblock.js +17 -383
  35. package/dist/docs/tech/PHASE_2_AI_NATIVE_OPERATIONS.docblock.js +17 -68
  36. package/dist/docs/tech/PHASE_3_AUTO_EVOLUTION.docblock.js +17 -140
  37. package/dist/docs/tech/PHASE_4_PERSONALIZATION_ENGINE.docblock.js +17 -86
  38. package/dist/docs/tech/PHASE_5_ZERO_TOUCH_OPERATIONS.docblock.js +17 -1
  39. package/dist/docs/tech/auth/better-auth-nextjs.docblock.js +25 -2
  40. package/dist/docs/tech/contracts/README.docblock.js +21 -1
  41. package/dist/docs/tech/contracts/create-subscription.docblock.js +21 -1
  42. package/dist/docs/tech/contracts/graphql-typed-outputs.docblock.js +21 -180
  43. package/dist/docs/tech/contracts/migrations.docblock.js +21 -1
  44. package/dist/docs/tech/contracts/openapi-export.docblock.js +22 -2
  45. package/dist/docs/tech/contracts/ops-to-presentation-linking.docblock.js +19 -60
  46. package/dist/docs/tech/contracts/overlays.docblock.js +21 -68
  47. package/dist/docs/tech/contracts/tests.docblock.js +21 -132
  48. package/dist/docs/tech/contracts/themes.docblock.js +21 -1
  49. package/dist/docs/tech/contracts/vertical-pocket-family-office.docblock.js +21 -106
  50. package/dist/docs/tech/lifecycle-stage-system.docblock.js +17 -213
  51. package/dist/docs/tech/llm/llm-integration.docblock.js +74 -5
  52. package/dist/docs/tech/mcp-endpoints.docblock.js +38 -1
  53. package/dist/docs/tech/presentation-runtime.docblock.js +17 -1
  54. package/dist/docs/tech/schema/README.docblock.js +21 -262
  55. package/dist/docs/tech/studio/learning-events.docblock.js +49 -1
  56. package/dist/docs/tech/studio/learning-journeys.docblock.js +25 -2
  57. package/dist/docs/tech/studio/platform-admin-panel.docblock.js +24 -2
  58. package/dist/docs/tech/studio/project-access-teams.docblock.js +26 -16
  59. package/dist/docs/tech/studio/project-routing.docblock.js +68 -1
  60. package/dist/docs/tech/studio/sandbox-unlogged.docblock.js +23 -2
  61. package/dist/docs/tech/studio/team-invitations.docblock.js +41 -36
  62. package/dist/docs/tech/studio/workspace-ops.docblock.js +48 -1
  63. package/dist/docs/tech/studio/workspaces.docblock.js +24 -2
  64. package/dist/docs/tech/telemetry-ingest.docblock.js +37 -3
  65. package/dist/docs/tech/templates/runtime.docblock.js +21 -1
  66. package/dist/docs/tech/vscode-extension.docblock.js +37 -3
  67. package/dist/docs/tech/workflows/overview.docblock.js +21 -1
  68. package/dist/docs/tech-contracts.docs.js +19 -2
  69. package/dist/events.js +12 -1
  70. package/dist/experiments/docs/experiments.docblock.js +22 -128
  71. package/dist/experiments/evaluator.js +101 -1
  72. package/dist/experiments/spec.js +33 -1
  73. package/dist/features.js +68 -1
  74. package/dist/forms/docs/forms.docblock.js +22 -1
  75. package/dist/forms.js +119 -1
  76. package/dist/index.js +107 -1
  77. package/dist/install.js +40 -1
  78. package/dist/integrations/contracts.d.ts +102 -102
  79. package/dist/integrations/contracts.js +388 -1
  80. package/dist/integrations/docs/integrations.docblock.js +95 -1
  81. package/dist/integrations/health.js +69 -1
  82. package/dist/integrations/index.js +23 -1
  83. package/dist/integrations/openbanking/contracts/accounts.d.ts +66 -66
  84. package/dist/integrations/openbanking/contracts/accounts.js +237 -1
  85. package/dist/integrations/openbanking/contracts/balances.d.ts +34 -34
  86. package/dist/integrations/openbanking/contracts/balances.js +167 -1
  87. package/dist/integrations/openbanking/contracts/index.js +12 -1
  88. package/dist/integrations/openbanking/contracts/transactions.d.ts +48 -48
  89. package/dist/integrations/openbanking/contracts/transactions.js +218 -1
  90. package/dist/integrations/openbanking/guards.js +32 -1
  91. package/dist/integrations/openbanking/models.d.ts +55 -55
  92. package/dist/integrations/openbanking/models.js +242 -1
  93. package/dist/integrations/openbanking/openbanking.feature.js +68 -1
  94. package/dist/integrations/openbanking/telemetry.js +39 -1
  95. package/dist/integrations/providers/elevenlabs.js +56 -1
  96. package/dist/integrations/providers/gcs-storage.js +79 -1
  97. package/dist/integrations/providers/gmail.js +91 -1
  98. package/dist/integrations/providers/google-calendar.js +70 -1
  99. package/dist/integrations/providers/impls/elevenlabs-voice.js +95 -1
  100. package/dist/integrations/providers/impls/gcs-storage.js +88 -1
  101. package/dist/integrations/providers/impls/gmail-inbound.js +200 -1
  102. package/dist/integrations/providers/impls/gmail-outbound.js +104 -5
  103. package/dist/integrations/providers/impls/google-calendar.js +154 -1
  104. package/dist/integrations/providers/impls/index.js +16 -1
  105. package/dist/integrations/providers/impls/mistral-embedding.js +41 -1
  106. package/dist/integrations/providers/impls/mistral-llm.js +247 -1
  107. package/dist/integrations/providers/impls/postmark-email.js +55 -1
  108. package/dist/integrations/providers/impls/powens-client.js +171 -1
  109. package/dist/integrations/providers/impls/powens-openbanking.js +218 -1
  110. package/dist/integrations/providers/impls/provider-factory.js +142 -1
  111. package/dist/integrations/providers/impls/qdrant-vector.js +69 -1
  112. package/dist/integrations/providers/impls/stripe-payments.js +202 -1
  113. package/dist/integrations/providers/impls/twilio-sms.js +58 -1
  114. package/dist/integrations/providers/index.js +13 -1
  115. package/dist/integrations/providers/mistral.js +72 -1
  116. package/dist/integrations/providers/postmark.js +72 -1
  117. package/dist/integrations/providers/powens.js +120 -1
  118. package/dist/integrations/providers/qdrant.js +77 -1
  119. package/dist/integrations/providers/registry.js +34 -1
  120. package/dist/integrations/providers/stripe.js +87 -1
  121. package/dist/integrations/providers/twilio-sms.js +65 -1
  122. package/dist/integrations/runtime.js +186 -1
  123. package/dist/integrations/secrets/aws-secret-manager.js +231 -1
  124. package/dist/integrations/secrets/env-secret-provider.js +81 -1
  125. package/dist/integrations/secrets/gcp-secret-manager.js +229 -1
  126. package/dist/integrations/secrets/index.js +8 -1
  127. package/dist/integrations/secrets/manager.js +103 -1
  128. package/dist/integrations/secrets/provider.js +58 -1
  129. package/dist/integrations/secrets/scaleway-secret-manager.js +247 -1
  130. package/dist/integrations/spec.js +39 -1
  131. package/dist/jobs/define-job.js +16 -1
  132. package/dist/jobs/gcp-cloud-tasks.js +53 -1
  133. package/dist/jobs/gcp-pubsub.js +39 -1
  134. package/dist/jobs/handlers/gmail-sync-handler.js +9 -1
  135. package/dist/jobs/handlers/index.js +12 -1
  136. package/dist/jobs/handlers/ping-handler.js +15 -1
  137. package/dist/jobs/handlers/storage-document-handler.js +14 -1
  138. package/dist/jobs/index.js +4 -1
  139. package/dist/jobs/memory-queue.js +71 -1
  140. package/dist/jobs/queue.js +33 -1
  141. package/dist/jobs/scaleway-sqs-queue.js +153 -1
  142. package/dist/jsonschema.d.ts +3 -3
  143. package/dist/jsonschema.js +32 -1
  144. package/dist/knowledge/contracts.d.ts +66 -66
  145. package/dist/knowledge/contracts.js +317 -1
  146. package/dist/knowledge/docs/knowledge.docblock.js +22 -138
  147. package/dist/knowledge/index.js +10 -1
  148. package/dist/knowledge/ingestion/document-processor.js +54 -1
  149. package/dist/knowledge/ingestion/embedding-service.js +25 -1
  150. package/dist/knowledge/ingestion/gmail-adapter.js +50 -5
  151. package/dist/knowledge/ingestion/index.js +7 -1
  152. package/dist/knowledge/ingestion/storage-adapter.js +26 -1
  153. package/dist/knowledge/ingestion/vector-indexer.js +32 -1
  154. package/dist/knowledge/query/index.js +3 -1
  155. package/dist/knowledge/query/service.js +64 -2
  156. package/dist/knowledge/runtime.js +49 -1
  157. package/dist/knowledge/spaces/email-threads.js +38 -1
  158. package/dist/knowledge/spaces/financial-docs.js +38 -1
  159. package/dist/knowledge/spaces/financial-overview.js +42 -1
  160. package/dist/knowledge/spaces/index.js +8 -1
  161. package/dist/knowledge/spaces/product-canon.js +38 -1
  162. package/dist/knowledge/spaces/support-faq.js +41 -1
  163. package/dist/knowledge/spaces/uploaded-docs.js +38 -1
  164. package/dist/knowledge/spec.js +39 -1
  165. package/dist/llm/exporters.js +541 -8
  166. package/dist/llm/index.js +4 -1
  167. package/dist/llm/prompts.js +246 -56
  168. package/dist/markdown.js +116 -3
  169. package/dist/migrations.js +33 -1
  170. package/dist/onboarding-base.d.ts +29 -29
  171. package/dist/onboarding-base.js +196 -1
  172. package/dist/openapi.js +75 -1
  173. package/dist/openbanking/docs/openbanking.docblock.js +22 -109
  174. package/dist/ownership.js +40 -1
  175. package/dist/policy/docs/policy.docblock.js +22 -1
  176. package/dist/policy/engine.js +223 -1
  177. package/dist/policy/opa-adapter.js +71 -1
  178. package/dist/policy/spec.js +33 -1
  179. package/dist/presentations/docs/presentations-conventions.docblock.js +21 -7
  180. package/dist/presentations.backcompat.js +47 -1
  181. package/dist/presentations.d.ts +3 -3
  182. package/dist/presentations.js +66 -1
  183. package/dist/presentations.v2.js +278 -6
  184. package/dist/prompt.js +10 -1
  185. package/dist/promptRegistry.js +34 -1
  186. package/dist/regenerator/docs/regenerator.docblock.js +22 -184
  187. package/dist/regenerator/executor.js +86 -1
  188. package/dist/regenerator/index.js +6 -1
  189. package/dist/regenerator/service.js +92 -1
  190. package/dist/regenerator/sinks.js +32 -1
  191. package/dist/regenerator/utils.js +51 -1
  192. package/dist/registry.js +208 -1
  193. package/dist/resources.js +47 -1
  194. package/dist/schema/dist/EnumType.js +2 -1
  195. package/dist/schema/dist/FieldType.js +49 -1
  196. package/dist/schema/dist/ScalarTypeEnum.js +236 -1
  197. package/dist/schema/dist/SchemaModel.js +39 -1
  198. package/dist/schema/dist/entity/defineEntity.js +1 -1
  199. package/dist/schema/dist/entity/index.js +2 -1
  200. package/dist/schema/dist/entity/types.js +1 -1
  201. package/dist/schema/dist/index.js +6 -1
  202. package/dist/schema-to-markdown.js +214 -10
  203. package/dist/server/graphql-pothos.js +128 -1
  204. package/dist/server/index.js +10 -1
  205. package/dist/server/mcp/createMcpServer.js +28 -1
  206. package/dist/server/mcp/registerPresentations.js +151 -1
  207. package/dist/server/mcp/registerPrompts.js +36 -2
  208. package/dist/server/mcp/registerResources.js +35 -1
  209. package/dist/server/mcp/registerTools.js +22 -1
  210. package/dist/server/provider-mcp.js +3 -1
  211. package/dist/server/rest-elysia.js +20 -1
  212. package/dist/server/rest-express.js +39 -1
  213. package/dist/server/rest-generic.js +125 -1
  214. package/dist/server/rest-next-app.js +38 -1
  215. package/dist/server/rest-next-mcp.js +45 -1
  216. package/dist/server/rest-next-pages.js +25 -1
  217. package/dist/spec.js +35 -1
  218. package/dist/telemetry/anomaly.js +48 -1
  219. package/dist/telemetry/docs/telemetry.docblock.js +22 -139
  220. package/dist/telemetry/index.js +5 -1
  221. package/dist/telemetry/spec.js +69 -1
  222. package/dist/telemetry/tracker.js +76 -1
  223. package/dist/tests/index.js +4 -1
  224. package/dist/tests/runner.js +150 -1
  225. package/dist/tests/spec.js +33 -1
  226. package/dist/themes.js +39 -1
  227. package/dist/workflow/adapters/db-adapter.js +83 -1
  228. package/dist/workflow/adapters/file-adapter.js +11 -1
  229. package/dist/workflow/adapters/index.js +5 -1
  230. package/dist/workflow/adapters/memory-store.js +58 -1
  231. package/dist/workflow/expression.js +98 -1
  232. package/dist/workflow/index.js +9 -1
  233. package/dist/workflow/runner.js +337 -1
  234. package/dist/workflow/sla-monitor.js +47 -1
  235. package/dist/workflow/spec.js +32 -1
  236. package/dist/workflow/validation.js +175 -1
  237. package/package.json +11 -4
@@ -1 +1,538 @@
1
- var e=class{blueprintRules=[];tenantRules=[];resolvedRules=[];register(e){return e.scope===`blueprint`?this.blueprintRules.push(e):e.scope===`tenant`?this.tenantRules.push(e):this.resolvedRules.push(e),this}validateBlueprint(e,t){return w(this.blueprintRules.flatMap(n=>n.validate(e,t)))}validateTenant(e,t,n){return w(this.tenantRules.flatMap(r=>r.validate(e,t,n)))}validateResolved(e,t,n){return w(this.resolvedRules.flatMap(r=>r.validate(e,t,n)))}};const t=/^(?!:\/\/)([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.[A-Za-z]{2,}$/,n=/^https:\/\//i,r={logo:[`png`,`svg`,`webp`],"logo-dark":[`png`,`svg`,`webp`],favicon:[`ico`,`png`,`svg`],"og-image":[`png`,`jpg`,`jpeg`,`webp`]},i={};let a;function o(){return a||=p(),a}function s(e,t,n=i){let r=o();return f(d(r.validateBlueprint(e,n)),d(r.validateTenant(e,t,n)))}function c(e,t=i){return d(o().validateBlueprint(e,t))}function l(e,t,n=i){return d(o().validateTenant(e,t,n))}function u(e,t,n=i){return d(o().validateResolved(e,t,n))}function d(e){let t=e.filter(e=>e.severity===`error`),n=e.filter(e=>e.severity===`warning`),r=e.filter(e=>e.severity===`info`);return{valid:t.length===0,errors:t,warnings:n,info:r}}function f(...e){let t=e.flatMap(e=>e.errors),n=e.flatMap(e=>e.warnings),r=e.flatMap(e=>e.info);return{valid:t.length===0,errors:t,warnings:n,info:r}}function p(){return new e().register(m).register(h).register(g).register(_).register(v).register(y).register(b).register(x).register(S)}const m={scope:`blueprint`,name:`integration.duplicate-slots`,category:`integration`,validate(e){let t=[],n=new Set;for(let r of e.integrationSlots??[])n.has(r.slotId)?t.push(C({code:`DUPLICATE_SLOT`,severity:`error`,path:`integrationSlots.${r.slotId}`,message:`Duplicate integration slot id "${r.slotId}".`})):n.add(r.slotId),r.allowedModes&&r.allowedModes.length===0&&t.push(C({code:`EMPTY_ALLOWED_MODES`,severity:`warning`,path:`integrationSlots.${r.slotId}.allowedModes`,message:`allowedModes is empty; the slot will accept any supported mode.`}));return e.branding&&!e.branding.appNameKey.trim()&&t.push(C({code:`MISSING_APP_NAME_KEY`,severity:`warning`,path:`branding.appNameKey`,message:`branding.appNameKey should reference a translation catalog key.`})),t}},h={scope:`blueprint`,name:`capability.registry-check`,category:`capability`,validate(e,t){let n=[],r=t.capabilities;return r&&(e.capabilities?.enabled??[]).forEach((e,t)=>{r.get(e.key,e.version)||n.push(C({code:`MISSING_CAPABILITY`,severity:`error`,path:`capabilities.enabled[${t}]`,message:`Capability "${e.key}@${e.version}" is not registered.`}))}),n}},g={scope:`tenant`,name:`capability.required-enabled`,category:`capability`,validate(e,t,n){let r=[],i=n.capabilities,a=new Set((e.capabilities?.enabled??[]).map(T)),o=t.capabilities?.disable??[];return o.forEach((e,t)=>{a.has(T(e))&&r.push(C({code:`DISABLED_REQUIRED_CAPABILITY`,severity:`error`,path:`capabilities.disable[${t}]`,message:`Capability "${e.key}@${e.version}" is required by the blueprint and cannot be disabled.`}))}),i&&((t.capabilities?.enable??[]).forEach((e,t)=>{i.get(e.key,e.version)||r.push(C({code:`UNKNOWN_CAPABILITY_ENABLE`,severity:`error`,path:`capabilities.enable[${t}]`,message:`Capability "${e.key}@${e.version}" is not registered.`}))}),o.forEach((e,t)=>{i.get(e.key,e.version)||r.push(C({code:`UNKNOWN_CAPABILITY_DISABLE`,severity:`warning`,path:`capabilities.disable[${t}]`,message:`Capability "${e.key}@${e.version}" is not registered.`}))})),r}},_={scope:`tenant`,name:`integration.slot-binding`,category:`integration`,validate(e,t,n){let r=[],i=new Map((e.integrationSlots??[]).map(e=>[e.slotId,e])),a=t.integrations??[],o=n.tenantConnections?.reduce((e,t)=>e.set(t.meta.id,t),new Map)??new Map,s=new Set;a.forEach(e=>{let a=`integrations.${e.slotId}`,c=i.get(e.slotId);if(!c){r.push(C({code:`UNKNOWN_SLOT_BINDING`,severity:`error`,path:a,message:`Integration slot "${e.slotId}" is not defined in the blueprint.`}));return}let l=!0,u=o.get(e.connectionId);if(!u){r.push(C({code:`MISSING_INTEGRATION_CONNECTION`,severity:`error`,path:a,message:`Integration connection "${e.connectionId}" was not found for tenant "${t.meta.tenantId}".`})),l=!1;return}u.meta.tenantId!==t.meta.tenantId&&(r.push(C({code:`FOREIGN_CONNECTION`,severity:`error`,path:a,message:`Connection "${e.connectionId}" belongs to tenant "${u.meta.tenantId}", not "${t.meta.tenantId}".`})),l=!1),c.allowedModes&&c.allowedModes.length>0&&(c.allowedModes.includes(u.ownershipMode)||(r.push(C({code:`MODE_MISMATCH`,severity:`error`,path:a,message:`Slot "${c.slotId}" only allows modes [${c.allowedModes.join(`, `)}] but connection is "${u.ownershipMode}".`})),l=!1)),u.status===`disconnected`||u.status===`error`?(r.push(C({code:`CONNECTION_NOT_READY`,severity:`error`,path:a,message:`Connection "${u.meta.label}" is in status "${u.status}".`})),l=!1):u.status===`unknown`&&r.push(C({code:`CONNECTION_STATUS_UNKNOWN`,severity:`warning`,path:a,message:`Connection "${u.meta.label}" has unknown health status.`}));let d=D(n.integrationSpecs,u);if(!d){r.push(C({code:`INTEGRATION_SPEC_NOT_FOUND`,severity:`warning`,path:a,message:`Integration spec "${u.meta.integrationKey}@${u.meta.integrationVersion}" is not registered.`})),l=!1;return}d.meta.category!==c.requiredCategory&&(r.push(C({code:`CATEGORY_MISMATCH`,severity:`error`,path:a,message:`Slot "${c.slotId}" requires category "${c.requiredCategory}" but connection provides "${d.meta.category}".`})),l=!1),d.supportedModes.includes(u.ownershipMode)||(r.push(C({code:`UNSUPPORTED_OWNERSHIP_MODE`,severity:`error`,path:a,message:`Integration spec "${d.meta.key}" does not support ownership mode "${u.ownershipMode}".`})),l=!1);for(let e of c.requiredCapabilities??[])E(d,e)||(r.push(C({code:`CAPABILITY_NOT_PROVIDED`,severity:`error`,path:a,message:`Integration "${d.meta.key}" does not provide required capability "${e.key}@${e.version}".`})),l=!1);l&&s.add(c.slotId)});for(let e of i.values())e.required&&!s.has(e.slotId)&&r.push(C({code:`MISSING_REQUIRED_SLOT`,severity:`error`,path:`integrations.${e.slotId}`,message:`Required integration slot "${e.slotId}" is not bound.`}));return r}},v={scope:`tenant`,name:`knowledge.bindings`,category:`knowledge`,validate(e,t,n){let r=[],i=n.knowledgeSpaces;if(!i)return r;let a=n.knowledgeSources??[];return(t.knowledge??[]).forEach((e,n)=>{let o=`knowledge[${n}]`,s=i.get(e.spaceKey,e.spaceVersion);if(!s){r.push(C({code:`UNKNOWN_KNOWLEDGE_SPACE`,severity:`error`,path:`${o}.spaceKey`,message:`Knowledge space "${e.spaceKey}" is not registered.`}));return}a.some(t=>t.meta.spaceKey===e.spaceKey?e.spaceVersion==null?!0:t.meta.spaceVersion===e.spaceVersion:!1)||r.push(C({code:`MISSING_KNOWLEDGE_SOURCES`,severity:`error`,path:o,message:`Knowledge space "${e.spaceKey}" has no configured sources for tenant "${t.meta.tenantId}".`})),(s.meta.category===`external`||s.meta.category===`ephemeral`)&&r.push(C({code:`LOW_TRUST_KNOWLEDGE`,severity:`warning`,path:o,message:`Knowledge space "${e.spaceKey}" has category "${s.meta.category}". Avoid using it for irreversible or policy decisions.`}))}),r}},y={scope:`tenant`,name:`branding.constraints`,category:`branding`,validate(e,i,a){let o=[],s=i.branding;if(!s)return o;let c=s.customDomain?.trim();if(c){t.test(c)||o.push(C({code:`INVALID_DOMAIN`,severity:`error`,path:`branding.customDomain`,message:`Custom domain "${c}" is not a valid hostname.`}));let e=(a.existingConfigs??[]).find(e=>{if(e.meta.id===i.meta.id)return!1;let t=e.branding?.customDomain?.trim();return t?t.toLowerCase()===c.toLowerCase():!1});e&&o.push(C({code:`DUPLICATE_DOMAIN`,severity:`error`,path:`branding.customDomain`,message:`Custom domain "${c}" is already used by tenant "${e.meta.tenantId}".`}))}return(s.assets??[]).forEach((e,t)=>{let i=`branding.assets[${t}]`;n.test(e.url)||o.push(C({code:`INSECURE_ASSET_URL`,severity:`error`,path:`${i}.url`,message:`Branding asset "${e.type}" must use an HTTPS URL.`}));let a=r[e.type]??r.logo,s=O(e.url);s&&a&&!a.includes(s)&&o.push(C({code:`UNEXPECTED_ASSET_TYPE`,severity:`warning`,path:`${i}.url`,message:`Asset "${e.type}" should use one of: ${(a??[]).join(`, `)}. Detected "${s}".`}))}),o}},b={scope:`tenant`,name:`translation.consistency`,category:`translation`,validate(e,t,n){let r=[],i=e.translationCatalog,a=k(n.translationCatalogs),o=i?a.blueprint.find(e=>e.meta.name===i.name&&e.meta.version===i.version):void 0;i&&!o&&r.push(C({code:`MISSING_BLUEPRINT_CATALOG`,severity:`error`,path:`translationCatalog`,message:`Blueprint translation catalog "${i.name}@${i.version}" is not loaded in context.`}));let s=new Set;e.branding?.appNameKey&&(s.add(e.branding.appNameKey),o&&!A(o.entries,e.branding.appNameKey,o.defaultLocale)&&r.push(C({code:`MISSING_TRANSLATION_KEY`,severity:`error`,path:`branding.appNameKey`,message:`Translation key "${e.branding.appNameKey}" is missing for locale "${o.defaultLocale}".`})));let c=t.locales;if(c){let{defaultLocale:e,enabledLocales:t}=c;if(t.includes(e)||r.push(C({code:`LOCALE_NOT_ENABLED`,severity:`warning`,path:`locales.enabledLocales`,message:`enabledLocales does not include defaultLocale "${e}".`})),o){let n=new Set(o.supportedLocales);for(let i of[e,...t])n.has(i)||r.push(C({code:`UNSUPPORTED_LOCALE`,severity:`error`,path:`locales.enabledLocales`,message:`Locale "${i}" is not supported by blueprint catalog "${o.meta.name}".`}))}}let l=new Set;for(let e of a.blueprint)for(let t of e.entries)l.add(t.key);for(let e of a.platform)for(let t of e.entries)l.add(t.key);return(t.translationOverrides?.entries??[]).forEach((e,n)=>{let i=`translationOverrides.entries[${n}]`;l.has(e.key)||r.push(C({code:`UNKNOWN_TRANSLATION_KEY`,severity:`error`,path:i,message:`Override references unknown key "${e.key}".`})),o&&(new Set([...o.supportedLocales,...t.locales?.enabledLocales??[],t.locales?.defaultLocale??o.defaultLocale]).has(e.locale)||r.push(C({code:`UNSUPPORTED_LOCALE_OVERRIDE`,severity:`error`,path:i,message:`Locale "${e.locale}" is not enabled for tenant overrides.`})))}),r}},x={scope:`resolved`,name:`integration.required-slots`,category:`integration`,validate(e,t){let n=[],r=(e.integrationSlots??[]).filter(e=>e.required);for(let e of r)t.integrations.some(t=>t.slot.slotId===e.slotId)||n.push(C({code:`MISSING_REQUIRED_SLOT`,severity:`error`,path:`integrations.${e.slotId}`,message:`Resolved config is missing integration slot "${e.slotId}".`}));for(let e of t.integrations)(e.connection.status===`disconnected`||e.connection.status===`error`)&&n.push(C({code:`CONNECTION_NOT_READY`,severity:`error`,path:`integrations.${e.slot.slotId}`,message:`Resolved integration "${e.slot.slotId}" uses a connection in status "${e.connection.status}".`}));return n}},S={scope:`resolved`,name:`translation.default-locale`,category:`translation`,validate(e,t){let n=[],r=t.translation;return r&&!r.supportedLocales.includes(r.defaultLocale)&&n.push(C({code:`DEFAULT_LOCALE_NOT_SUPPORTED`,severity:`warning`,path:`translation.defaultLocale`,message:`supportedLocales should include defaultLocale for consistent fallback behaviour.`})),n}};function C(e){return e}function w(e){let t=new Map;for(let n of e){let e=`${n.code}|${n.path}|${n.severity}`;t.has(e)||t.set(e,n)}return[...t.values()]}function T(e){return`${e.key}@${e.version}`}function E(e,t){return e.capabilities.provides.some(e=>e.key===t.key&&e.version===t.version)}function D(e,t){if(e)return e.get(t.meta.integrationKey,t.meta.integrationVersion)}function O(e){let[t]=e.split(`?`);if(!t)return;let n=t.lastIndexOf(`.`);if(n!==-1)return t.slice(n+1).toLowerCase()}function k(e){return e?{platform:e.platform?Array.isArray(e.platform)?e.platform:[e.platform]:[],blueprint:e.blueprint?Array.isArray(e.blueprint)?e.blueprint:[e.blueprint]:[]}:{platform:[],blueprint:[]}}function A(e,t,n){return e.some(e=>e.key===t&&e.locale===n)}export{c as validateBlueprint,s as validateConfig,u as validateResolvedConfig,l as validateTenantConfig};
1
+ //#region src/app-config/validation.ts
2
+ var ValidationRuleRegistry = class {
3
+ blueprintRules = [];
4
+ tenantRules = [];
5
+ resolvedRules = [];
6
+ register(rule) {
7
+ if (rule.scope === "blueprint") this.blueprintRules.push(rule);
8
+ else if (rule.scope === "tenant") this.tenantRules.push(rule);
9
+ else this.resolvedRules.push(rule);
10
+ return this;
11
+ }
12
+ validateBlueprint(blueprint, context) {
13
+ return dedupeIssues(this.blueprintRules.flatMap((rule) => rule.validate(blueprint, context)));
14
+ }
15
+ validateTenant(blueprint, tenant, context) {
16
+ return dedupeIssues(this.tenantRules.flatMap((rule) => rule.validate(blueprint, tenant, context)));
17
+ }
18
+ validateResolved(blueprint, resolved, context) {
19
+ return dedupeIssues(this.resolvedRules.flatMap((rule) => rule.validate(blueprint, resolved, context)));
20
+ }
21
+ };
22
+ const DOMAIN_REGEX = /^(?!:\/\/)([a-zA-Z0-9-]+\.)*[a-zA-Z0-9-]+\.[A-Za-z]{2,}$/;
23
+ const HTTPS_URL_REGEX = /^https:\/\//i;
24
+ const ALLOWED_ASSET_EXTENSIONS = {
25
+ logo: [
26
+ "png",
27
+ "svg",
28
+ "webp"
29
+ ],
30
+ "logo-dark": [
31
+ "png",
32
+ "svg",
33
+ "webp"
34
+ ],
35
+ favicon: [
36
+ "ico",
37
+ "png",
38
+ "svg"
39
+ ],
40
+ "og-image": [
41
+ "png",
42
+ "jpg",
43
+ "jpeg",
44
+ "webp"
45
+ ]
46
+ };
47
+ const EMPTY_CONTEXT = {};
48
+ let registryInstance;
49
+ function getRegistry() {
50
+ if (!registryInstance) registryInstance = createDefaultRegistry();
51
+ return registryInstance;
52
+ }
53
+ function validateConfig(blueprint, tenant, context = EMPTY_CONTEXT) {
54
+ const registry = getRegistry();
55
+ return mergeResults(buildResult(registry.validateBlueprint(blueprint, context)), buildResult(registry.validateTenant(blueprint, tenant, context)));
56
+ }
57
+ function validateBlueprint(blueprint, context = EMPTY_CONTEXT) {
58
+ return buildResult(getRegistry().validateBlueprint(blueprint, context));
59
+ }
60
+ function validateTenantConfig(blueprint, tenant, context = EMPTY_CONTEXT) {
61
+ return buildResult(getRegistry().validateTenant(blueprint, tenant, context));
62
+ }
63
+ function validateResolvedConfig(blueprint, resolved, context = EMPTY_CONTEXT) {
64
+ return buildResult(getRegistry().validateResolved(blueprint, resolved, context));
65
+ }
66
+ function buildResult(issues) {
67
+ const errors = issues.filter((issue$1) => issue$1.severity === "error");
68
+ const warnings = issues.filter((issue$1) => issue$1.severity === "warning");
69
+ const info = issues.filter((issue$1) => issue$1.severity === "info");
70
+ return {
71
+ valid: errors.length === 0,
72
+ errors,
73
+ warnings,
74
+ info
75
+ };
76
+ }
77
+ function mergeResults(...results) {
78
+ const errors = results.flatMap((result) => result.errors);
79
+ const warnings = results.flatMap((result) => result.warnings);
80
+ const info = results.flatMap((result) => result.info);
81
+ return {
82
+ valid: errors.length === 0,
83
+ errors,
84
+ warnings,
85
+ info
86
+ };
87
+ }
88
+ function createDefaultRegistry() {
89
+ return new ValidationRuleRegistry().register(blueprintIntegrationSlotRule).register(blueprintCapabilityRegistryRule).register(tenantCapabilityRule).register(tenantIntegrationBindingRule).register(tenantKnowledgeRule).register(tenantBrandingRule).register(tenantTranslationRule).register(resolvedIntegrationRule).register(resolvedTranslationRule);
90
+ }
91
+ const blueprintIntegrationSlotRule = {
92
+ scope: "blueprint",
93
+ name: "integration.duplicate-slots",
94
+ category: "integration",
95
+ validate(blueprint) {
96
+ const issues = [];
97
+ const seen = /* @__PURE__ */ new Set();
98
+ for (const slot of blueprint.integrationSlots ?? []) {
99
+ if (seen.has(slot.slotId)) issues.push(issue({
100
+ code: "DUPLICATE_SLOT",
101
+ severity: "error",
102
+ path: `integrationSlots.${slot.slotId}`,
103
+ message: `Duplicate integration slot id "${slot.slotId}".`
104
+ }));
105
+ else seen.add(slot.slotId);
106
+ if (slot.allowedModes && slot.allowedModes.length === 0) issues.push(issue({
107
+ code: "EMPTY_ALLOWED_MODES",
108
+ severity: "warning",
109
+ path: `integrationSlots.${slot.slotId}.allowedModes`,
110
+ message: "allowedModes is empty; the slot will accept any supported mode."
111
+ }));
112
+ }
113
+ if (blueprint.branding && !blueprint.branding.appNameKey.trim()) issues.push(issue({
114
+ code: "MISSING_APP_NAME_KEY",
115
+ severity: "warning",
116
+ path: "branding.appNameKey",
117
+ message: "branding.appNameKey should reference a translation catalog key."
118
+ }));
119
+ return issues;
120
+ }
121
+ };
122
+ const blueprintCapabilityRegistryRule = {
123
+ scope: "blueprint",
124
+ name: "capability.registry-check",
125
+ category: "capability",
126
+ validate(blueprint, context) {
127
+ const issues = [];
128
+ const registry = context.capabilities;
129
+ if (!registry) return issues;
130
+ (blueprint.capabilities?.enabled ?? []).forEach((ref, index) => {
131
+ if (!registry.get(ref.key, ref.version)) issues.push(issue({
132
+ code: "MISSING_CAPABILITY",
133
+ severity: "error",
134
+ path: `capabilities.enabled[${index}]`,
135
+ message: `Capability "${ref.key}@${ref.version}" is not registered.`
136
+ }));
137
+ });
138
+ return issues;
139
+ }
140
+ };
141
+ const tenantCapabilityRule = {
142
+ scope: "tenant",
143
+ name: "capability.required-enabled",
144
+ category: "capability",
145
+ validate(blueprint, tenant, context) {
146
+ const issues = [];
147
+ const registry = context.capabilities;
148
+ const requiredKeys = new Set((blueprint.capabilities?.enabled ?? []).map(capabilityRefKey));
149
+ const disabled = tenant.capabilities?.disable ?? [];
150
+ disabled.forEach((ref, index) => {
151
+ if (requiredKeys.has(capabilityRefKey(ref))) issues.push(issue({
152
+ code: "DISABLED_REQUIRED_CAPABILITY",
153
+ severity: "error",
154
+ path: `capabilities.disable[${index}]`,
155
+ message: `Capability "${ref.key}@${ref.version}" is required by the blueprint and cannot be disabled.`
156
+ }));
157
+ });
158
+ if (registry) {
159
+ (tenant.capabilities?.enable ?? []).forEach((ref, index) => {
160
+ if (!registry.get(ref.key, ref.version)) issues.push(issue({
161
+ code: "UNKNOWN_CAPABILITY_ENABLE",
162
+ severity: "error",
163
+ path: `capabilities.enable[${index}]`,
164
+ message: `Capability "${ref.key}@${ref.version}" is not registered.`
165
+ }));
166
+ });
167
+ disabled.forEach((ref, index) => {
168
+ if (!registry.get(ref.key, ref.version)) issues.push(issue({
169
+ code: "UNKNOWN_CAPABILITY_DISABLE",
170
+ severity: "warning",
171
+ path: `capabilities.disable[${index}]`,
172
+ message: `Capability "${ref.key}@${ref.version}" is not registered.`
173
+ }));
174
+ });
175
+ }
176
+ return issues;
177
+ }
178
+ };
179
+ const tenantIntegrationBindingRule = {
180
+ scope: "tenant",
181
+ name: "integration.slot-binding",
182
+ category: "integration",
183
+ validate(blueprint, tenant, context) {
184
+ const issues = [];
185
+ const slots = new Map((blueprint.integrationSlots ?? []).map((slot) => [slot.slotId, slot]));
186
+ const bindings = tenant.integrations ?? [];
187
+ const connections = context.tenantConnections?.reduce((map, connection) => map.set(connection.meta.id, connection), /* @__PURE__ */ new Map()) ?? /* @__PURE__ */ new Map();
188
+ const satisfiedSlots = /* @__PURE__ */ new Set();
189
+ bindings.forEach((binding) => {
190
+ const path = `integrations.${binding.slotId}`;
191
+ const slot = slots.get(binding.slotId);
192
+ if (!slot) {
193
+ issues.push(issue({
194
+ code: "UNKNOWN_SLOT_BINDING",
195
+ severity: "error",
196
+ path,
197
+ message: `Integration slot "${binding.slotId}" is not defined in the blueprint.`
198
+ }));
199
+ return;
200
+ }
201
+ let slotValid = true;
202
+ const connection = connections.get(binding.connectionId);
203
+ if (!connection) {
204
+ issues.push(issue({
205
+ code: "MISSING_INTEGRATION_CONNECTION",
206
+ severity: "error",
207
+ path,
208
+ message: `Integration connection "${binding.connectionId}" was not found for tenant "${tenant.meta.tenantId}".`
209
+ }));
210
+ slotValid = false;
211
+ return;
212
+ }
213
+ if (connection.meta.tenantId !== tenant.meta.tenantId) {
214
+ issues.push(issue({
215
+ code: "FOREIGN_CONNECTION",
216
+ severity: "error",
217
+ path,
218
+ message: `Connection "${binding.connectionId}" belongs to tenant "${connection.meta.tenantId}", not "${tenant.meta.tenantId}".`
219
+ }));
220
+ slotValid = false;
221
+ }
222
+ if (slot.allowedModes && slot.allowedModes.length > 0) {
223
+ if (!slot.allowedModes.includes(connection.ownershipMode)) {
224
+ issues.push(issue({
225
+ code: "MODE_MISMATCH",
226
+ severity: "error",
227
+ path,
228
+ message: `Slot "${slot.slotId}" only allows modes [${slot.allowedModes.join(", ")}] but connection is "${connection.ownershipMode}".`
229
+ }));
230
+ slotValid = false;
231
+ }
232
+ }
233
+ if (connection.status === "disconnected" || connection.status === "error") {
234
+ issues.push(issue({
235
+ code: "CONNECTION_NOT_READY",
236
+ severity: "error",
237
+ path,
238
+ message: `Connection "${connection.meta.label}" is in status "${connection.status}".`
239
+ }));
240
+ slotValid = false;
241
+ } else if (connection.status === "unknown") issues.push(issue({
242
+ code: "CONNECTION_STATUS_UNKNOWN",
243
+ severity: "warning",
244
+ path,
245
+ message: `Connection "${connection.meta.label}" has unknown health status.`
246
+ }));
247
+ const spec = lookupIntegrationSpec(context.integrationSpecs, connection);
248
+ if (!spec) {
249
+ issues.push(issue({
250
+ code: "INTEGRATION_SPEC_NOT_FOUND",
251
+ severity: "warning",
252
+ path,
253
+ message: `Integration spec "${connection.meta.integrationKey}@${connection.meta.integrationVersion}" is not registered.`
254
+ }));
255
+ slotValid = false;
256
+ return;
257
+ }
258
+ if (spec.meta.category !== slot.requiredCategory) {
259
+ issues.push(issue({
260
+ code: "CATEGORY_MISMATCH",
261
+ severity: "error",
262
+ path,
263
+ message: `Slot "${slot.slotId}" requires category "${slot.requiredCategory}" but connection provides "${spec.meta.category}".`
264
+ }));
265
+ slotValid = false;
266
+ }
267
+ if (!spec.supportedModes.includes(connection.ownershipMode)) {
268
+ issues.push(issue({
269
+ code: "UNSUPPORTED_OWNERSHIP_MODE",
270
+ severity: "error",
271
+ path,
272
+ message: `Integration spec "${spec.meta.key}" does not support ownership mode "${connection.ownershipMode}".`
273
+ }));
274
+ slotValid = false;
275
+ }
276
+ for (const required of slot.requiredCapabilities ?? []) if (!integrationProvidesCapability(spec, required)) {
277
+ issues.push(issue({
278
+ code: "CAPABILITY_NOT_PROVIDED",
279
+ severity: "error",
280
+ path,
281
+ message: `Integration "${spec.meta.key}" does not provide required capability "${required.key}@${required.version}".`
282
+ }));
283
+ slotValid = false;
284
+ }
285
+ if (slotValid) satisfiedSlots.add(slot.slotId);
286
+ });
287
+ for (const slot of slots.values()) if (slot.required && !satisfiedSlots.has(slot.slotId)) issues.push(issue({
288
+ code: "MISSING_REQUIRED_SLOT",
289
+ severity: "error",
290
+ path: `integrations.${slot.slotId}`,
291
+ message: `Required integration slot "${slot.slotId}" is not bound.`
292
+ }));
293
+ return issues;
294
+ }
295
+ };
296
+ const tenantKnowledgeRule = {
297
+ scope: "tenant",
298
+ name: "knowledge.bindings",
299
+ category: "knowledge",
300
+ validate(_blueprint, tenant, context) {
301
+ const issues = [];
302
+ const registry = context.knowledgeSpaces;
303
+ if (!registry) return issues;
304
+ const sources = context.knowledgeSources ?? [];
305
+ (tenant.knowledge ?? []).forEach((binding, index) => {
306
+ const path = `knowledge[${index}]`;
307
+ const space = registry.get(binding.spaceKey, binding.spaceVersion);
308
+ if (!space) {
309
+ issues.push(issue({
310
+ code: "UNKNOWN_KNOWLEDGE_SPACE",
311
+ severity: "error",
312
+ path: `${path}.spaceKey`,
313
+ message: `Knowledge space "${binding.spaceKey}" is not registered.`
314
+ }));
315
+ return;
316
+ }
317
+ if (!sources.some((source) => {
318
+ if (source.meta.spaceKey !== binding.spaceKey) return false;
319
+ if (binding.spaceVersion != null) return source.meta.spaceVersion === binding.spaceVersion;
320
+ return true;
321
+ })) issues.push(issue({
322
+ code: "MISSING_KNOWLEDGE_SOURCES",
323
+ severity: "error",
324
+ path,
325
+ message: `Knowledge space "${binding.spaceKey}" has no configured sources for tenant "${tenant.meta.tenantId}".`
326
+ }));
327
+ if (space.meta.category === "external" || space.meta.category === "ephemeral") issues.push(issue({
328
+ code: "LOW_TRUST_KNOWLEDGE",
329
+ severity: "warning",
330
+ path,
331
+ message: `Knowledge space "${binding.spaceKey}" has category "${space.meta.category}". Avoid using it for irreversible or policy decisions.`
332
+ }));
333
+ });
334
+ return issues;
335
+ }
336
+ };
337
+ const tenantBrandingRule = {
338
+ scope: "tenant",
339
+ name: "branding.constraints",
340
+ category: "branding",
341
+ validate(_blueprint, tenant, context) {
342
+ const issues = [];
343
+ const branding = tenant.branding;
344
+ if (!branding) return issues;
345
+ const domain = branding.customDomain?.trim();
346
+ if (domain) {
347
+ if (!DOMAIN_REGEX.test(domain)) issues.push(issue({
348
+ code: "INVALID_DOMAIN",
349
+ severity: "error",
350
+ path: "branding.customDomain",
351
+ message: `Custom domain "${domain}" is not a valid hostname.`
352
+ }));
353
+ const conflict = (context.existingConfigs ?? []).find((config) => {
354
+ if (config.meta.id === tenant.meta.id) return false;
355
+ const otherDomain = config.branding?.customDomain?.trim();
356
+ if (!otherDomain) return false;
357
+ return otherDomain.toLowerCase() === domain.toLowerCase();
358
+ });
359
+ if (conflict) issues.push(issue({
360
+ code: "DUPLICATE_DOMAIN",
361
+ severity: "error",
362
+ path: "branding.customDomain",
363
+ message: `Custom domain "${domain}" is already used by tenant "${conflict.meta.tenantId}".`
364
+ }));
365
+ }
366
+ (branding.assets ?? []).forEach((asset, index) => {
367
+ const assetPath = `branding.assets[${index}]`;
368
+ if (!HTTPS_URL_REGEX.test(asset.url)) issues.push(issue({
369
+ code: "INSECURE_ASSET_URL",
370
+ severity: "error",
371
+ path: `${assetPath}.url`,
372
+ message: `Branding asset "${asset.type}" must use an HTTPS URL.`
373
+ }));
374
+ const allowed = ALLOWED_ASSET_EXTENSIONS[asset.type] ?? ALLOWED_ASSET_EXTENSIONS.logo;
375
+ const extension = extractExtension(asset.url);
376
+ if (extension && allowed && !allowed.includes(extension)) issues.push(issue({
377
+ code: "UNEXPECTED_ASSET_TYPE",
378
+ severity: "warning",
379
+ path: `${assetPath}.url`,
380
+ message: `Asset "${asset.type}" should use one of: ${(allowed ?? []).join(", ")}. Detected "${extension}".`
381
+ }));
382
+ });
383
+ return issues;
384
+ }
385
+ };
386
+ const tenantTranslationRule = {
387
+ scope: "tenant",
388
+ name: "translation.consistency",
389
+ category: "translation",
390
+ validate(blueprint, tenant, context) {
391
+ const issues = [];
392
+ const pointer = blueprint.translationCatalog;
393
+ const catalogs = normalizeCatalogs(context.translationCatalogs);
394
+ const blueprintCatalog = pointer ? catalogs.blueprint.find((catalog) => catalog.meta.name === pointer.name && catalog.meta.version === pointer.version) : void 0;
395
+ if (pointer && !blueprintCatalog) issues.push(issue({
396
+ code: "MISSING_BLUEPRINT_CATALOG",
397
+ severity: "error",
398
+ path: "translationCatalog",
399
+ message: `Blueprint translation catalog "${pointer.name}@${pointer.version}" is not loaded in context.`
400
+ }));
401
+ const requiredKeys = /* @__PURE__ */ new Set();
402
+ if (blueprint.branding?.appNameKey) {
403
+ requiredKeys.add(blueprint.branding.appNameKey);
404
+ if (blueprintCatalog && !hasTranslationEntry(blueprintCatalog.entries, blueprint.branding.appNameKey, blueprintCatalog.defaultLocale)) issues.push(issue({
405
+ code: "MISSING_TRANSLATION_KEY",
406
+ severity: "error",
407
+ path: "branding.appNameKey",
408
+ message: `Translation key "${blueprint.branding.appNameKey}" is missing for locale "${blueprintCatalog.defaultLocale}".`
409
+ }));
410
+ }
411
+ const tenantLocales = tenant.locales;
412
+ if (tenantLocales) {
413
+ const { defaultLocale, enabledLocales } = tenantLocales;
414
+ if (!enabledLocales.includes(defaultLocale)) issues.push(issue({
415
+ code: "LOCALE_NOT_ENABLED",
416
+ severity: "warning",
417
+ path: "locales.enabledLocales",
418
+ message: `enabledLocales does not include defaultLocale "${defaultLocale}".`
419
+ }));
420
+ if (blueprintCatalog) {
421
+ const supported = new Set(blueprintCatalog.supportedLocales);
422
+ for (const locale of [defaultLocale, ...enabledLocales]) if (!supported.has(locale)) issues.push(issue({
423
+ code: "UNSUPPORTED_LOCALE",
424
+ severity: "error",
425
+ path: "locales.enabledLocales",
426
+ message: `Locale "${locale}" is not supported by blueprint catalog "${blueprintCatalog.meta.name}".`
427
+ }));
428
+ }
429
+ }
430
+ const allowedKeys = /* @__PURE__ */ new Set();
431
+ for (const catalog of catalogs.blueprint) for (const entry of catalog.entries) allowedKeys.add(entry.key);
432
+ for (const catalog of catalogs.platform) for (const entry of catalog.entries) allowedKeys.add(entry.key);
433
+ (tenant.translationOverrides?.entries ?? []).forEach((entry, index) => {
434
+ const path = `translationOverrides.entries[${index}]`;
435
+ if (!allowedKeys.has(entry.key)) issues.push(issue({
436
+ code: "UNKNOWN_TRANSLATION_KEY",
437
+ severity: "error",
438
+ path,
439
+ message: `Override references unknown key "${entry.key}".`
440
+ }));
441
+ if (blueprintCatalog) {
442
+ if (!new Set([
443
+ ...blueprintCatalog.supportedLocales,
444
+ ...tenant.locales?.enabledLocales ?? [],
445
+ tenant.locales?.defaultLocale ?? blueprintCatalog.defaultLocale
446
+ ]).has(entry.locale)) issues.push(issue({
447
+ code: "UNSUPPORTED_LOCALE_OVERRIDE",
448
+ severity: "error",
449
+ path,
450
+ message: `Locale "${entry.locale}" is not enabled for tenant overrides.`
451
+ }));
452
+ }
453
+ });
454
+ return issues;
455
+ }
456
+ };
457
+ const resolvedIntegrationRule = {
458
+ scope: "resolved",
459
+ name: "integration.required-slots",
460
+ category: "integration",
461
+ validate(blueprint, resolved) {
462
+ const issues = [];
463
+ const requiredSlots = (blueprint.integrationSlots ?? []).filter((slot) => slot.required);
464
+ for (const slot of requiredSlots) if (!resolved.integrations.some((integration) => integration.slot.slotId === slot.slotId)) issues.push(issue({
465
+ code: "MISSING_REQUIRED_SLOT",
466
+ severity: "error",
467
+ path: `integrations.${slot.slotId}`,
468
+ message: `Resolved config is missing integration slot "${slot.slotId}".`
469
+ }));
470
+ for (const integration of resolved.integrations) if (integration.connection.status === "disconnected" || integration.connection.status === "error") issues.push(issue({
471
+ code: "CONNECTION_NOT_READY",
472
+ severity: "error",
473
+ path: `integrations.${integration.slot.slotId}`,
474
+ message: `Resolved integration "${integration.slot.slotId}" uses a connection in status "${integration.connection.status}".`
475
+ }));
476
+ return issues;
477
+ }
478
+ };
479
+ const resolvedTranslationRule = {
480
+ scope: "resolved",
481
+ name: "translation.default-locale",
482
+ category: "translation",
483
+ validate(_blueprint, resolved) {
484
+ const issues = [];
485
+ const translation = resolved.translation;
486
+ if (translation && !translation.supportedLocales.includes(translation.defaultLocale)) issues.push(issue({
487
+ code: "DEFAULT_LOCALE_NOT_SUPPORTED",
488
+ severity: "warning",
489
+ path: "translation.defaultLocale",
490
+ message: "supportedLocales should include defaultLocale for consistent fallback behaviour."
491
+ }));
492
+ return issues;
493
+ }
494
+ };
495
+ function issue(input) {
496
+ return input;
497
+ }
498
+ function dedupeIssues(issues) {
499
+ const map = /* @__PURE__ */ new Map();
500
+ for (const current of issues) {
501
+ const key = `${current.code}|${current.path}|${current.severity}`;
502
+ if (!map.has(key)) map.set(key, current);
503
+ }
504
+ return [...map.values()];
505
+ }
506
+ function capabilityRefKey(ref) {
507
+ return `${ref.key}@${ref.version}`;
508
+ }
509
+ function integrationProvidesCapability(spec, required) {
510
+ return spec.capabilities.provides.some((provided) => provided.key === required.key && provided.version === required.version);
511
+ }
512
+ function lookupIntegrationSpec(registry, connection) {
513
+ if (!registry) return void 0;
514
+ return registry.get(connection.meta.integrationKey, connection.meta.integrationVersion);
515
+ }
516
+ function extractExtension(url) {
517
+ const [raw] = url.split("?");
518
+ if (!raw) return void 0;
519
+ const lastDot = raw.lastIndexOf(".");
520
+ if (lastDot === -1) return void 0;
521
+ return raw.slice(lastDot + 1).toLowerCase();
522
+ }
523
+ function normalizeCatalogs(catalogs) {
524
+ if (!catalogs) return {
525
+ platform: [],
526
+ blueprint: []
527
+ };
528
+ return {
529
+ platform: catalogs.platform ? Array.isArray(catalogs.platform) ? catalogs.platform : [catalogs.platform] : [],
530
+ blueprint: catalogs.blueprint ? Array.isArray(catalogs.blueprint) ? catalogs.blueprint : [catalogs.blueprint] : []
531
+ };
532
+ }
533
+ function hasTranslationEntry(entries, key, locale) {
534
+ return entries.some((entry) => entry.key === key && entry.locale === locale);
535
+ }
536
+
537
+ //#endregion
538
+ export { validateBlueprint, validateConfig, validateResolvedConfig, validateTenantConfig };
@@ -1 +1,22 @@
1
- import{registerDocBlocks as e}from"../../docs/registry.js";import"../../registry.js";const t=[{id:`docs.tech.contracts.capabilities`,title:`CapabilitySpec Overview`,summary:"Capability specs provide a canonical, versioned contract for what a module offers (`provides`) and what it depends on (`requires`). They enable safe composition across features, automated validation during `installFeature`, and consistent documentation for shared surfaces (APIs, events, workflows, UI, resources).",kind:`reference`,visibility:`public`,route:`/docs/tech/contracts/capabilities`,tags:[`tech`,`contracts`,`capabilities`],body:"# CapabilitySpec Overview\n\n## Purpose\n\nCapability specs provide a canonical, versioned contract for what a module offers (`provides`) and what it depends on (`requires`). They enable safe composition across features, automated validation during `installFeature`, and consistent documentation for shared surfaces (APIs, events, workflows, UI, resources).\n\n## Schema\n\nDefined in `@lssm/lib.contracts/src/capabilities.ts`.\n\n```ts\nexport interface CapabilitySpec {\n meta: CapabilityMeta; // ownership metadata + { key, version, kind }\n provides?: CapabilitySurfaceRef[]; // what concrete surfaces this capability exposes\n requires?: CapabilityRequirement[];// capabilities that must already exist\n}\n```\n\n- **CapabilityMeta**\n - `key`: stable slug (e.g., `payments.stripe`)\n - `version`: bump on breaking changes\n - `kind`: `'api' | 'event' | 'data' | 'ui' | 'integration'`\n - ownership fields (`title`, `description`, `domain`, `owners`, `tags`, `stability`)\n- **CapabilitySurfaceRef**\n - `surface`: `'operation' | 'event' | 'workflow' | 'presentation' | 'resource'`\n - `name` / `version`: points to the declared contract (operation name, event name, etc.)\n - optional `description`\n- **CapabilityRequirement**\n - `key`: capability slug to satisfy\n - `version?`: pin to an exact version when required (defaults to highest registered)\n - `kind?`: extra guard if the same key hosts multiple kinds\n - `optional?`: skip strict enforcement (informational requirement)\n - `reason?`: why this dependency exists (docs + tooling)\n\n## Registry\n\n`CapabilityRegistry` provides:\n\n- `register(spec)`: register a capability (`key + version` must be unique)\n- `get(key, version?)`: retrieve the exact or highest version\n- `list()`: inspect all capabilities\n- `satisfies(requirement, additional?)`: check if a requirement is met (includes locally provided capabilities passed via `additional`)\n\n## Feature Integration\n\n`FeatureModuleSpec` now accepts:\n\n```ts\ncapabilities?: {\n provides?: CapabilityRef[]; // capabilities this feature exposes\n requires?: CapabilityRequirement[]; // capabilities the feature needs\n};\n```\n\nDuring `installFeature`:\n\n1. `provides` entries must exist in the `CapabilityRegistry`.\n2. `requires` entries must be satisfied either by:\n - the same feature’s `provides`,\n - or existing capabilities already registered in the global registry.\n\nErrors are thrown when dependencies cannot be satisfied, preventing unsafe module composition.\n\n## Authoring Guidelines\n\n1. **Register capability specs** in a shared package (e.g., `packages/.../capabilities`) before referencing them in features.\n2. **Version consciously**: bump capability versions when the provided surfaces or contract semantics change.\n3. **Document dependencies** via `reason` strings to help operators understand why a capability is required.\n4. **Prefer stable keys** that map to business/technical domains (`billing.invoices`, `payments.stripe`, `cms.assets`).\n5. When introducing new capability kinds, update the `CapabilityKind` union and accompanying docs/tests.\n\n## Tooling (Roadmap)\n\n- CLI validation warns when feature specs reference missing capabilities.\n- Future build steps will leverage capability data to scaffold adapters and enforce policy in generated code.\n- Capability metadata will surface in docs/LLM guides to describe module marketplaces and installation flows.\n\n"}];e(t);export{t as tech_contracts_capabilities_DocBlocks};
1
+ import { registerDocBlocks } from "../../docs/registry.js";
2
+ import "../../registry.js";
3
+
4
+ //#region src/capabilities/docs/capabilities.docblock.ts
5
+ const tech_contracts_capabilities_DocBlocks = [{
6
+ id: "docs.tech.contracts.capabilities",
7
+ title: "CapabilitySpec Overview",
8
+ summary: "Capability specs provide a canonical, versioned contract for what a module offers (`provides`) and what it depends on (`requires`). They enable safe composition across features, automated validation during `installFeature`, and consistent documentation for shared surfaces (APIs, events, workflows, UI, resources).",
9
+ kind: "reference",
10
+ visibility: "public",
11
+ route: "/docs/tech/contracts/capabilities",
12
+ tags: [
13
+ "tech",
14
+ "contracts",
15
+ "capabilities"
16
+ ],
17
+ body: "# CapabilitySpec Overview\n\n## Purpose\n\nCapability specs provide a canonical, versioned contract for what a module offers (`provides`) and what it depends on (`requires`). They enable safe composition across features, automated validation during `installFeature`, and consistent documentation for shared surfaces (APIs, events, workflows, UI, resources).\n\n## Schema\n\nDefined in `@lssm/lib.contracts/src/capabilities.ts`.\n\n```ts\nexport interface CapabilitySpec {\n meta: CapabilityMeta; // ownership metadata + { key, version, kind }\n provides?: CapabilitySurfaceRef[]; // what concrete surfaces this capability exposes\n requires?: CapabilityRequirement[];// capabilities that must already exist\n}\n```\n\n- **CapabilityMeta**\n - `key`: stable slug (e.g., `payments.stripe`)\n - `version`: bump on breaking changes\n - `kind`: `'api' | 'event' | 'data' | 'ui' | 'integration'`\n - ownership fields (`title`, `description`, `domain`, `owners`, `tags`, `stability`)\n- **CapabilitySurfaceRef**\n - `surface`: `'operation' | 'event' | 'workflow' | 'presentation' | 'resource'`\n - `name` / `version`: points to the declared contract (operation name, event name, etc.)\n - optional `description`\n- **CapabilityRequirement**\n - `key`: capability slug to satisfy\n - `version?`: pin to an exact version when required (defaults to highest registered)\n - `kind?`: extra guard if the same key hosts multiple kinds\n - `optional?`: skip strict enforcement (informational requirement)\n - `reason?`: why this dependency exists (docs + tooling)\n\n## Registry\n\n`CapabilityRegistry` provides:\n\n- `register(spec)`: register a capability (`key + version` must be unique)\n- `get(key, version?)`: retrieve the exact or highest version\n- `list()`: inspect all capabilities\n- `satisfies(requirement, additional?)`: check if a requirement is met (includes locally provided capabilities passed via `additional`)\n\n## Feature Integration\n\n`FeatureModuleSpec` now accepts:\n\n```ts\ncapabilities?: {\n provides?: CapabilityRef[]; // capabilities this feature exposes\n requires?: CapabilityRequirement[]; // capabilities the feature needs\n};\n```\n\nDuring `installFeature`:\n\n1. `provides` entries must exist in the `CapabilityRegistry`.\n2. `requires` entries must be satisfied either by:\n - the same feature’s `provides`,\n - or existing capabilities already registered in the global registry.\n\nErrors are thrown when dependencies cannot be satisfied, preventing unsafe module composition.\n\n## Authoring Guidelines\n\n1. **Register capability specs** in a shared package (e.g., `packages/.../capabilities`) before referencing them in features.\n2. **Version consciously**: bump capability versions when the provided surfaces or contract semantics change.\n3. **Document dependencies** via `reason` strings to help operators understand why a capability is required.\n4. **Prefer stable keys** that map to business/technical domains (`billing.invoices`, `payments.stripe`, `cms.assets`).\n5. When introducing new capability kinds, update the `CapabilityKind` union and accompanying docs/tests.\n\n## Tooling (Roadmap)\n\n- CLI validation warns when feature specs reference missing capabilities.\n- Future build steps will leverage capability data to scaffold adapters and enforce policy in generated code.\n- Capability metadata will surface in docs/LLM guides to describe module marketplaces and installation flows.\n\n"
18
+ }];
19
+ registerDocBlocks(tech_contracts_capabilities_DocBlocks);
20
+
21
+ //#endregion
22
+ export { tech_contracts_capabilities_DocBlocks };