@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.
- package/package.json +352 -0
- package/src/checks/index.ts +24 -0
- package/src/checks/lib/astro-site-url.ts +30 -0
- package/src/checks/lib/i18n.ts +30 -0
- package/src/checks/result-helpers.ts +102 -0
- package/src/checks/suppressions-config.ts +195 -0
- package/src/index.ts +14 -0
- package/src/integration/README.md +38 -0
- package/src/integration/adapters.ts +143 -0
- package/src/integration/crm-buffer.ts +491 -0
- package/src/integration/dispatch.ts +106 -0
- package/src/integration/funnel.ts +379 -0
- package/src/integration/index.ts +56 -0
- package/src/integration/lifecycle.ts +155 -0
- package/src/integration/orchestration.ts +439 -0
- package/src/integration/port-barrel.ts +124 -0
- package/src/integration/port.ts +143 -0
- package/src/integration/qstash.ts +134 -0
- package/src/integration/sharding.ts +133 -0
- package/src/integration/tests/deliver-event.test.ts +52 -0
- package/src/integration/tests/dispatch.test.ts +88 -0
- package/src/integration/tests/funnel-bridge.test.ts +46 -0
- package/src/integration/tests/hub-runtime.test.ts +128 -0
- package/src/integration/tests/lifecycle.test.ts +69 -0
- package/src/integration/tests/qstash.test.ts +83 -0
- package/src/integration/tests/sharding.test.ts +64 -0
- package/src/integration/vitest.config.ts +8 -0
- package/src/integration-adapter-supabase-crm/adapter.ts +437 -0
- package/src/integration-adapter-supabase-crm/client.ts +605 -0
- package/src/integration-adapter-supabase-crm/index.ts +28 -0
- package/src/integration-adapter-supabase-crm/pipedrive-sync-target.ts +482 -0
- package/src/integration-adapter-supabase-crm/supabase-rest.ts +89 -0
- package/src/integration-adapter-supabase-crm/tenant-registry.ts +334 -0
- package/src/integration-adapter-supabase-crm/tests/change-balance.pbt.test.ts +83 -0
- package/src/integration-adapter-supabase-crm/tests/funnel-persistence.test.ts +409 -0
- package/src/integration-adapter-supabase-crm/tests/lifecycle-sync.test.ts +288 -0
- package/src/integration-adapter-supabase-crm/tests/stage-map.test.ts +80 -0
- package/src/integration-adapter-supabase-crm/vitest.config.ts +8 -0
- package/src/integration-adapter-supabase-crm/worker.ts +223 -0
- package/src/observability/conventions.ts +93 -0
- package/src/observability/env-resolver.ts +57 -0
- package/src/observability/index.ts +57 -0
- package/src/observability/metric-registry.ts +184 -0
- package/src/observability/otlp-converter.ts +125 -0
- package/src/observability/otlp-json.ts +135 -0
- package/src/observability/otlp-transport.ts +68 -0
- package/src/observability/pusher.ts +142 -0
- package/src/observability/redact.ts +27 -0
- package/src/observability/tests/conventions.test.ts +100 -0
- package/src/observability/tests/metric-registry.test.ts +117 -0
- package/src/observability/tests/otlp-converter.pbt.test.ts +77 -0
- package/src/observability/tests/otlp-converter.test.ts +157 -0
- package/src/observability/tests/otlp-json.test.ts +120 -0
- package/src/observability/tests/pusher.test.ts +75 -0
- package/src/observability/tests/redact.pbt.test.ts +71 -0
- package/src/observability/tests/redact.test.ts +37 -0
- package/src/observability/typed-refs.ts +137 -0
- package/src/observability/vitest.config.ts +8 -0
- package/src/ontology/analytics/matomo-binding.yaml +172 -0
- package/src/ontology/analytics/matomo-fleet.registry.yaml +26 -0
- package/src/ontology/analytics/messkanon.yaml +151 -0
- package/src/ontology/archetype-registry.ts +61 -0
- package/src/ontology/archetypes/components/brand-label.yaml +19 -0
- package/src/ontology/archetypes/components/copyright.yaml +19 -0
- package/src/ontology/archetypes/components/currency-aware-price-display.yaml +19 -0
- package/src/ontology/archetypes/components/currency-selector.yaml +19 -0
- package/src/ontology/archetypes/components/footer-promo.yaml +19 -0
- package/src/ontology/archetypes/components/lang-switcher.yaml +19 -0
- package/src/ontology/archetypes/components/layout.yaml +19 -0
- package/src/ontology/archetypes/components/live-photo.yaml +20 -0
- package/src/ontology/archetypes/components/material-credit.yaml +20 -0
- package/src/ontology/archetypes/components/media.yaml +21 -0
- package/src/ontology/archetypes/components/nachweis-card.yaml +34 -0
- package/src/ontology/archetypes/components/nachweis-detail.yaml +43 -0
- package/src/ontology/archetypes/components/nachweis-list.yaml +37 -0
- package/src/ontology/archetypes/components/nachweis-verify.yaml +31 -0
- package/src/ontology/archetypes/components/not-found.yaml +22 -0
- package/src/ontology/archetypes/components/offer-capacity.yaml +21 -0
- package/src/ontology/archetypes/components/passport-header.yaml +19 -0
- package/src/ontology/archetypes/components/passport-provenance.yaml +20 -0
- package/src/ontology/archetypes/components/passport-score-grid.yaml +20 -0
- package/src/ontology/archetypes/components/passport-star-map.yaml +20 -0
- package/src/ontology/archetypes/components/person-profile.yaml +20 -0
- package/src/ontology/archetypes/components/pulsar.yaml +20 -0
- package/src/ontology/archetypes/components/responsive-image.yaml +19 -0
- package/src/ontology/archetypes/components/scroll-to-top.yaml +22 -0
- package/src/ontology/archetypes/components/social-meta.yaml +20 -0
- package/src/ontology/archetypes/components/structured-data.yaml +19 -0
- package/src/ontology/archetypes/index.json +1003 -0
- package/src/ontology/archetypes/index.yaml +821 -0
- package/src/ontology/archetypes/sections/approach.yaml +23 -0
- package/src/ontology/archetypes/sections/article-list.yaml +30 -0
- package/src/ontology/archetypes/sections/audience-cards.yaml +30 -0
- package/src/ontology/archetypes/sections/breadcrumbs.yaml +21 -0
- package/src/ontology/archetypes/sections/chat-widget.yaml +37 -0
- package/src/ontology/archetypes/sections/comparison-cards.yaml +31 -0
- package/src/ontology/archetypes/sections/controlled-responsibility-block.yaml +34 -0
- package/src/ontology/archetypes/sections/credits.yaml +29 -0
- package/src/ontology/archetypes/sections/donation-card.yaml +53 -0
- package/src/ontology/archetypes/sections/donation-use.yaml +23 -0
- package/src/ontology/archetypes/sections/dynamic-status-block.yaml +35 -0
- package/src/ontology/archetypes/sections/faq-list.yaml +26 -0
- package/src/ontology/archetypes/sections/final-cta.yaml +53 -0
- package/src/ontology/archetypes/sections/hero-decision-card.yaml +65 -0
- package/src/ontology/archetypes/sections/hero.yaml +50 -0
- package/src/ontology/archetypes/sections/impact.yaml +23 -0
- package/src/ontology/archetypes/sections/markdown.yaml +43 -0
- package/src/ontology/archetypes/sections/mountain-journey.yaml +43 -0
- package/src/ontology/archetypes/sections/navigation.yaml +20 -0
- package/src/ontology/archetypes/sections/notausgang-block.yaml +41 -0
- package/src/ontology/archetypes/sections/open-source-registry.yaml +26 -0
- package/src/ontology/archetypes/sections/ownership-block.yaml +57 -0
- package/src/ontology/archetypes/sections/people.yaml +48 -0
- package/src/ontology/archetypes/sections/price-card.yaml +50 -0
- package/src/ontology/archetypes/sections/problem.yaml +23 -0
- package/src/ontology/archetypes/sections/send-message.yaml +63 -0
- package/src/ontology/archetypes/sections/service-metadata-block.yaml +40 -0
- package/src/ontology/archetypes/sections/social-proof.yaml +27 -0
- package/src/ontology/archetypes/sections/transparency.yaml +32 -0
- package/src/ontology/archetypes/sections/trust-strip.yaml +42 -0
- package/src/ontology/archetypes/sections/video-section.yaml +27 -0
- package/src/ontology/archetypes/sections/women.yaml +29 -0
- package/src/ontology/biome-token-projection.ts +229 -0
- package/src/ontology/biomes/check-concrete-blueprint.yaml +118 -0
- package/src/ontology/biomes/handwerk-material-warm.yaml +135 -0
- package/src/ontology/biomes/nonprofit-trust.yaml +123 -0
- package/src/ontology/blueprints/offer.yaml +39 -0
- package/src/ontology/blueprints/prompts/local-market-signal.md +13 -0
- package/src/ontology/blueprints/prompts/website-local-narrative.md +24 -0
- package/src/ontology/blueprints/ratgeber.yaml +62 -0
- package/src/ontology/blueprints/website-local.yaml +309 -0
- package/src/ontology/blueprints/website-service.yaml +84 -0
- package/src/ontology/capabilities/lead.submit.yaml +44 -0
- package/src/ontology/cosmic/index.ts +48 -0
- package/src/ontology/cosmic/moon-catalog.ts +173 -0
- package/src/ontology/cosmic/planet-catalog.ts +82 -0
- package/src/ontology/cosmic/star-catalog.ts +337 -0
- package/src/ontology/enums.ts +159 -0
- package/src/ontology/external-surfaces/index.ts +158 -0
- package/src/ontology/external-surfaces/jsonld-types.yaml +135 -0
- package/src/ontology/external-surfaces/sitemap-shape.yaml +6 -0
- package/src/ontology/external-surfaces/url-schema.yaml +95 -0
- package/src/ontology/growth/events/contact-email-click.yaml +21 -0
- package/src/ontology/growth/events/contact-form-submit.yaml +25 -0
- package/src/ontology/growth/events/contact-phone-click.yaml +21 -0
- package/src/ontology/growth/events/contact-route-click.yaml +21 -0
- package/src/ontology/growth/events/contact-submit.yaml +28 -0
- package/src/ontology/growth/events/contact-whatsapp-click.yaml +21 -0
- package/src/ontology/growth/events/cta-click.yaml +30 -0
- package/src/ontology/growth/events/donation-confirmed.yaml +32 -0
- package/src/ontology/growth/events/donation-form-start.yaml +21 -0
- package/src/ontology/growth/events/donation-form-submit.yaml +29 -0
- package/src/ontology/growth/events/donation-intent.yaml +29 -0
- package/src/ontology/growth/events/form-error.yaml +27 -0
- package/src/ontology/growth/events/form-start.yaml +21 -0
- package/src/ontology/growth/events/form-submit.yaml +21 -0
- package/src/ontology/growth/events/outbound-click.yaml +25 -0
- package/src/ontology/growth/events/page-view.yaml +24 -0
- package/src/ontology/growth/events/passport-view.yaml +28 -0
- package/src/ontology/growth/events/star-map-navigate.yaml +30 -0
- package/src/ontology/index.ts +165 -0
- package/src/ontology/manifest.ts +344 -0
- package/src/ontology/operations/index.ts +1 -0
- package/src/ontology/schemas/biome.ts +303 -0
- package/src/ontology/schemas/capability.ts +104 -0
- package/src/ontology/schemas/constellation.ts +114 -0
- package/src/ontology/schemas/dns-records.ts +53 -0
- package/src/ontology/schemas/index.ts +95 -0
- package/src/ontology/schemas/manifest-resolver.ts +123 -0
- package/src/ontology/schemas/page-entry.ts +213 -0
- package/src/ontology/schemas/section-archetype.ts +109 -0
- package/src/ontology/schemas/site-family.ts +98 -0
- package/src/ontology/schemas/system/growth.ts +80 -0
- package/src/ontology/schemas/system/integrations.ts +143 -0
- package/src/ontology/schemas/system/manifest.ts +492 -0
- package/src/ontology/schemas/system/page-output.ts +99 -0
- package/src/ontology/schemas/system/release.ts +58 -0
- package/src/ontology/schemas/system/text.ts +58 -0
- package/src/ontology/schemas/system.ts +53 -0
- package/src/ontology/shared-section-props/body-fragments.ts +182 -0
- package/src/ontology/shared-section-props/catalog.ts +195 -0
- package/src/ontology/shared-section-props/common.ts +178 -0
- package/src/ontology/shared-section-props/index.ts +34 -0
- package/src/ontology/shared-section-props/visual-header.ts +163 -0
- package/src/ontology/site-families/charity-donation-trust/cultural-rules.yaml +6 -0
- package/src/ontology/site-families/charity-donation-trust/family.yaml +44 -0
- package/src/ontology/site-families/charity-donation-trust/linguistic-rules.yaml +6 -0
- package/src/ontology/site-families/check-quality-operator/cultural-rules.yaml +6 -0
- package/src/ontology/site-families/check-quality-operator/family.yaml +40 -0
- package/src/ontology/site-families/check-quality-operator/linguistic-rules.yaml +6 -0
- package/src/ontology/site-families/handwerk-trust-engineering/cultural-rules.yaml +6 -0
- package/src/ontology/site-families/handwerk-trust-engineering/family.yaml +53 -0
- package/src/ontology/site-families/handwerk-trust-engineering/linguistic-rules.yaml +6 -0
- package/src/ontology/tests/catalogs.pbt.test.ts +89 -0
- package/src/ontology/tests/enums.test.ts +203 -0
- package/src/ontology/tests/sternsystem-owner.test.ts +63 -0
- package/src/ontology/vitest.config.ts +8 -0
- package/src/passport/dht-sign.test.ts +99 -0
- package/src/passport/dht-sign.ts +73 -0
- package/src/passport/identity-sign.test.ts +134 -0
- package/src/passport/identity-sign.ts +85 -0
- package/src/passport/index.ts +53 -0
- package/src/passport/key-rotate.ts +133 -0
- package/src/passport/schema.ts +198 -0
- package/src/passport/sign.ts +225 -0
- package/src/passport/tests/schema.pbt.test.ts +93 -0
- package/src/passport/tests/schema.test.ts +222 -0
- package/src/passport/vitest.config.ts +8 -0
- package/src/share/agent/api-catalog.ts +77 -0
- package/src/share/agent/capability.ts +68 -0
- package/src/share/agent/dns-aid.test.ts +74 -0
- package/src/share/agent/dns-aid.ts +53 -0
- package/src/share/agent/fleet-catalog.ts +149 -0
- package/src/share/agent/index.ts +29 -0
- package/src/share/agent/knowledge.ts +96 -0
- package/src/share/agent/manifest.ts +126 -0
- package/src/share/agent/mcp-card.ts +63 -0
- package/src/share/agent/omit-empty.ts +77 -0
- package/src/share/agent/openapi.ts +157 -0
- package/src/share/agent/proof.ts +61 -0
- package/src/share/attribution-display.ts +86 -0
- package/src/share/check-hints.ts +38 -0
- package/src/share/content/entity-id.ts +65 -0
- package/src/share/content/index.ts +16 -0
- package/src/share/content/merge.ts +86 -0
- package/src/share/content/resolve-field-path.ts +39 -0
- package/src/share/content/substitute-deep.ts +47 -0
- package/src/share/content-discipline/index.ts +14 -0
- package/src/share/content-discipline/parsers.ts +66 -0
- package/src/share/content-discipline/types.ts +118 -0
- package/src/share/content-reference.ts +333 -0
- package/src/share/counter-utils.ts +75 -0
- package/src/share/css-value-normalize.ts +48 -0
- package/src/share/dev-props-validator.ts +131 -0
- package/src/share/entitlement.ts +172 -0
- package/src/share/env.d.ts +73 -0
- package/src/share/feature-policy.ts +367 -0
- package/src/share/formula-eval.ts +364 -0
- package/src/share/fs/index.ts +96 -0
- package/src/share/i18n/localization.ts +57 -0
- package/src/share/image-provider.ts +314 -0
- package/src/share/index.ts +86 -0
- package/src/share/knowledge/claim.ts +129 -0
- package/src/share/knowledge/derivation.ts +55 -0
- package/src/share/knowledge/freshness.ts +127 -0
- package/src/share/knowledge/index.ts +23 -0
- package/src/share/knowledge/ledger.ts +222 -0
- package/src/share/knowledge/plan.ts +122 -0
- package/src/share/knowledge/source.ts +170 -0
- package/src/share/legal/translation-policy.ts +133 -0
- package/src/share/material-credits.ts +181 -0
- package/src/share/middleware/language-redirect.ts +49 -0
- package/src/share/offer-capacity.ts +149 -0
- package/src/share/onboarding-yaml/index.ts +162 -0
- package/src/share/page.ts +351 -0
- package/src/share/redirects.test.ts +62 -0
- package/src/share/redirects.ts +54 -0
- package/src/share/rfc0042-utils.ts +95 -0
- package/src/share/runtime-context.ts +70 -0
- package/src/share/schemas/claim-records.ts +69 -0
- package/src/share/schemas/claims.ts +150 -0
- package/src/share/schemas/effects.ts +212 -0
- package/src/share/schemas/features.ts +147 -0
- package/src/share/schemas/horizontal-align.ts +22 -0
- package/src/share/schemas/icon-color.ts +27 -0
- package/src/share/schemas/index.ts +43 -0
- package/src/share/schemas/live-photo.ts +53 -0
- package/src/share/schemas/material-credit.ts +292 -0
- package/src/share/schemas/media.ts +220 -0
- package/src/share/schemas/navigation.ts +74 -0
- package/src/share/schemas/page-base.ts +33 -0
- package/src/share/schemas/participant.ts +247 -0
- package/src/share/schemas/person.ts +24 -0
- package/src/share/schemas/print.ts +93 -0
- package/src/share/schemas/section-background.ts +106 -0
- package/src/share/schemas/section-body.ts +138 -0
- package/src/share/schemas/section-cards.ts +37 -0
- package/src/share/schemas/section-cta.ts +74 -0
- package/src/share/schemas/section-header.ts +77 -0
- package/src/share/schemas/section-image.ts +77 -0
- package/src/share/schemas/section-motion.ts +52 -0
- package/src/share/schemas/section-shell.ts +63 -0
- package/src/share/schemas/section-stats.ts +37 -0
- package/src/share/schemas/site-background.ts +78 -0
- package/src/share/schemas/standard-list-item.ts +38 -0
- package/src/share/scripts/external-links.ts +45 -0
- package/src/share/scripts/gsap-counter.ts +168 -0
- package/src/share/scripts/gsap-mountain-journey.ts +236 -0
- package/src/share/scripts/gsap-parallax.ts +50 -0
- package/src/share/scripts/gsap-reveal.ts +65 -0
- package/src/share/scripts/gsap-stagger.ts +54 -0
- package/src/share/scripts/index.ts +25 -0
- package/src/share/scripts/inline-number-animation.ts +120 -0
- package/src/share/scripts/lenis.ts +109 -0
- package/src/share/scripts/live-photos.ts +250 -0
- package/src/share/scripts/lordicon.ts +94 -0
- package/src/share/scripts/orchestrator.ts +138 -0
- package/src/share/scripts/scheduler.ts +53 -0
- package/src/share/scripts/video-player.ts +116 -0
- package/src/share/semantic/ai.ts +140 -0
- package/src/share/semantic/block-extraction.ts +52 -0
- package/src/share/semantic/block-extractors/index.ts +502 -0
- package/src/share/semantic/breadcrumbs.ts +120 -0
- package/src/share/semantic/build-page.ts +309 -0
- package/src/share/semantic/business-projection.ts +278 -0
- package/src/share/semantic/derived-prices-loader.ts +36 -0
- package/src/share/semantic/extract.ts +118 -0
- package/src/share/semantic/feed.ts +124 -0
- package/src/share/semantic/ids.ts +76 -0
- package/src/share/semantic/image-sitemap.ts +153 -0
- package/src/share/semantic/index.ts +36 -0
- package/src/share/semantic/jsonld/article.ts +47 -0
- package/src/share/semantic/jsonld/breadcrumb.ts +47 -0
- package/src/share/semantic/jsonld/collection-list.ts +30 -0
- package/src/share/semantic/jsonld/context.ts +46 -0
- package/src/share/semantic/jsonld/faq.ts +49 -0
- package/src/share/semantic/jsonld/initiative.ts +53 -0
- package/src/share/semantic/jsonld/organization.ts +101 -0
- package/src/share/semantic/jsonld/person.ts +71 -0
- package/src/share/semantic/jsonld/service.ts +80 -0
- package/src/share/semantic/jsonld/shared.ts +31 -0
- package/src/share/semantic/jsonld/types.ts +18 -0
- package/src/share/semantic/jsonld/webpage.ts +98 -0
- package/src/share/semantic/jsonld/website.ts +27 -0
- package/src/share/semantic/jsonld.ts +75 -0
- package/src/share/semantic/llms-policy.ts +74 -0
- package/src/share/semantic/llms.ts +394 -0
- package/src/share/semantic/markdown-hygiene.ts +68 -0
- package/src/share/semantic/markdown-twin-provenance.ts +242 -0
- package/src/share/semantic/models.ts +402 -0
- package/src/share/semantic/organization-profile.ts +160 -0
- package/src/share/semantic/output-projection.ts +131 -0
- package/src/share/semantic/page-builders/markdown-page.ts +84 -0
- package/src/share/semantic/page-markdown.ts +234 -0
- package/src/share/semantic/page-utils.ts +125 -0
- package/src/share/semantic/price-marker-resolver.ts +92 -0
- package/src/share/semantic/robots.ts +127 -0
- package/src/share/semantic/update-stamp.ts +136 -0
- package/src/share/shared-context.ts +177 -0
- package/src/share/string-utils.ts +19 -0
- package/src/share/tests/api-catalog.test.ts +97 -0
- package/src/share/tests/breadcrumbs.test.ts +126 -0
- package/src/share/tests/build-page-price-markers.test.ts +160 -0
- package/src/share/tests/capability.test.ts +107 -0
- package/src/share/tests/claim.test.ts +90 -0
- package/src/share/tests/content-ref-index.test.ts +328 -0
- package/src/share/tests/create-dev-props-validator.test.ts +38 -0
- package/src/share/tests/derivation.test.ts +39 -0
- package/src/share/tests/dev-props-validator.test.ts +88 -0
- package/src/share/tests/fleet-catalog.test.ts +134 -0
- package/src/share/tests/formula-eval.test.ts +619 -0
- package/src/share/tests/freshness.test.ts +60 -0
- package/src/share/tests/hero-section-numbering-parity.test.ts +51 -0
- package/src/share/tests/image-sitemap.test.ts +101 -0
- package/src/share/tests/jsonld-service.test.ts +282 -0
- package/src/share/tests/knowledge.test.ts +86 -0
- package/src/share/tests/ledger.test.ts +125 -0
- package/src/share/tests/llms-0789.test.ts +63 -0
- package/src/share/tests/manifest.test.ts +100 -0
- package/src/share/tests/markdown-twin-provenance.test.ts +108 -0
- package/src/share/tests/mcp-card.test.ts +57 -0
- package/src/share/tests/onboarding-yaml.test.ts +111 -0
- package/src/share/tests/ontology-role-by-cosmic-name.test.ts +28 -0
- package/src/share/tests/openapi.test.ts +116 -0
- package/src/share/tests/organization-jsonld.test.ts +74 -0
- package/src/share/tests/output-projection.test.ts +61 -0
- package/src/share/tests/plan.test.ts +97 -0
- package/src/share/tests/price-marker-resolver.test.ts +141 -0
- package/src/share/tests/proof.test.ts +72 -0
- package/src/share/tests/resolve-field-path.test.ts +52 -0
- package/src/share/tests/source.test.ts +81 -0
- package/src/share/tests/substitute-deep.test.ts +65 -0
- package/src/share/tests/text-normalize.pbt.test.ts +143 -0
- package/src/share/tests/text-normalize.test.ts +285 -0
- package/src/share/tests/twin-path.test.ts +43 -0
- package/src/share/text-normalize.ts +548 -0
- package/src/share/text-position.ts +19 -0
- package/src/share/types/hls-js-light.d.ts +16 -0
- package/src/share/visibility.ts +167 -0
- package/src/share/vitest.config.ts +8 -0
- package/src/share/wrap-inline-numbers.ts +139 -0
- package/src/surface/blueprint-schema.ts +314 -0
- package/src/surface/blueprint-types.ts +256 -0
- package/src/surface/blueprint.ts +300 -0
- package/src/surface/breaker.ts +57 -0
- package/src/surface/decision-composer.ts +156 -0
- package/src/surface/eligibility.ts +372 -0
- package/src/surface/evidence-records.ts +97 -0
- package/src/surface/fleet.ts +60 -0
- package/src/surface/geo.ts +170 -0
- package/src/surface/governance.ts +146 -0
- package/src/surface/index.ts +216 -0
- package/src/surface/io/index.ts +18 -0
- package/src/surface/io/surface-module-context-io.ts +60 -0
- package/src/surface/io/visibility-outcomes-io.ts +46 -0
- package/src/surface/module-context.ts +120 -0
- package/src/surface/substance.ts +161 -0
- package/src/surface/tests/eligibility.pbt.test.ts +137 -0
- package/src/surface/tests/eligibility.test.ts +302 -0
- package/src/surface/types.ts +289 -0
- package/src/surface/visibility.ts +57 -0
- package/src/surface/vitest.config.ts +8 -0
- 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
|
+
}
|