@warpgogol/werkstatt-shared 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (403) hide show
  1. package/package.json +352 -0
  2. package/src/checks/index.ts +24 -0
  3. package/src/checks/lib/astro-site-url.ts +30 -0
  4. package/src/checks/lib/i18n.ts +30 -0
  5. package/src/checks/result-helpers.ts +102 -0
  6. package/src/checks/suppressions-config.ts +195 -0
  7. package/src/index.ts +14 -0
  8. package/src/integration/README.md +38 -0
  9. package/src/integration/adapters.ts +143 -0
  10. package/src/integration/crm-buffer.ts +491 -0
  11. package/src/integration/dispatch.ts +106 -0
  12. package/src/integration/funnel.ts +379 -0
  13. package/src/integration/index.ts +56 -0
  14. package/src/integration/lifecycle.ts +155 -0
  15. package/src/integration/orchestration.ts +439 -0
  16. package/src/integration/port-barrel.ts +124 -0
  17. package/src/integration/port.ts +143 -0
  18. package/src/integration/qstash.ts +134 -0
  19. package/src/integration/sharding.ts +133 -0
  20. package/src/integration/tests/deliver-event.test.ts +52 -0
  21. package/src/integration/tests/dispatch.test.ts +88 -0
  22. package/src/integration/tests/funnel-bridge.test.ts +46 -0
  23. package/src/integration/tests/hub-runtime.test.ts +128 -0
  24. package/src/integration/tests/lifecycle.test.ts +69 -0
  25. package/src/integration/tests/qstash.test.ts +83 -0
  26. package/src/integration/tests/sharding.test.ts +64 -0
  27. package/src/integration/vitest.config.ts +8 -0
  28. package/src/integration-adapter-supabase-crm/adapter.ts +437 -0
  29. package/src/integration-adapter-supabase-crm/client.ts +605 -0
  30. package/src/integration-adapter-supabase-crm/index.ts +28 -0
  31. package/src/integration-adapter-supabase-crm/pipedrive-sync-target.ts +482 -0
  32. package/src/integration-adapter-supabase-crm/supabase-rest.ts +89 -0
  33. package/src/integration-adapter-supabase-crm/tenant-registry.ts +334 -0
  34. package/src/integration-adapter-supabase-crm/tests/change-balance.pbt.test.ts +83 -0
  35. package/src/integration-adapter-supabase-crm/tests/funnel-persistence.test.ts +409 -0
  36. package/src/integration-adapter-supabase-crm/tests/lifecycle-sync.test.ts +288 -0
  37. package/src/integration-adapter-supabase-crm/tests/stage-map.test.ts +80 -0
  38. package/src/integration-adapter-supabase-crm/vitest.config.ts +8 -0
  39. package/src/integration-adapter-supabase-crm/worker.ts +223 -0
  40. package/src/observability/conventions.ts +93 -0
  41. package/src/observability/env-resolver.ts +57 -0
  42. package/src/observability/index.ts +57 -0
  43. package/src/observability/metric-registry.ts +184 -0
  44. package/src/observability/otlp-converter.ts +125 -0
  45. package/src/observability/otlp-json.ts +135 -0
  46. package/src/observability/otlp-transport.ts +68 -0
  47. package/src/observability/pusher.ts +142 -0
  48. package/src/observability/redact.ts +27 -0
  49. package/src/observability/tests/conventions.test.ts +100 -0
  50. package/src/observability/tests/metric-registry.test.ts +117 -0
  51. package/src/observability/tests/otlp-converter.pbt.test.ts +77 -0
  52. package/src/observability/tests/otlp-converter.test.ts +157 -0
  53. package/src/observability/tests/otlp-json.test.ts +120 -0
  54. package/src/observability/tests/pusher.test.ts +75 -0
  55. package/src/observability/tests/redact.pbt.test.ts +71 -0
  56. package/src/observability/tests/redact.test.ts +37 -0
  57. package/src/observability/typed-refs.ts +137 -0
  58. package/src/observability/vitest.config.ts +8 -0
  59. package/src/ontology/analytics/matomo-binding.yaml +172 -0
  60. package/src/ontology/analytics/matomo-fleet.registry.yaml +26 -0
  61. package/src/ontology/analytics/messkanon.yaml +151 -0
  62. package/src/ontology/archetype-registry.ts +61 -0
  63. package/src/ontology/archetypes/components/brand-label.yaml +19 -0
  64. package/src/ontology/archetypes/components/copyright.yaml +19 -0
  65. package/src/ontology/archetypes/components/currency-aware-price-display.yaml +19 -0
  66. package/src/ontology/archetypes/components/currency-selector.yaml +19 -0
  67. package/src/ontology/archetypes/components/footer-promo.yaml +19 -0
  68. package/src/ontology/archetypes/components/lang-switcher.yaml +19 -0
  69. package/src/ontology/archetypes/components/layout.yaml +19 -0
  70. package/src/ontology/archetypes/components/live-photo.yaml +20 -0
  71. package/src/ontology/archetypes/components/material-credit.yaml +20 -0
  72. package/src/ontology/archetypes/components/media.yaml +21 -0
  73. package/src/ontology/archetypes/components/nachweis-card.yaml +34 -0
  74. package/src/ontology/archetypes/components/nachweis-detail.yaml +43 -0
  75. package/src/ontology/archetypes/components/nachweis-list.yaml +37 -0
  76. package/src/ontology/archetypes/components/nachweis-verify.yaml +31 -0
  77. package/src/ontology/archetypes/components/not-found.yaml +22 -0
  78. package/src/ontology/archetypes/components/offer-capacity.yaml +21 -0
  79. package/src/ontology/archetypes/components/passport-header.yaml +19 -0
  80. package/src/ontology/archetypes/components/passport-provenance.yaml +20 -0
  81. package/src/ontology/archetypes/components/passport-score-grid.yaml +20 -0
  82. package/src/ontology/archetypes/components/passport-star-map.yaml +20 -0
  83. package/src/ontology/archetypes/components/person-profile.yaml +20 -0
  84. package/src/ontology/archetypes/components/pulsar.yaml +20 -0
  85. package/src/ontology/archetypes/components/responsive-image.yaml +19 -0
  86. package/src/ontology/archetypes/components/scroll-to-top.yaml +22 -0
  87. package/src/ontology/archetypes/components/social-meta.yaml +20 -0
  88. package/src/ontology/archetypes/components/structured-data.yaml +19 -0
  89. package/src/ontology/archetypes/index.json +1003 -0
  90. package/src/ontology/archetypes/index.yaml +821 -0
  91. package/src/ontology/archetypes/sections/approach.yaml +23 -0
  92. package/src/ontology/archetypes/sections/article-list.yaml +30 -0
  93. package/src/ontology/archetypes/sections/audience-cards.yaml +30 -0
  94. package/src/ontology/archetypes/sections/breadcrumbs.yaml +21 -0
  95. package/src/ontology/archetypes/sections/chat-widget.yaml +37 -0
  96. package/src/ontology/archetypes/sections/comparison-cards.yaml +31 -0
  97. package/src/ontology/archetypes/sections/controlled-responsibility-block.yaml +34 -0
  98. package/src/ontology/archetypes/sections/credits.yaml +29 -0
  99. package/src/ontology/archetypes/sections/donation-card.yaml +53 -0
  100. package/src/ontology/archetypes/sections/donation-use.yaml +23 -0
  101. package/src/ontology/archetypes/sections/dynamic-status-block.yaml +35 -0
  102. package/src/ontology/archetypes/sections/faq-list.yaml +26 -0
  103. package/src/ontology/archetypes/sections/final-cta.yaml +53 -0
  104. package/src/ontology/archetypes/sections/hero-decision-card.yaml +65 -0
  105. package/src/ontology/archetypes/sections/hero.yaml +50 -0
  106. package/src/ontology/archetypes/sections/impact.yaml +23 -0
  107. package/src/ontology/archetypes/sections/markdown.yaml +43 -0
  108. package/src/ontology/archetypes/sections/mountain-journey.yaml +43 -0
  109. package/src/ontology/archetypes/sections/navigation.yaml +20 -0
  110. package/src/ontology/archetypes/sections/notausgang-block.yaml +41 -0
  111. package/src/ontology/archetypes/sections/open-source-registry.yaml +26 -0
  112. package/src/ontology/archetypes/sections/ownership-block.yaml +57 -0
  113. package/src/ontology/archetypes/sections/people.yaml +48 -0
  114. package/src/ontology/archetypes/sections/price-card.yaml +50 -0
  115. package/src/ontology/archetypes/sections/problem.yaml +23 -0
  116. package/src/ontology/archetypes/sections/send-message.yaml +63 -0
  117. package/src/ontology/archetypes/sections/service-metadata-block.yaml +40 -0
  118. package/src/ontology/archetypes/sections/social-proof.yaml +27 -0
  119. package/src/ontology/archetypes/sections/transparency.yaml +32 -0
  120. package/src/ontology/archetypes/sections/trust-strip.yaml +42 -0
  121. package/src/ontology/archetypes/sections/video-section.yaml +27 -0
  122. package/src/ontology/archetypes/sections/women.yaml +29 -0
  123. package/src/ontology/biome-token-projection.ts +229 -0
  124. package/src/ontology/biomes/check-concrete-blueprint.yaml +118 -0
  125. package/src/ontology/biomes/handwerk-material-warm.yaml +135 -0
  126. package/src/ontology/biomes/nonprofit-trust.yaml +123 -0
  127. package/src/ontology/blueprints/offer.yaml +39 -0
  128. package/src/ontology/blueprints/prompts/local-market-signal.md +13 -0
  129. package/src/ontology/blueprints/prompts/website-local-narrative.md +24 -0
  130. package/src/ontology/blueprints/ratgeber.yaml +62 -0
  131. package/src/ontology/blueprints/website-local.yaml +309 -0
  132. package/src/ontology/blueprints/website-service.yaml +84 -0
  133. package/src/ontology/capabilities/lead.submit.yaml +44 -0
  134. package/src/ontology/cosmic/index.ts +48 -0
  135. package/src/ontology/cosmic/moon-catalog.ts +173 -0
  136. package/src/ontology/cosmic/planet-catalog.ts +82 -0
  137. package/src/ontology/cosmic/star-catalog.ts +337 -0
  138. package/src/ontology/enums.ts +159 -0
  139. package/src/ontology/external-surfaces/index.ts +158 -0
  140. package/src/ontology/external-surfaces/jsonld-types.yaml +135 -0
  141. package/src/ontology/external-surfaces/sitemap-shape.yaml +6 -0
  142. package/src/ontology/external-surfaces/url-schema.yaml +95 -0
  143. package/src/ontology/growth/events/contact-email-click.yaml +21 -0
  144. package/src/ontology/growth/events/contact-form-submit.yaml +25 -0
  145. package/src/ontology/growth/events/contact-phone-click.yaml +21 -0
  146. package/src/ontology/growth/events/contact-route-click.yaml +21 -0
  147. package/src/ontology/growth/events/contact-submit.yaml +28 -0
  148. package/src/ontology/growth/events/contact-whatsapp-click.yaml +21 -0
  149. package/src/ontology/growth/events/cta-click.yaml +30 -0
  150. package/src/ontology/growth/events/donation-confirmed.yaml +32 -0
  151. package/src/ontology/growth/events/donation-form-start.yaml +21 -0
  152. package/src/ontology/growth/events/donation-form-submit.yaml +29 -0
  153. package/src/ontology/growth/events/donation-intent.yaml +29 -0
  154. package/src/ontology/growth/events/form-error.yaml +27 -0
  155. package/src/ontology/growth/events/form-start.yaml +21 -0
  156. package/src/ontology/growth/events/form-submit.yaml +21 -0
  157. package/src/ontology/growth/events/outbound-click.yaml +25 -0
  158. package/src/ontology/growth/events/page-view.yaml +24 -0
  159. package/src/ontology/growth/events/passport-view.yaml +28 -0
  160. package/src/ontology/growth/events/star-map-navigate.yaml +30 -0
  161. package/src/ontology/index.ts +165 -0
  162. package/src/ontology/manifest.ts +344 -0
  163. package/src/ontology/operations/index.ts +1 -0
  164. package/src/ontology/schemas/biome.ts +303 -0
  165. package/src/ontology/schemas/capability.ts +104 -0
  166. package/src/ontology/schemas/constellation.ts +114 -0
  167. package/src/ontology/schemas/dns-records.ts +53 -0
  168. package/src/ontology/schemas/index.ts +95 -0
  169. package/src/ontology/schemas/manifest-resolver.ts +123 -0
  170. package/src/ontology/schemas/page-entry.ts +213 -0
  171. package/src/ontology/schemas/section-archetype.ts +109 -0
  172. package/src/ontology/schemas/site-family.ts +98 -0
  173. package/src/ontology/schemas/system/growth.ts +80 -0
  174. package/src/ontology/schemas/system/integrations.ts +143 -0
  175. package/src/ontology/schemas/system/manifest.ts +492 -0
  176. package/src/ontology/schemas/system/page-output.ts +99 -0
  177. package/src/ontology/schemas/system/release.ts +58 -0
  178. package/src/ontology/schemas/system/text.ts +58 -0
  179. package/src/ontology/schemas/system.ts +53 -0
  180. package/src/ontology/shared-section-props/body-fragments.ts +182 -0
  181. package/src/ontology/shared-section-props/catalog.ts +195 -0
  182. package/src/ontology/shared-section-props/common.ts +178 -0
  183. package/src/ontology/shared-section-props/index.ts +34 -0
  184. package/src/ontology/shared-section-props/visual-header.ts +163 -0
  185. package/src/ontology/site-families/charity-donation-trust/cultural-rules.yaml +6 -0
  186. package/src/ontology/site-families/charity-donation-trust/family.yaml +44 -0
  187. package/src/ontology/site-families/charity-donation-trust/linguistic-rules.yaml +6 -0
  188. package/src/ontology/site-families/check-quality-operator/cultural-rules.yaml +6 -0
  189. package/src/ontology/site-families/check-quality-operator/family.yaml +40 -0
  190. package/src/ontology/site-families/check-quality-operator/linguistic-rules.yaml +6 -0
  191. package/src/ontology/site-families/handwerk-trust-engineering/cultural-rules.yaml +6 -0
  192. package/src/ontology/site-families/handwerk-trust-engineering/family.yaml +53 -0
  193. package/src/ontology/site-families/handwerk-trust-engineering/linguistic-rules.yaml +6 -0
  194. package/src/ontology/tests/catalogs.pbt.test.ts +89 -0
  195. package/src/ontology/tests/enums.test.ts +203 -0
  196. package/src/ontology/tests/sternsystem-owner.test.ts +63 -0
  197. package/src/ontology/vitest.config.ts +8 -0
  198. package/src/passport/dht-sign.test.ts +99 -0
  199. package/src/passport/dht-sign.ts +73 -0
  200. package/src/passport/identity-sign.test.ts +134 -0
  201. package/src/passport/identity-sign.ts +85 -0
  202. package/src/passport/index.ts +53 -0
  203. package/src/passport/key-rotate.ts +133 -0
  204. package/src/passport/schema.ts +198 -0
  205. package/src/passport/sign.ts +225 -0
  206. package/src/passport/tests/schema.pbt.test.ts +93 -0
  207. package/src/passport/tests/schema.test.ts +222 -0
  208. package/src/passport/vitest.config.ts +8 -0
  209. package/src/share/agent/api-catalog.ts +77 -0
  210. package/src/share/agent/capability.ts +68 -0
  211. package/src/share/agent/dns-aid.test.ts +74 -0
  212. package/src/share/agent/dns-aid.ts +53 -0
  213. package/src/share/agent/fleet-catalog.ts +149 -0
  214. package/src/share/agent/index.ts +29 -0
  215. package/src/share/agent/knowledge.ts +96 -0
  216. package/src/share/agent/manifest.ts +126 -0
  217. package/src/share/agent/mcp-card.ts +63 -0
  218. package/src/share/agent/omit-empty.ts +77 -0
  219. package/src/share/agent/openapi.ts +157 -0
  220. package/src/share/agent/proof.ts +61 -0
  221. package/src/share/attribution-display.ts +86 -0
  222. package/src/share/check-hints.ts +38 -0
  223. package/src/share/content/entity-id.ts +65 -0
  224. package/src/share/content/index.ts +16 -0
  225. package/src/share/content/merge.ts +86 -0
  226. package/src/share/content/resolve-field-path.ts +39 -0
  227. package/src/share/content/substitute-deep.ts +47 -0
  228. package/src/share/content-discipline/index.ts +14 -0
  229. package/src/share/content-discipline/parsers.ts +66 -0
  230. package/src/share/content-discipline/types.ts +118 -0
  231. package/src/share/content-reference.ts +333 -0
  232. package/src/share/counter-utils.ts +75 -0
  233. package/src/share/css-value-normalize.ts +48 -0
  234. package/src/share/dev-props-validator.ts +131 -0
  235. package/src/share/entitlement.ts +172 -0
  236. package/src/share/env.d.ts +73 -0
  237. package/src/share/feature-policy.ts +367 -0
  238. package/src/share/formula-eval.ts +364 -0
  239. package/src/share/fs/index.ts +96 -0
  240. package/src/share/i18n/localization.ts +57 -0
  241. package/src/share/image-provider.ts +314 -0
  242. package/src/share/index.ts +86 -0
  243. package/src/share/knowledge/claim.ts +129 -0
  244. package/src/share/knowledge/derivation.ts +55 -0
  245. package/src/share/knowledge/freshness.ts +127 -0
  246. package/src/share/knowledge/index.ts +23 -0
  247. package/src/share/knowledge/ledger.ts +222 -0
  248. package/src/share/knowledge/plan.ts +122 -0
  249. package/src/share/knowledge/source.ts +170 -0
  250. package/src/share/legal/translation-policy.ts +133 -0
  251. package/src/share/material-credits.ts +181 -0
  252. package/src/share/middleware/language-redirect.ts +49 -0
  253. package/src/share/offer-capacity.ts +149 -0
  254. package/src/share/onboarding-yaml/index.ts +162 -0
  255. package/src/share/page.ts +351 -0
  256. package/src/share/redirects.test.ts +62 -0
  257. package/src/share/redirects.ts +54 -0
  258. package/src/share/rfc0042-utils.ts +95 -0
  259. package/src/share/runtime-context.ts +70 -0
  260. package/src/share/schemas/claim-records.ts +69 -0
  261. package/src/share/schemas/claims.ts +150 -0
  262. package/src/share/schemas/effects.ts +212 -0
  263. package/src/share/schemas/features.ts +147 -0
  264. package/src/share/schemas/horizontal-align.ts +22 -0
  265. package/src/share/schemas/icon-color.ts +27 -0
  266. package/src/share/schemas/index.ts +43 -0
  267. package/src/share/schemas/live-photo.ts +53 -0
  268. package/src/share/schemas/material-credit.ts +292 -0
  269. package/src/share/schemas/media.ts +220 -0
  270. package/src/share/schemas/navigation.ts +74 -0
  271. package/src/share/schemas/page-base.ts +33 -0
  272. package/src/share/schemas/participant.ts +247 -0
  273. package/src/share/schemas/person.ts +24 -0
  274. package/src/share/schemas/print.ts +93 -0
  275. package/src/share/schemas/section-background.ts +106 -0
  276. package/src/share/schemas/section-body.ts +138 -0
  277. package/src/share/schemas/section-cards.ts +37 -0
  278. package/src/share/schemas/section-cta.ts +74 -0
  279. package/src/share/schemas/section-header.ts +77 -0
  280. package/src/share/schemas/section-image.ts +77 -0
  281. package/src/share/schemas/section-motion.ts +52 -0
  282. package/src/share/schemas/section-shell.ts +63 -0
  283. package/src/share/schemas/section-stats.ts +37 -0
  284. package/src/share/schemas/site-background.ts +78 -0
  285. package/src/share/schemas/standard-list-item.ts +38 -0
  286. package/src/share/scripts/external-links.ts +45 -0
  287. package/src/share/scripts/gsap-counter.ts +168 -0
  288. package/src/share/scripts/gsap-mountain-journey.ts +236 -0
  289. package/src/share/scripts/gsap-parallax.ts +50 -0
  290. package/src/share/scripts/gsap-reveal.ts +65 -0
  291. package/src/share/scripts/gsap-stagger.ts +54 -0
  292. package/src/share/scripts/index.ts +25 -0
  293. package/src/share/scripts/inline-number-animation.ts +120 -0
  294. package/src/share/scripts/lenis.ts +109 -0
  295. package/src/share/scripts/live-photos.ts +250 -0
  296. package/src/share/scripts/lordicon.ts +94 -0
  297. package/src/share/scripts/orchestrator.ts +138 -0
  298. package/src/share/scripts/scheduler.ts +53 -0
  299. package/src/share/scripts/video-player.ts +116 -0
  300. package/src/share/semantic/ai.ts +140 -0
  301. package/src/share/semantic/block-extraction.ts +52 -0
  302. package/src/share/semantic/block-extractors/index.ts +502 -0
  303. package/src/share/semantic/breadcrumbs.ts +120 -0
  304. package/src/share/semantic/build-page.ts +309 -0
  305. package/src/share/semantic/business-projection.ts +278 -0
  306. package/src/share/semantic/derived-prices-loader.ts +36 -0
  307. package/src/share/semantic/extract.ts +118 -0
  308. package/src/share/semantic/feed.ts +124 -0
  309. package/src/share/semantic/ids.ts +76 -0
  310. package/src/share/semantic/image-sitemap.ts +153 -0
  311. package/src/share/semantic/index.ts +36 -0
  312. package/src/share/semantic/jsonld/article.ts +47 -0
  313. package/src/share/semantic/jsonld/breadcrumb.ts +47 -0
  314. package/src/share/semantic/jsonld/collection-list.ts +30 -0
  315. package/src/share/semantic/jsonld/context.ts +46 -0
  316. package/src/share/semantic/jsonld/faq.ts +49 -0
  317. package/src/share/semantic/jsonld/initiative.ts +53 -0
  318. package/src/share/semantic/jsonld/organization.ts +101 -0
  319. package/src/share/semantic/jsonld/person.ts +71 -0
  320. package/src/share/semantic/jsonld/service.ts +80 -0
  321. package/src/share/semantic/jsonld/shared.ts +31 -0
  322. package/src/share/semantic/jsonld/types.ts +18 -0
  323. package/src/share/semantic/jsonld/webpage.ts +98 -0
  324. package/src/share/semantic/jsonld/website.ts +27 -0
  325. package/src/share/semantic/jsonld.ts +75 -0
  326. package/src/share/semantic/llms-policy.ts +74 -0
  327. package/src/share/semantic/llms.ts +394 -0
  328. package/src/share/semantic/markdown-hygiene.ts +68 -0
  329. package/src/share/semantic/markdown-twin-provenance.ts +242 -0
  330. package/src/share/semantic/models.ts +402 -0
  331. package/src/share/semantic/organization-profile.ts +160 -0
  332. package/src/share/semantic/output-projection.ts +131 -0
  333. package/src/share/semantic/page-builders/markdown-page.ts +84 -0
  334. package/src/share/semantic/page-markdown.ts +234 -0
  335. package/src/share/semantic/page-utils.ts +125 -0
  336. package/src/share/semantic/price-marker-resolver.ts +92 -0
  337. package/src/share/semantic/robots.ts +127 -0
  338. package/src/share/semantic/update-stamp.ts +136 -0
  339. package/src/share/shared-context.ts +177 -0
  340. package/src/share/string-utils.ts +19 -0
  341. package/src/share/tests/api-catalog.test.ts +97 -0
  342. package/src/share/tests/breadcrumbs.test.ts +126 -0
  343. package/src/share/tests/build-page-price-markers.test.ts +160 -0
  344. package/src/share/tests/capability.test.ts +107 -0
  345. package/src/share/tests/claim.test.ts +90 -0
  346. package/src/share/tests/content-ref-index.test.ts +328 -0
  347. package/src/share/tests/create-dev-props-validator.test.ts +38 -0
  348. package/src/share/tests/derivation.test.ts +39 -0
  349. package/src/share/tests/dev-props-validator.test.ts +88 -0
  350. package/src/share/tests/fleet-catalog.test.ts +134 -0
  351. package/src/share/tests/formula-eval.test.ts +619 -0
  352. package/src/share/tests/freshness.test.ts +60 -0
  353. package/src/share/tests/hero-section-numbering-parity.test.ts +51 -0
  354. package/src/share/tests/image-sitemap.test.ts +101 -0
  355. package/src/share/tests/jsonld-service.test.ts +282 -0
  356. package/src/share/tests/knowledge.test.ts +86 -0
  357. package/src/share/tests/ledger.test.ts +125 -0
  358. package/src/share/tests/llms-0789.test.ts +63 -0
  359. package/src/share/tests/manifest.test.ts +100 -0
  360. package/src/share/tests/markdown-twin-provenance.test.ts +108 -0
  361. package/src/share/tests/mcp-card.test.ts +57 -0
  362. package/src/share/tests/onboarding-yaml.test.ts +111 -0
  363. package/src/share/tests/ontology-role-by-cosmic-name.test.ts +28 -0
  364. package/src/share/tests/openapi.test.ts +116 -0
  365. package/src/share/tests/organization-jsonld.test.ts +74 -0
  366. package/src/share/tests/output-projection.test.ts +61 -0
  367. package/src/share/tests/plan.test.ts +97 -0
  368. package/src/share/tests/price-marker-resolver.test.ts +141 -0
  369. package/src/share/tests/proof.test.ts +72 -0
  370. package/src/share/tests/resolve-field-path.test.ts +52 -0
  371. package/src/share/tests/source.test.ts +81 -0
  372. package/src/share/tests/substitute-deep.test.ts +65 -0
  373. package/src/share/tests/text-normalize.pbt.test.ts +143 -0
  374. package/src/share/tests/text-normalize.test.ts +285 -0
  375. package/src/share/tests/twin-path.test.ts +43 -0
  376. package/src/share/text-normalize.ts +548 -0
  377. package/src/share/text-position.ts +19 -0
  378. package/src/share/types/hls-js-light.d.ts +16 -0
  379. package/src/share/visibility.ts +167 -0
  380. package/src/share/vitest.config.ts +8 -0
  381. package/src/share/wrap-inline-numbers.ts +139 -0
  382. package/src/surface/blueprint-schema.ts +314 -0
  383. package/src/surface/blueprint-types.ts +256 -0
  384. package/src/surface/blueprint.ts +300 -0
  385. package/src/surface/breaker.ts +57 -0
  386. package/src/surface/decision-composer.ts +156 -0
  387. package/src/surface/eligibility.ts +372 -0
  388. package/src/surface/evidence-records.ts +97 -0
  389. package/src/surface/fleet.ts +60 -0
  390. package/src/surface/geo.ts +170 -0
  391. package/src/surface/governance.ts +146 -0
  392. package/src/surface/index.ts +216 -0
  393. package/src/surface/io/index.ts +18 -0
  394. package/src/surface/io/surface-module-context-io.ts +60 -0
  395. package/src/surface/io/visibility-outcomes-io.ts +46 -0
  396. package/src/surface/module-context.ts +120 -0
  397. package/src/surface/substance.ts +161 -0
  398. package/src/surface/tests/eligibility.pbt.test.ts +137 -0
  399. package/src/surface/tests/eligibility.test.ts +302 -0
  400. package/src/surface/types.ts +289 -0
  401. package/src/surface/visibility.ts +57 -0
  402. package/src/surface/vitest.config.ts +8 -0
  403. package/tsconfig.json +17 -0
@@ -0,0 +1,491 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>CRM Buffer type contracts (Lagebild). Supabase-backed intermediate storage layer between
4
+ UChat (IntegrationEvent source) and Pipedrive (CRM sync target). Stores consolidated contacts,
5
+ deals, funnel stage transitions, and the outbox used by the async sync worker. This is the
6
+ single source of truth for types shared between the buffer DestinationAdapter and the sync worker.
7
+ Pure types + constants — no I/O, no astro:env imports.</purpose>
8
+ <non-goals>
9
+ <item>Do not import a Supabase SDK — callers inject a CrmBufferClient implementation.</item>
10
+ <item>Do not read astro:env — secrets are injected by the adapter/worker at call time.</item>
11
+ <item>Do not become a CRM replacement — stores only what is needed for Pipedrive sync.</item>
12
+ </non-goals>
13
+ </MODULE_CONTRACT>
14
+ <CHANGE_SUMMARY>
15
+ <item>Lagebild MVP: initial CRM buffer type contracts.</item>
16
+ <item>RFC-0188 Phase 3: bridge canonical funnel stage → generic stage; add offer snapshot on the
17
+ deal, append-only funnel-event and legal-consent rows. Generic BUFFER_DEAL_STAGES is BRIDGED
18
+ (not replaced) so existing rows and the Pipedrive sync worker keep working.</item>
19
+ </CHANGE_SUMMARY>
20
+ */
21
+
22
+ import type {
23
+ VisitorBuyerType,
24
+ VisitorFunnelEventKind,
25
+ VisitorFunnelEventPayload,
26
+ VisitorFunnelStage,
27
+ } from "./funnel.ts";
28
+ import { VISITOR_FUNNEL_STAGES } from "./funnel.ts";
29
+ import type {
30
+ InvoiceKind,
31
+ InvoiceStatus,
32
+ SubscriptionPlan,
33
+ SubscriptionStatus,
34
+ } from "./lifecycle.ts";
35
+
36
+ // ---------------------------------------------------------------------------
37
+ // Funnel stage catalog (closed)
38
+ // ---------------------------------------------------------------------------
39
+
40
+ /**
41
+ * Closed catalog of deal funnel stages. Order matters — later stages come after
42
+ * earlier ones. Mirrors the standard Pipedrive pipeline stages.
43
+ * Extend only by updating this array AND the corresponding Pipedrive pipeline config.
44
+ */
45
+ export const BUFFER_DEAL_STAGES = [
46
+ "new",
47
+ "contacted",
48
+ "qualified",
49
+ "proposal",
50
+ "negotiation",
51
+ "won",
52
+ "lost",
53
+ ] as const;
54
+
55
+ export type BufferDealStage = (typeof BUFFER_DEAL_STAGES)[number];
56
+
57
+ // ---------------------------------------------------------------------------
58
+ // RFC-0188 bridge: canonical funnel stage → generic buffer/Pipedrive stage
59
+ // ---------------------------------------------------------------------------
60
+
61
+ /**
62
+ * RFC-0188 keeps the precise, platform-owned VisitorFunnelStage on the deal
63
+ * (`funnel_stage`) while BRIDGING it down to the generic BufferDealStage the
64
+ * Pipedrive sync worker already understands. This preserves existing rows and the
65
+ * outbound sync mapping — the canonical stage is additive, never a replacement.
66
+ * Exhaustive by construction: a missing key is a compile error after a catalog change.
67
+ */
68
+ export const FUNNEL_STAGE_TO_BUFFER_STAGE: Record<VisitorFunnelStage, BufferDealStage> = {
69
+ new_session: "new",
70
+ privacy_acknowledged: "new",
71
+ intent_selected: "contacted",
72
+ organization_selected: "contacted",
73
+ qualification_priority: "qualified",
74
+ qualification_company: "qualified",
75
+ qualification_service: "qualified",
76
+ qualification_region: "qualified",
77
+ offer_presented: "proposal",
78
+ payment_pending: "proposal",
79
+ payment_confirmed: "negotiation",
80
+ start_choice_pending: "negotiation",
81
+ start_deferred: "negotiation",
82
+ buyer_type_pending: "negotiation",
83
+ b2b_start_consent_pending: "negotiation",
84
+ b2c_withdrawal_consent_pending: "negotiation",
85
+ start_approved: "negotiation",
86
+ legal_data_requested: "negotiation",
87
+ materials_requested: "negotiation",
88
+ production_ready: "negotiation",
89
+ change_balance_checked: "contacted",
90
+ change_payment_pending: "proposal",
91
+ change_description_requested: "negotiation",
92
+ operator_review: "negotiation",
93
+ won: "won",
94
+ lost: "lost",
95
+ };
96
+
97
+ /** Bridge a canonical funnel stage down to the generic stage the sync worker maps. */
98
+ export function bridgeFunnelStage(stage: VisitorFunnelStage): BufferDealStage {
99
+ return FUNNEL_STAGE_TO_BUFFER_STAGE[stage];
100
+ }
101
+
102
+ /** True when `value` is a canonical funnel stage (re-exported guard for buffer callers). */
103
+ export function isFunnelStage(value: string): value is VisitorFunnelStage {
104
+ return (VISITOR_FUNNEL_STAGES as readonly string[]).includes(value);
105
+ }
106
+
107
+ // ---------------------------------------------------------------------------
108
+ // Row shapes (mirror the Supabase DDL — see services/lagebild-sync/supabase/)
109
+ // ---------------------------------------------------------------------------
110
+
111
+ /** Normalized contact stored in the buffer. One row per unique contact. */
112
+ export interface BufferContact {
113
+ id: string; // UUID
114
+ tenant_id: string; // UUID — for future multi-tenancy
115
+ uchat_contact_id?: string; // UChat's own contact identifier (for dedup)
116
+ name?: string;
117
+ email?: string;
118
+ phone?: string;
119
+ /** Raw metadata from the last UChat event (e.g. tags, custom fields). */
120
+ uchat_meta?: Record<string, unknown>;
121
+ pipedrive_person_id?: number; // set after first successful Pipedrive sync
122
+ created_at: string; // ISO-8601
123
+ updated_at: string; // ISO-8601
124
+ }
125
+
126
+ /**
127
+ * RFC-0190: the target company a site is FOR (may differ from the orderer Person).
128
+ * One Person → many Deals; each Deal → one Organization. Tenant-scoped; mirrored to
129
+ * the tenant's Pipedrive Organization. Dedup by (tenant_id, legal_name) when present,
130
+ * else (tenant_id, name).
131
+ */
132
+ export interface BufferOrganization {
133
+ id: string; // UUID
134
+ tenant_id: string;
135
+ name: string; // display name (target company)
136
+ legal_name?: string; // for Impressum / invoicing
137
+ industry?: string;
138
+ region?: string;
139
+ pipedrive_org_id?: number; // set after first Pipedrive sync
140
+ stripe_customer_id?: string; // set by RFC-0191 billing (billed entity)
141
+ created_at: string;
142
+ updated_at: string;
143
+ }
144
+
145
+ /** Deal linked to a contact with a current funnel stage. */
146
+ export interface BufferDeal {
147
+ id: string; // UUID
148
+ tenant_id: string;
149
+ contact_id: string; // FK → buffer_contacts.id
150
+ /**
151
+ * RFC-0190: the target Organization this deal's site is for (additive, nullable).
152
+ * Absent on pre-RFC-0190 rows and on org-less events (e.g. a pure free question).
153
+ */
154
+ organization_id?: string; // FK → buffer_organizations.id
155
+ title: string;
156
+ /** Generic stage the Pipedrive sync worker maps (bridged from funnel_stage). */
157
+ stage: BufferDealStage;
158
+ /**
159
+ * RFC-0188: the precise, platform-owned funnel stage (additive). When set, the
160
+ * generic `stage` above is its bridge via FUNNEL_STAGE_TO_BUFFER_STAGE. Absent on
161
+ * pre-RFC-0188 rows, which keep working on the generic stage alone.
162
+ */
163
+ funnel_stage?: VisitorFunnelStage;
164
+ value?: number; // deal value (currency-agnostic, same unit as Pipedrive)
165
+ currency?: string; // ISO 4217
166
+ /**
167
+ * RFC-0188: deal-time offer snapshot captured at offer.selected. Frozen so a later
168
+ * business/offer.md change never re-prices a historical deal. Keyed price strings
169
+ * (e.g. { monthly: "70 €", yearly: "700 €", setup: "200 €" }).
170
+ */
171
+ offer_snapshot?: Record<string, string>;
172
+ /** Actor that last changed the stage. */
173
+ last_actor: "uchat" | "pipedrive" | "manual";
174
+ pipedrive_deal_id?: number; // set after first successful Pipedrive sync
175
+ pipedrive_lead_id?: string; // set when synced as a Pipedrive lead (not deal)
176
+ created_at: string;
177
+ updated_at: string;
178
+ }
179
+
180
+ /** Immutable audit log of every stage transition (append-only). */
181
+ export interface BufferStageTransition {
182
+ id: string; // UUID
183
+ tenant_id: string;
184
+ deal_id: string; // FK → buffer_deals.id
185
+ from_stage?: BufferDealStage; // null on first transition (deal creation)
186
+ to_stage: BufferDealStage;
187
+ actor: "uchat" | "pipedrive" | "manual";
188
+ /** ISO-8601 event time (from the source IntegrationEvent.occurredAt). */
189
+ occurred_at: string;
190
+ created_at: string;
191
+ }
192
+
193
+ /**
194
+ * RFC-0188: append-only typed funnel-event snapshot. One row per accepted funnel
195
+ * event (session.started, qualification.answered, offer.selected, …). The full
196
+ * VisitorFunnelEventPayload is frozen here so a deal's history is auditable and
197
+ * never re-derived. Idempotent by (tenant_id, idempotency_key): a UChat webhook
198
+ * retry must not create a second row.
199
+ */
200
+ export interface BufferFunnelEvent {
201
+ id: string; // UUID
202
+ tenant_id: string;
203
+ deal_id: string; // FK → buffer_deals.id
204
+ /** Stable idempotency key (the source IntegrationEvent.eventId). */
205
+ idempotency_key: string;
206
+ event_kind: VisitorFunnelEventKind;
207
+ funnel_version: string;
208
+ from_stage?: VisitorFunnelStage;
209
+ to_stage?: VisitorFunnelStage;
210
+ /** Frozen typed payload snapshot (deal-time). */
211
+ payload: VisitorFunnelEventPayload;
212
+ actor: "uchat" | "stripe" | "operator" | "send-message";
213
+ occurred_at: string; // ISO-8601
214
+ created_at: string;
215
+ }
216
+
217
+ /**
218
+ * RFC-0188: append-only legal-consent evidence. NEVER overwritten by a later
219
+ * conversation message. Captures B2C withdrawal-rights acknowledgement and B2B
220
+ * start-before-completion consent as immutable rows for legal auditability.
221
+ */
222
+ export interface BufferConsentEvent {
223
+ id: string; // UUID
224
+ tenant_id: string;
225
+ deal_id: string; // FK → buffer_deals.id
226
+ buyer_type: VisitorBuyerType;
227
+ /** Which consent this row records. */
228
+ consent_kind: "b2b_start_before_completion" | "b2c_withdrawal_acknowledged";
229
+ /** B2B: visitor agreed to start before the withdrawal period ends. */
230
+ start_before_withdrawal_period?: boolean;
231
+ /** B2C: visitor acknowledged the withdrawal-right expiry on early start. */
232
+ withdrawal_expiry_acknowledged?: boolean;
233
+ locale: string;
234
+ occurred_at: string; // ISO-8601
235
+ created_at: string;
236
+ }
237
+
238
+ // ---------------------------------------------------------------------------
239
+ // RFC-0191 lifecycle state: subscriptions + invoices (Stripe is the authority)
240
+ // ---------------------------------------------------------------------------
241
+
242
+ /**
243
+ * RFC-0191: the recurring subscription anchoring a client's MRR. One per active site
244
+ * subscription, attached to the Organization (RFC-0190) and the deal. Dedup by
245
+ * stripe_subscription_id. The buffer mirrors Stripe — it never recomputes amounts.
246
+ */
247
+ export interface BufferSubscription {
248
+ id: string; // UUID
249
+ tenant_id: string;
250
+ organization_id: string; // FK → buffer_organizations.id (the billed company)
251
+ deal_id: string; // FK → buffer_deals.id (the site)
252
+ stripe_subscription_id: string;
253
+ status: SubscriptionStatus;
254
+ plan: SubscriptionPlan;
255
+ mrr_cents: number; // monthly recurring revenue, smallest currency unit
256
+ currency: string; // ISO 4217
257
+ current_period_end?: string; // ISO-8601 (renewal anchor)
258
+ /** Authoritative remaining included-changes count for the P4 change flow. */
259
+ included_changes_balance: number;
260
+ /** RFC-0191 / spec §08: per-cycle included-change allowance (the reset target each cycle). */
261
+ included_changes_per_cycle: number;
262
+ /** RFC-0386: Pipedrive P3 deal id, set after first lifecycle sync. */
263
+ pipedrive_deal_id?: number;
264
+ created_at: string;
265
+ updated_at: string;
266
+ }
267
+
268
+ /**
269
+ * RFC-0191: append-only invoice mirror. One row per Stripe invoice (setup / cycle /
270
+ * change / ad-hoc). Idempotent by stripe_invoice_id so a webhook redelivery never
271
+ * double-records. Amounts in cents (Stripe is the authority).
272
+ */
273
+ export interface BufferInvoice {
274
+ id: string; // UUID
275
+ tenant_id: string;
276
+ organization_id: string; // FK → buffer_organizations.id
277
+ deal_id?: string; // FK → buffer_deals.id (the related site/change deal)
278
+ subscription_id?: string; // FK → buffer_subscriptions.id (for cycle invoices)
279
+ stripe_invoice_id: string;
280
+ kind: InvoiceKind;
281
+ amount_cents: number;
282
+ currency: string;
283
+ status: InvoiceStatus;
284
+ paid_at?: string; // ISO-8601
285
+ created_at: string;
286
+ }
287
+
288
+ // ---------------------------------------------------------------------------
289
+ // Sync outbox
290
+ // ---------------------------------------------------------------------------
291
+
292
+ /** Pending states for the async sync outbox worker. */
293
+ export const SYNC_OUTBOX_STATUSES = ["pending", "processing", "done", "failed", "dead"] as const;
294
+ export type SyncOutboxStatus = (typeof SYNC_OUTBOX_STATUSES)[number];
295
+
296
+ /** Sync operation kind — what the outbox worker should do. */
297
+ export const SYNC_OUTBOX_OPS = [
298
+ "upsert_contact",
299
+ "upsert_deal",
300
+ "update_deal_stage",
301
+ // RFC-0190: sync the target Organization to Pipedrive BEFORE the deal so the deal
302
+ // can reference pipedrive_org_id.
303
+ "upsert_organization",
304
+ // RFC-0386: lifecycle sync — drain subscription/invoice state to Pipedrive P3/P4.
305
+ "upsert_subscription",
306
+ "upsert_invoice",
307
+ ] as const;
308
+ export type SyncOutboxOp = (typeof SYNC_OUTBOX_OPS)[number];
309
+
310
+ /** One pending task in the sync outbox (read by the supabase-sync worker). */
311
+ export interface SyncOutboxRow {
312
+ id: string; // UUID
313
+ tenant_id: string;
314
+ op: SyncOutboxOp;
315
+ /** Serialized payload specific to the op (e.g. BufferContact id, BufferDeal id). */
316
+ payload: Record<string, unknown>;
317
+ status: SyncOutboxStatus;
318
+ /** ISO-8601 time the task was scheduled. */
319
+ scheduled_at: string;
320
+ /** ISO-8601 time the worker started processing (null until picked up). */
321
+ processing_at?: string;
322
+ /** ISO-8601 time the task completed or failed. */
323
+ resolved_at?: string;
324
+ retry_count: number;
325
+ max_retries: number;
326
+ last_error?: string;
327
+ created_at: string;
328
+ updated_at: string;
329
+ }
330
+
331
+ // ---------------------------------------------------------------------------
332
+ // CrmBufferWriter — adapter-side write port (no SDK import)
333
+ // ---------------------------------------------------------------------------
334
+
335
+ /** Result of upserting a contact or deal into the buffer. */
336
+ export interface BufferUpsertResult {
337
+ id: string;
338
+ created: boolean; // true = new row, false = updated existing
339
+ }
340
+
341
+ /** Result of writing one or more outbox tasks. */
342
+ export interface OutboxWriteResult {
343
+ ids: string[];
344
+ }
345
+
346
+ /**
347
+ * Injectable write-side port for the CRM buffer (Supabase). Used by the
348
+ * DestinationAdapter to persist contacts, deals, funnel events, consent,
349
+ * subscriptions, invoices, and outbox tasks. Callers never import the Supabase
350
+ * SDK — they receive a CrmBufferWriter implementation from the adapter factory.
351
+ */
352
+ export interface CrmBufferWriter {
353
+ upsertContact(
354
+ tenantId: string,
355
+ data: Omit<BufferContact, "id" | "tenant_id" | "created_at" | "updated_at">,
356
+ ): Promise<BufferUpsertResult>;
357
+
358
+ upsertOrganization(
359
+ tenantId: string,
360
+ data: Omit<BufferOrganization, "id" | "tenant_id" | "created_at" | "updated_at">,
361
+ ): Promise<BufferUpsertResult>;
362
+
363
+ findOrganizationByStripeCustomer(
364
+ tenantId: string,
365
+ stripeCustomerId: string,
366
+ ): Promise<{ id: string } | null>;
367
+
368
+ upsertDeal(
369
+ tenantId: string,
370
+ data: Omit<BufferDeal, "id" | "tenant_id" | "created_at" | "updated_at">,
371
+ ): Promise<BufferUpsertResult>;
372
+
373
+ appendStageTransition(
374
+ tenantId: string,
375
+ data: Omit<BufferStageTransition, "id" | "tenant_id" | "created_at">,
376
+ ): Promise<string>;
377
+
378
+ appendFunnelEvent(
379
+ tenantId: string,
380
+ data: Omit<BufferFunnelEvent, "id" | "tenant_id" | "created_at">,
381
+ ): Promise<string>;
382
+
383
+ appendConsentEvent(
384
+ tenantId: string,
385
+ data: Omit<BufferConsentEvent, "id" | "tenant_id" | "created_at">,
386
+ ): Promise<string>;
387
+
388
+ upsertSubscription(
389
+ tenantId: string,
390
+ data: Omit<
391
+ BufferSubscription,
392
+ "id" | "tenant_id" | "created_at" | "updated_at" | "included_changes_balance"
393
+ > & { included_changes_balance?: number },
394
+ ): Promise<BufferUpsertResult>;
395
+
396
+ findSubscriptionByStripeId(
397
+ tenantId: string,
398
+ stripeSubscriptionId: string,
399
+ ): Promise<{
400
+ id: string;
401
+ included_changes_per_cycle: number;
402
+ included_changes_balance: number;
403
+ } | null>;
404
+
405
+ findActiveSubscriptionByOrganization(
406
+ tenantId: string,
407
+ organizationId: string,
408
+ ): Promise<{ id: string; included_changes_balance: number } | null>;
409
+
410
+ appendInvoice(
411
+ tenantId: string,
412
+ data: Omit<BufferInvoice, "id" | "tenant_id" | "created_at">,
413
+ ): Promise<string>;
414
+
415
+ adjustChangeBalance(tenantId: string, subscriptionId: string, delta: number): Promise<number>;
416
+
417
+ writeOutbox(
418
+ tenantId: string,
419
+ tasks: Array<{
420
+ op: SyncOutboxOp;
421
+ payload: Record<string, unknown>;
422
+ maxRetries?: number;
423
+ }>,
424
+ ): Promise<OutboxWriteResult>;
425
+ }
426
+
427
+ // ---------------------------------------------------------------------------
428
+ // CrmBufferReader — worker-side read/patch port
429
+ // ---------------------------------------------------------------------------
430
+
431
+ /** Patch payload for writing back Pipedrive IDs on a deal. */
432
+ export interface DealPipedriveIdPatch {
433
+ pipedrive_deal_id?: number;
434
+ pipedrive_lead_id?: string;
435
+ }
436
+
437
+ /**
438
+ * Injectable read-side port for the CRM buffer. Used by the sync worker and
439
+ * CrmSyncTarget adapters to read buffer rows and write back destination IDs.
440
+ */
441
+ export interface CrmBufferReader {
442
+ readPendingOutbox(tenantId: string, limit?: number): Promise<SyncOutboxRow[]>;
443
+
444
+ updateOutboxStatus(
445
+ id: string,
446
+ status: SyncOutboxStatus,
447
+ opts?: { lastError?: string; retryCount?: number },
448
+ ): Promise<void>;
449
+
450
+ getContact(tenantId: string, contactId: string): Promise<BufferContact | null>;
451
+
452
+ getDeal(tenantId: string, dealId: string): Promise<BufferDeal | null>;
453
+
454
+ getOrganization(tenantId: string, organizationId: string): Promise<BufferOrganization | null>;
455
+
456
+ patchContactPipedriveId(
457
+ tenantId: string,
458
+ contactId: string,
459
+ pipedrivePersonId: number,
460
+ ): Promise<void>;
461
+
462
+ patchOrganizationPipedriveId(
463
+ tenantId: string,
464
+ organizationId: string,
465
+ pipedriveOrgId: number,
466
+ ): Promise<void>;
467
+
468
+ patchDealPipedriveIds(
469
+ tenantId: string,
470
+ dealId: string,
471
+ patch: DealPipedriveIdPatch,
472
+ ): Promise<void>;
473
+
474
+ // RFC-0386: lifecycle sync read methods.
475
+
476
+ getSubscription(tenantId: string, subscriptionId: string): Promise<BufferSubscription | null>;
477
+
478
+ getInvoice(tenantId: string, invoiceId: string): Promise<BufferInvoice | null>;
479
+
480
+ patchSubscriptionPipedriveDealId(
481
+ tenantId: string,
482
+ subscriptionId: string,
483
+ pipedriveDealId: number,
484
+ ): Promise<void>;
485
+
486
+ // RFC-0386: balance reset on paid cycle invoices.
487
+ adjustChangeBalance(tenantId: string, subscriptionId: string, delta: number): Promise<number>;
488
+ }
489
+
490
+ /** Combined interface — the Supabase client implements both writer and reader. */
491
+ export type CrmBufferClient = CrmBufferWriter & CrmBufferReader;
@@ -0,0 +1,106 @@
1
+ /*
2
+ <MODULE_CONTRACT>
3
+ <purpose>RFC-0179: the dynamic-dispatch execution seam. The shared, token-free consumer never runs a
4
+ destination adapter itself — it dispatches each event into the originating tenant Worker, which runs
5
+ its gogol-adapter destinations with the CLIENT's own secrets and returns what routed/failed. This
6
+ keeps destination tokens and any datastore inside the isolated tenant while the delivery backbone is
7
+ shared. Pure contracts + two thin helpers (tenant-side executor, consumer-side dispatcher).</purpose>
8
+ <non-goals>
9
+ <item>Do not read or pass destination tokens through the shared consumer — only the tenant holds them.</item>
10
+ <item>Do not persist event payloads — the dispatch is in-flight only (RFC-0177).</item>
11
+ </non-goals>
12
+ </MODULE_CONTRACT>
13
+ <CHANGE_SUMMARY>
14
+ <item>RFC-0179: initial implementation.</item>
15
+ </CHANGE_SUMMARY>
16
+ */
17
+
18
+ import type { IntegrationEvent, IntegrationSecrets } from "./port.ts";
19
+ import { routeEventToReady, type RouteResult } from "./index.ts";
20
+
21
+ /** Internal route the shared consumer dispatches into on the tenant (RFC-0179). */
22
+ export const DISPATCH_ROUTE = "/internal/integration-route" as const;
23
+
24
+ /** Envelope the shared consumer sends into a tenant's internal route. */
25
+ export interface DispatchExecuteRequest {
26
+ siteId: string;
27
+ event: IntegrationEvent;
28
+ }
29
+
30
+ /** What the tenant reports back. `routed`/`failed` are `${kind}:${vendor}` ids. */
31
+ export interface DispatchExecuteResult {
32
+ routed: string[];
33
+ failed: string[];
34
+ skipped: string[];
35
+ }
36
+
37
+ /**
38
+ * Tenant-side handler body for `POST /internal/integration-route` (RFC-0179).
39
+ * Runs the tenant's ready gogol-adapter destinations with the CLIENT's own
40
+ * secrets and an optional dedup set. The shared consumer never calls adapters
41
+ * directly — it calls this, so tokens stay inside the tenant.
42
+ */
43
+ export async function executeDispatch(
44
+ request: DispatchExecuteRequest,
45
+ secrets: IntegrationSecrets,
46
+ seen?: { has(key: string): boolean | Promise<boolean>; add(key: string): unknown },
47
+ ): Promise<DispatchExecuteResult> {
48
+ const result: RouteResult = await routeEventToReady(request.event, secrets, seen);
49
+ return { routed: result.routed, failed: result.failed, skipped: result.skipped };
50
+ }
51
+
52
+ /**
53
+ * Minimal structural type for a Workers-for-Platforms dynamic-dispatch binding.
54
+ * `get(siteId)` returns the tenant Worker's fetcher; we never import the platform
55
+ * types (keeps @warpgogol/werkstatt-shared/share runtime-agnostic, mirroring QueueBinding/KvDedupStore).
56
+ */
57
+ export interface DispatchNamespaceBinding {
58
+ get(siteId: string): { fetch(request: Request): Promise<Response> };
59
+ }
60
+
61
+ /** Outcome the consumer uses to decide ack vs retry for a queued message. */
62
+ export interface DispatchOutcome {
63
+ ok: boolean;
64
+ result?: DispatchExecuteResult;
65
+ /** Set when the dispatch itself failed (unreachable tenant / bad status). */
66
+ transportError?: string;
67
+ }
68
+
69
+ /**
70
+ * Consumer-side: dispatch one event into its originating tenant Worker over HTTP
71
+ * and interpret the reply (RFC-0179). The consumer authenticates with the shared
72
+ * inbound secret (the tenant already trusts that secret for its inbound route) and
73
+ * holds NO destination token. A non-2xx response or a reply where nothing routed
74
+ * but something failed is a transport failure → the caller should `retry()`.
75
+ */
76
+ export async function dispatchToTenant(
77
+ dispatcher: DispatchNamespaceBinding,
78
+ request: DispatchExecuteRequest,
79
+ inboundSecret: string,
80
+ origin = "https://tenant.internal",
81
+ ): Promise<DispatchOutcome> {
82
+ try {
83
+ const tenant = dispatcher.get(request.siteId);
84
+ const response = await tenant.fetch(
85
+ new Request(`${origin}${DISPATCH_ROUTE}`, {
86
+ method: "POST",
87
+ headers: {
88
+ "content-type": "application/json",
89
+ "x-integration-secret": inboundSecret,
90
+ },
91
+ body: JSON.stringify(request),
92
+ }),
93
+ );
94
+ if (!response.ok) {
95
+ return { ok: false, transportError: `tenant responded ${response.status}` };
96
+ }
97
+ const result = (await response.json()) as DispatchExecuteResult;
98
+ // Nothing routed but something failed ⇒ retry the whole message (bounded → DLQ).
99
+ if (result.routed.length === 0 && result.failed.length > 0) {
100
+ return { ok: false, result, transportError: "all destinations failed" };
101
+ }
102
+ return { ok: true, result };
103
+ } catch (err) {
104
+ return { ok: false, transportError: (err as Error).message };
105
+ }
106
+ }