@warpgogol/werkstatt-shared 0.12.0 → 0.14.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 +21 -69
- package/src/integration/funnel.ts +78 -1
- package/src/integration/index.ts +19 -2
- package/src/integration/lagebild-ingress.ts +169 -0
- package/src/integration/orchestration.ts +7 -13
- package/src/integration/port-barrel.ts +9 -28
- package/src/integration/tests/funnel-bridge.test.ts +7 -7
- package/src/integration/tests/lagebild-ingress.test.ts +118 -0
- package/src/observability/typed-refs.ts +1 -1
- package/src/ontology/analytics/matomo-fleet.registry.yaml +1 -1
- package/src/ontology/archetypes/index.json +12 -9
- package/src/ontology/archetypes/index.yaml +26 -10
- package/src/ontology/blueprints/website-local.yaml +11 -11
- package/src/ontology/blueprints/website-service.yaml +20 -20
- package/src/ontology/capabilities/lead.submit.yaml +2 -2
- package/src/ontology/enums.ts +2 -2
- package/src/ontology/external-surfaces/jsonld-types.yaml +14 -14
- package/src/ontology/external-surfaces/url-schema.yaml +1 -1
- package/src/ontology/index.ts +2 -0
- package/src/ontology/schemas/index.ts +7 -2
- package/src/ontology/schemas/system/integrations.ts +1 -1
- package/src/ontology/schemas/system/manifest.ts +38 -1
- package/src/ontology/schemas/system.ts +7 -2
- package/src/ontology/shared-section-props/common.ts +11 -0
- package/src/ontology/shared-section-props/visual-header.ts +1 -1
- package/src/share/agent/api-catalog.ts +5 -0
- package/src/share/agent/ard-catalog.ts +14 -0
- package/src/share/agent/index.ts +2 -0
- package/src/share/agent/manifest.ts +25 -2
- package/src/share/agent/mcp-card.ts +4 -0
- package/src/share/agent/openapi.ts +78 -0
- package/src/share/agent/search.test.ts +188 -0
- package/src/share/agent/search.ts +164 -0
- package/src/share/env.d.ts +0 -4
- package/src/share/schemas/effects.ts +16 -0
- package/src/share/schemas/media.ts +4 -0
- package/src/share/schemas/section-shell.ts +2 -1
- package/src/share/scripts/external-link-qr.ts +9 -1
- package/src/share/scripts/gsap-counter.ts +2 -0
- package/src/share/scripts/inline-number-animation.ts +2 -0
- package/src/share/scripts/live-photos.ts +2 -0
- package/src/share/scripts/video-player.test.ts +210 -0
- package/src/share/scripts/video-player.ts +73 -13
- package/src/share/semantic/extract.ts +25 -1
- package/src/share/semantic/tests/split-sentences.test.ts +57 -0
- package/src/share/tests/manifest.test.ts +51 -0
- package/src/share/tests/openapi.test.ts +30 -0
- package/src/share/types/qrcode-browser.d.ts +18 -0
- package/src/share/utility-registry.yaml +2 -2
- package/src/integration/crm-buffer.ts +0 -491
- package/src/integration-adapter-supabase-crm/adapter.ts +0 -437
- package/src/integration-adapter-supabase-crm/client.ts +0 -605
- package/src/integration-adapter-supabase-crm/index.ts +0 -28
- package/src/integration-adapter-supabase-crm/pipedrive-sync-target.ts +0 -482
- package/src/integration-adapter-supabase-crm/supabase-rest.ts +0 -89
- package/src/integration-adapter-supabase-crm/tenant-registry.ts +0 -334
- package/src/integration-adapter-supabase-crm/tests/change-balance.pbt.test.ts +0 -83
- package/src/integration-adapter-supabase-crm/tests/funnel-persistence.test.ts +0 -409
- package/src/integration-adapter-supabase-crm/tests/lifecycle-sync.test.ts +0 -288
- package/src/integration-adapter-supabase-crm/tests/stage-map.test.ts +0 -80
- package/src/integration-adapter-supabase-crm/vitest.config.ts +0 -8
- package/src/integration-adapter-supabase-crm/worker.ts +0 -223
- package/src/ontology/archetypes/components/currency-selector.yaml +0 -19
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@warpgogol/werkstatt-shared",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -66,22 +66,6 @@
|
|
|
66
66
|
"types": "./src/integration/index.ts",
|
|
67
67
|
"default": "./src/integration/index.ts"
|
|
68
68
|
},
|
|
69
|
-
"./integration-adapter-supabase-crm": {
|
|
70
|
-
"types": "./src/integration-adapter-supabase-crm/index.ts",
|
|
71
|
-
"default": "./src/integration-adapter-supabase-crm/index.ts"
|
|
72
|
-
},
|
|
73
|
-
"./integration-adapter-supabase-crm/tenant-registry": {
|
|
74
|
-
"types": "./src/integration-adapter-supabase-crm/tenant-registry.ts",
|
|
75
|
-
"default": "./src/integration-adapter-supabase-crm/tenant-registry.ts"
|
|
76
|
-
},
|
|
77
|
-
"./integration-adapter-supabase-crm/worker": {
|
|
78
|
-
"types": "./src/integration-adapter-supabase-crm/worker.ts",
|
|
79
|
-
"default": "./src/integration-adapter-supabase-crm/worker.ts"
|
|
80
|
-
},
|
|
81
|
-
"./integration/crm-buffer": {
|
|
82
|
-
"types": "./src/integration/crm-buffer.ts",
|
|
83
|
-
"default": "./src/integration/crm-buffer.ts"
|
|
84
|
-
},
|
|
85
69
|
"./integration/port": {
|
|
86
70
|
"types": "./src/integration/port.ts",
|
|
87
71
|
"default": "./src/integration/port.ts"
|
|
@@ -414,30 +398,6 @@
|
|
|
414
398
|
"types": "./src/integration/vitest.config.ts",
|
|
415
399
|
"default": "./src/integration/vitest.config.ts"
|
|
416
400
|
},
|
|
417
|
-
"./integration-adapter-supabase-crm/adapter": {
|
|
418
|
-
"types": "./src/integration-adapter-supabase-crm/adapter.ts",
|
|
419
|
-
"default": "./src/integration-adapter-supabase-crm/adapter.ts"
|
|
420
|
-
},
|
|
421
|
-
"./integration-adapter-supabase-crm/client": {
|
|
422
|
-
"types": "./src/integration-adapter-supabase-crm/client.ts",
|
|
423
|
-
"default": "./src/integration-adapter-supabase-crm/client.ts"
|
|
424
|
-
},
|
|
425
|
-
"./integration-adapter-supabase-crm/index": {
|
|
426
|
-
"types": "./src/integration-adapter-supabase-crm/index.ts",
|
|
427
|
-
"default": "./src/integration-adapter-supabase-crm/index.ts"
|
|
428
|
-
},
|
|
429
|
-
"./integration-adapter-supabase-crm/pipedrive-sync-target": {
|
|
430
|
-
"types": "./src/integration-adapter-supabase-crm/pipedrive-sync-target.ts",
|
|
431
|
-
"default": "./src/integration-adapter-supabase-crm/pipedrive-sync-target.ts"
|
|
432
|
-
},
|
|
433
|
-
"./integration-adapter-supabase-crm/supabase-rest": {
|
|
434
|
-
"types": "./src/integration-adapter-supabase-crm/supabase-rest.ts",
|
|
435
|
-
"default": "./src/integration-adapter-supabase-crm/supabase-rest.ts"
|
|
436
|
-
},
|
|
437
|
-
"./integration-adapter-supabase-crm/vitest.config": {
|
|
438
|
-
"types": "./src/integration-adapter-supabase-crm/vitest.config.ts",
|
|
439
|
-
"default": "./src/integration-adapter-supabase-crm/vitest.config.ts"
|
|
440
|
-
},
|
|
441
401
|
"./observability/conventions": {
|
|
442
402
|
"types": "./src/observability/conventions.ts",
|
|
443
403
|
"default": "./src/observability/conventions.ts"
|
|
@@ -670,6 +630,10 @@
|
|
|
670
630
|
"types": "./src/share/agent/proof.ts",
|
|
671
631
|
"default": "./src/share/agent/proof.ts"
|
|
672
632
|
},
|
|
633
|
+
"./share/agent/search": {
|
|
634
|
+
"types": "./src/share/agent/search.ts",
|
|
635
|
+
"default": "./src/share/agent/search.ts"
|
|
636
|
+
},
|
|
673
637
|
"./share/content/entity-id": {
|
|
674
638
|
"types": "./src/share/content/entity-id.ts",
|
|
675
639
|
"default": "./src/share/content/entity-id.ts"
|
|
@@ -1126,22 +1090,6 @@
|
|
|
1126
1090
|
"types": "./src/integration/tests/sharding.test.ts",
|
|
1127
1091
|
"default": "./src/integration/tests/sharding.test.ts"
|
|
1128
1092
|
},
|
|
1129
|
-
"./integration-adapter-supabase-crm/tests/change-balance.pbt.test": {
|
|
1130
|
-
"types": "./src/integration-adapter-supabase-crm/tests/change-balance.pbt.test.ts",
|
|
1131
|
-
"default": "./src/integration-adapter-supabase-crm/tests/change-balance.pbt.test.ts"
|
|
1132
|
-
},
|
|
1133
|
-
"./integration-adapter-supabase-crm/tests/funnel-persistence.test": {
|
|
1134
|
-
"types": "./src/integration-adapter-supabase-crm/tests/funnel-persistence.test.ts",
|
|
1135
|
-
"default": "./src/integration-adapter-supabase-crm/tests/funnel-persistence.test.ts"
|
|
1136
|
-
},
|
|
1137
|
-
"./integration-adapter-supabase-crm/tests/lifecycle-sync.test": {
|
|
1138
|
-
"types": "./src/integration-adapter-supabase-crm/tests/lifecycle-sync.test.ts",
|
|
1139
|
-
"default": "./src/integration-adapter-supabase-crm/tests/lifecycle-sync.test.ts"
|
|
1140
|
-
},
|
|
1141
|
-
"./integration-adapter-supabase-crm/tests/stage-map.test": {
|
|
1142
|
-
"types": "./src/integration-adapter-supabase-crm/tests/stage-map.test.ts",
|
|
1143
|
-
"default": "./src/integration-adapter-supabase-crm/tests/stage-map.test.ts"
|
|
1144
|
-
},
|
|
1145
1093
|
"./observability/tests/conventions.test": {
|
|
1146
1094
|
"types": "./src/observability/tests/conventions.test.ts",
|
|
1147
1095
|
"default": "./src/observability/tests/conventions.test.ts"
|
|
@@ -1349,6 +1297,10 @@
|
|
|
1349
1297
|
"./share/scripts": {
|
|
1350
1298
|
"types": "./src/share/scripts/index.ts",
|
|
1351
1299
|
"default": "./src/share/scripts/index.ts"
|
|
1300
|
+
},
|
|
1301
|
+
"./integration/lagebild-ingress": {
|
|
1302
|
+
"types": "./src/integration/lagebild-ingress.ts",
|
|
1303
|
+
"default": "./src/integration/lagebild-ingress.ts"
|
|
1352
1304
|
}
|
|
1353
1305
|
},
|
|
1354
1306
|
"scripts": {
|
|
@@ -1368,18 +1320,18 @@
|
|
|
1368
1320
|
],
|
|
1369
1321
|
"dependencies": {
|
|
1370
1322
|
"@lordicon/element": "^2.3.1",
|
|
1323
|
+
"@sindresorhus/slugify": "^3.0.0",
|
|
1371
1324
|
"@warpgogol/werkstatt-engine": "*",
|
|
1372
1325
|
"ajv": "^8.20.0",
|
|
1373
1326
|
"bs58": "^6.0.0",
|
|
1374
|
-
"@sindresorhus/slugify": "^3.0.0",
|
|
1375
1327
|
"cyrillic-to-translit-js": "^3.2.1",
|
|
1376
1328
|
"expr-eval": "^2.0.2",
|
|
1377
1329
|
"github-slugger": "^2.0.0",
|
|
1378
|
-
"
|
|
1379
|
-
"
|
|
1330
|
+
"gray-matter": "^4.0.3",
|
|
1331
|
+
"hls.js": "^1.7.1",
|
|
1332
|
+
"lenis": "^1.3.26",
|
|
1380
1333
|
"plyr": "^3.8.4",
|
|
1381
1334
|
"qrcode": "^1.5.4",
|
|
1382
|
-
"gray-matter": "^4.0.3",
|
|
1383
1335
|
"yaml": "^2.9.0",
|
|
1384
1336
|
"zod": "^4.4.3"
|
|
1385
1337
|
},
|
|
@@ -1387,18 +1339,18 @@
|
|
|
1387
1339
|
"@syrokomskyi/axiom-study": "link:../../../pipelines/packages/axiom/axiom-study"
|
|
1388
1340
|
},
|
|
1389
1341
|
"devDependencies": {
|
|
1390
|
-
"@astrojs/markdown-remark": "^7.
|
|
1391
|
-
"@cloudflare/workers-types": "^5.
|
|
1392
|
-
"@types/node": "^
|
|
1342
|
+
"@astrojs/markdown-remark": "^7.2.4",
|
|
1343
|
+
"@cloudflare/workers-types": "^5.20260823.1",
|
|
1344
|
+
"@types/node": "^26.2.0",
|
|
1345
|
+
"@types/qrcode": "^1.5.6",
|
|
1393
1346
|
"@warpgogol/werkstatt-engine": "*",
|
|
1394
|
-
"astro": "^7.2.
|
|
1395
|
-
"eslint": "^10.
|
|
1347
|
+
"astro": "^7.2.4",
|
|
1348
|
+
"eslint": "^10.9.0",
|
|
1396
1349
|
"fast-check": "^4.9.0",
|
|
1397
1350
|
"gsap": "^3.15.0",
|
|
1398
|
-
"@types/qrcode": "^1.5.5",
|
|
1399
1351
|
"typescript": "^6.0.3",
|
|
1400
|
-
"typescript-eslint": "8.
|
|
1401
|
-
"vitest": "^4.1.
|
|
1352
|
+
"typescript-eslint": "8.68.0",
|
|
1353
|
+
"vitest": "^4.1.11"
|
|
1402
1354
|
},
|
|
1403
1355
|
"packageManager": "pnpm@11.10.0"
|
|
1404
1356
|
}
|
|
@@ -8,12 +8,13 @@ types + constants + pure helpers — no I/O, no astro:env, no vendor SDK.</purpo
|
|
|
8
8
|
<non-goals>
|
|
9
9
|
<item>Do not let a chat vendor own the graph — UChat requests transitions; this module is the authority.</item>
|
|
10
10
|
<item>Do not reference Make.com or carry any legacy UChat goto/stage string as architecture.</item>
|
|
11
|
-
<item>Do not import astro:env
|
|
11
|
+
<item>Do not import astro:env or a vendor SDK — callers inject persistence.</item>
|
|
12
12
|
</non-goals>
|
|
13
13
|
</MODULE_CONTRACT>
|
|
14
14
|
<CHANGE_SUMMARY>
|
|
15
15
|
<item>RFC-0188 Phase 2 (contract): initial funnel stage/event/transition contracts.</item>
|
|
16
16
|
<item>RFC-0219: add FUNNEL_SYSTEM_TRIGGERS, FunnelTransitionTrigger, FUNNEL_TRANSITION_TRIGGERS — trigger overlay for the state-chart generator.</item>
|
|
17
|
+
<item>Cleanup: move BUFFER_DEAL_STAGES, FUNNEL_STAGE_TO_BUFFER_STAGE, bridgeFunnelStage, SYNC_OUTBOX_STATUSES, SYNC_OUTBOX_OPS from deleted crm-buffer.ts into this module.</item>
|
|
17
18
|
</CHANGE_SUMMARY>
|
|
18
19
|
*/
|
|
19
20
|
|
|
@@ -377,3 +378,79 @@ export function scanForMakeComReferences(text: string): Array<{ line: number; ma
|
|
|
377
378
|
});
|
|
378
379
|
return hits;
|
|
379
380
|
}
|
|
381
|
+
|
|
382
|
+
// ---------------------------------------------------------------------------
|
|
383
|
+
// Generic deal stage bridge (formerly in crm-buffer.ts — RFC-0188 Phase 3)
|
|
384
|
+
// ---------------------------------------------------------------------------
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Closed catalog of deal funnel stages. Order matters — later stages come after
|
|
388
|
+
* earlier ones. Mirrors the standard Pipedrive pipeline stages.
|
|
389
|
+
*/
|
|
390
|
+
export const BUFFER_DEAL_STAGES = [
|
|
391
|
+
"new",
|
|
392
|
+
"contacted",
|
|
393
|
+
"qualified",
|
|
394
|
+
"proposal",
|
|
395
|
+
"negotiation",
|
|
396
|
+
"won",
|
|
397
|
+
"lost",
|
|
398
|
+
] as const;
|
|
399
|
+
|
|
400
|
+
export type BufferDealStage = (typeof BUFFER_DEAL_STAGES)[number];
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* RFC-0188 keeps the precise, platform-owned VisitorFunnelStage on the deal
|
|
404
|
+
* (`funnel_stage`) while bridging it down to the generic BufferDealStage the
|
|
405
|
+
* Pipedrive sync worker already understands. Exhaustive by construction.
|
|
406
|
+
*/
|
|
407
|
+
export const FUNNEL_STAGE_TO_BUFFER_STAGE: Record<VisitorFunnelStage, BufferDealStage> = {
|
|
408
|
+
new_session: "new",
|
|
409
|
+
privacy_acknowledged: "new",
|
|
410
|
+
intent_selected: "contacted",
|
|
411
|
+
organization_selected: "contacted",
|
|
412
|
+
qualification_priority: "qualified",
|
|
413
|
+
qualification_company: "qualified",
|
|
414
|
+
qualification_service: "qualified",
|
|
415
|
+
qualification_region: "qualified",
|
|
416
|
+
offer_presented: "proposal",
|
|
417
|
+
payment_pending: "proposal",
|
|
418
|
+
payment_confirmed: "negotiation",
|
|
419
|
+
start_choice_pending: "negotiation",
|
|
420
|
+
start_deferred: "negotiation",
|
|
421
|
+
buyer_type_pending: "negotiation",
|
|
422
|
+
b2b_start_consent_pending: "negotiation",
|
|
423
|
+
b2c_withdrawal_consent_pending: "negotiation",
|
|
424
|
+
start_approved: "negotiation",
|
|
425
|
+
legal_data_requested: "negotiation",
|
|
426
|
+
materials_requested: "negotiation",
|
|
427
|
+
production_ready: "negotiation",
|
|
428
|
+
change_balance_checked: "contacted",
|
|
429
|
+
change_payment_pending: "proposal",
|
|
430
|
+
change_description_requested: "negotiation",
|
|
431
|
+
operator_review: "negotiation",
|
|
432
|
+
won: "won",
|
|
433
|
+
lost: "lost",
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
/** Bridge a canonical funnel stage down to the generic stage the sync worker maps. */
|
|
437
|
+
export function bridgeFunnelStage(stage: VisitorFunnelStage): BufferDealStage {
|
|
438
|
+
return FUNNEL_STAGE_TO_BUFFER_STAGE[stage];
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// ---------------------------------------------------------------------------
|
|
442
|
+
// Sync outbox statuses (formerly in crm-buffer.ts)
|
|
443
|
+
// ---------------------------------------------------------------------------
|
|
444
|
+
|
|
445
|
+
export const SYNC_OUTBOX_STATUSES = ["pending", "processing", "done", "failed", "dead"] as const;
|
|
446
|
+
export type SyncOutboxStatus = (typeof SYNC_OUTBOX_STATUSES)[number];
|
|
447
|
+
|
|
448
|
+
export const SYNC_OUTBOX_OPS = [
|
|
449
|
+
"upsert_contact",
|
|
450
|
+
"upsert_deal",
|
|
451
|
+
"update_deal_stage",
|
|
452
|
+
"upsert_organization",
|
|
453
|
+
"upsert_subscription",
|
|
454
|
+
"upsert_invoice",
|
|
455
|
+
] as const;
|
|
456
|
+
export type SyncOutboxOp = (typeof SYNC_OUTBOX_OPS)[number];
|
package/src/integration/index.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/*
|
|
2
2
|
<MODULE_CONTRACT>
|
|
3
3
|
<purpose>
|
|
4
|
-
RFC-0168: Integration Port barrel. Re-exports types from port.ts,
|
|
4
|
+
RFC-0168: Integration Port barrel. Re-exports types from port.ts,
|
|
5
5
|
funnel.ts, lifecycle.ts, sharding.ts, dispatch.ts, qstash.ts, and runtime
|
|
6
6
|
orchestration (registries + fan-out) from orchestration.ts. Type-only consumers
|
|
7
|
-
|
|
7
|
+
import from `./port-barrel.ts` (`@warpgogol/werkstatt-shared/share/integration/port`)
|
|
8
8
|
to avoid transitively pulling in adapter implementations; consumers needing
|
|
9
9
|
runtime logic import from here — the orchestration module is re-exported transparently.
|
|
10
10
|
</purpose>
|
|
@@ -54,3 +54,20 @@ export {
|
|
|
54
54
|
consumeIntegrationBatch,
|
|
55
55
|
upsertLead,
|
|
56
56
|
} from "./orchestration.ts";
|
|
57
|
+
|
|
58
|
+
// Lagebild ingress client (POST /v1/ingress — WebsiteIngress contract v1)
|
|
59
|
+
export {
|
|
60
|
+
submitIngress,
|
|
61
|
+
buildIdempotencyKey,
|
|
62
|
+
LAGEBILD_INGRESS_CONTRACT_VERSION,
|
|
63
|
+
type LagebildIngressConfig,
|
|
64
|
+
type IngressSubmitInput,
|
|
65
|
+
type WebsiteIngressPayload,
|
|
66
|
+
type IngressResult,
|
|
67
|
+
type IngressInteractionKind,
|
|
68
|
+
type IdentityClaimType,
|
|
69
|
+
type IdentityClaim,
|
|
70
|
+
type IngressOrigin,
|
|
71
|
+
type ExplicitPolicyAssertion,
|
|
72
|
+
type PolicyAssertionKind,
|
|
73
|
+
} from "./lagebild-ingress.ts";
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>Thin HTTP client for Lagebild API POST /v1/ingress. Werkstatt does NOT
|
|
4
|
+
recreate Lagebild semantics locally — it only calls the public WebsiteIngress v1
|
|
5
|
+
contract. The client builds the payload from form data, sends it with a service
|
|
6
|
+
principal API key, and returns the acceptance result. Replay protection is
|
|
7
|
+
built-in via a stable idempotency_key (derived from submission_id + tenant).</purpose>
|
|
8
|
+
<non-goals>
|
|
9
|
+
<item>Do not implement Lagebild domain logic — only the HTTP call.</item>
|
|
10
|
+
<item>Do not persist anything locally — the API owns canonical state.</item>
|
|
11
|
+
<item>Do not retry on non-2xx — the caller decides retry strategy.</item>
|
|
12
|
+
</non-goals>
|
|
13
|
+
</MODULE_CONTRACT>
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export const LAGEBILD_INGRESS_CONTRACT_VERSION = 1 as const;
|
|
17
|
+
|
|
18
|
+
export type IngressInteractionKind =
|
|
19
|
+
| "inquiry"
|
|
20
|
+
| "contact_message"
|
|
21
|
+
| "callback_request"
|
|
22
|
+
| "appointment_request"
|
|
23
|
+
| "other";
|
|
24
|
+
|
|
25
|
+
export type IdentityClaimType =
|
|
26
|
+
| "person_name"
|
|
27
|
+
| "organization_name"
|
|
28
|
+
| "email"
|
|
29
|
+
| "phone"
|
|
30
|
+
| "website"
|
|
31
|
+
| "postal_address"
|
|
32
|
+
| "other";
|
|
33
|
+
|
|
34
|
+
export interface IdentityClaim {
|
|
35
|
+
readonly claim_type: IdentityClaimType;
|
|
36
|
+
readonly value: string;
|
|
37
|
+
readonly label?: string | null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface IngressOrigin {
|
|
41
|
+
readonly page_uri?: string | null;
|
|
42
|
+
readonly referrer_uri?: string | null;
|
|
43
|
+
readonly locale?: string | null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type PolicyAssertionKind = "consent" | "objection" | "preference";
|
|
47
|
+
|
|
48
|
+
export interface ExplicitPolicyAssertion {
|
|
49
|
+
readonly kind: PolicyAssertionKind;
|
|
50
|
+
readonly purpose: string;
|
|
51
|
+
readonly channel?: string | null;
|
|
52
|
+
readonly value: boolean;
|
|
53
|
+
readonly statement_ref?: string | null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface WebsiteIngressPayload {
|
|
57
|
+
readonly contract_version: typeof LAGEBILD_INGRESS_CONTRACT_VERSION;
|
|
58
|
+
readonly tenant_id: string;
|
|
59
|
+
readonly source_system_id: string;
|
|
60
|
+
readonly submission_id: string;
|
|
61
|
+
readonly idempotency_key: string;
|
|
62
|
+
readonly occurred_at: string;
|
|
63
|
+
readonly interaction_kind: IngressInteractionKind;
|
|
64
|
+
readonly identity_claims: readonly IdentityClaim[];
|
|
65
|
+
readonly message?: string | null;
|
|
66
|
+
readonly origin?: IngressOrigin;
|
|
67
|
+
readonly explicit_policy_assertions?: readonly ExplicitPolicyAssertion[];
|
|
68
|
+
readonly correlation_id?: string | null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface IngressResult {
|
|
72
|
+
readonly submission_id: string | null;
|
|
73
|
+
readonly accepted: boolean;
|
|
74
|
+
readonly is_replay: boolean;
|
|
75
|
+
readonly rejection_class?: string | null;
|
|
76
|
+
readonly correlation_id?: string | null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface LagebildIngressConfig {
|
|
80
|
+
readonly apiUrl: string;
|
|
81
|
+
readonly apiKey: string;
|
|
82
|
+
readonly tenantId: string;
|
|
83
|
+
readonly sourceSystemId: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface IngressSubmitInput {
|
|
87
|
+
readonly submissionId: string;
|
|
88
|
+
readonly idempotencyKey: string;
|
|
89
|
+
readonly interactionKind: IngressInteractionKind;
|
|
90
|
+
readonly identityClaims: readonly IdentityClaim[];
|
|
91
|
+
readonly message?: string | null;
|
|
92
|
+
readonly origin?: IngressOrigin;
|
|
93
|
+
readonly explicitPolicyAssertions?: readonly ExplicitPolicyAssertion[];
|
|
94
|
+
readonly correlationId?: string | null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Submit a website ingress event to the Lagebild API.
|
|
99
|
+
* Returns the acceptance result. Does not throw on non-2xx — returns
|
|
100
|
+
* a result with accepted=false so the caller can decide retry strategy.
|
|
101
|
+
*/
|
|
102
|
+
export async function submitIngress(
|
|
103
|
+
config: LagebildIngressConfig,
|
|
104
|
+
input: IngressSubmitInput,
|
|
105
|
+
): Promise<IngressResult> {
|
|
106
|
+
const payload: WebsiteIngressPayload = {
|
|
107
|
+
contract_version: LAGEBILD_INGRESS_CONTRACT_VERSION,
|
|
108
|
+
tenant_id: config.tenantId,
|
|
109
|
+
source_system_id: config.sourceSystemId,
|
|
110
|
+
submission_id: input.submissionId,
|
|
111
|
+
idempotency_key: input.idempotencyKey,
|
|
112
|
+
occurred_at: new Date().toISOString(),
|
|
113
|
+
interaction_kind: input.interactionKind,
|
|
114
|
+
identity_claims: input.identityClaims,
|
|
115
|
+
...(input.message != null ? { message: input.message } : {}),
|
|
116
|
+
...(input.origin != null ? { origin: input.origin } : {}),
|
|
117
|
+
...(input.explicitPolicyAssertions != null
|
|
118
|
+
? { explicit_policy_assertions: input.explicitPolicyAssertions }
|
|
119
|
+
: {}),
|
|
120
|
+
...(input.correlationId != null ? { correlation_id: input.correlationId } : {}),
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const url = config.apiUrl.replace(/\/$/, "") + "/v1/ingress";
|
|
124
|
+
|
|
125
|
+
let response: Response;
|
|
126
|
+
try {
|
|
127
|
+
response = await fetch(url, {
|
|
128
|
+
method: "POST",
|
|
129
|
+
headers: {
|
|
130
|
+
"Content-Type": "application/json",
|
|
131
|
+
Authorization: "Bearer " + config.apiKey,
|
|
132
|
+
},
|
|
133
|
+
body: JSON.stringify(payload),
|
|
134
|
+
});
|
|
135
|
+
} catch {
|
|
136
|
+
return {
|
|
137
|
+
submission_id: input.submissionId,
|
|
138
|
+
accepted: false,
|
|
139
|
+
is_replay: false,
|
|
140
|
+
rejection_class: "network_error",
|
|
141
|
+
correlation_id: input.correlationId ?? null,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
let body: unknown;
|
|
146
|
+
try {
|
|
147
|
+
body = await response.json();
|
|
148
|
+
} catch {
|
|
149
|
+
body = {};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const result = body as Partial<IngressResult>;
|
|
153
|
+
return {
|
|
154
|
+
submission_id: result.submission_id ?? input.submissionId,
|
|
155
|
+
accepted: result.accepted ?? response.ok,
|
|
156
|
+
is_replay: result.is_replay ?? false,
|
|
157
|
+
rejection_class: result.rejection_class ?? null,
|
|
158
|
+
correlation_id: result.correlation_id ?? input.correlationId ?? null,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Build a stable idempotency key from tenant + submission ID.
|
|
164
|
+
* This ensures retries within the same submission are deduplicated
|
|
165
|
+
* by the Lagebild API.
|
|
166
|
+
*/
|
|
167
|
+
export function buildIdempotencyKey(tenantId: string, submissionId: string): string {
|
|
168
|
+
return tenantId + ":" + submissionId;
|
|
169
|
+
}
|
|
@@ -31,8 +31,9 @@ import type {
|
|
|
31
31
|
IntegrationSecrets,
|
|
32
32
|
Lead,
|
|
33
33
|
LeadMessage,
|
|
34
|
+
EXECUTION_MODES,
|
|
34
35
|
} from "./port.ts";
|
|
35
|
-
import { DESTINATION_KINDS,
|
|
36
|
+
import { DESTINATION_KINDS, eventToLeadMessage } from "./port.ts";
|
|
36
37
|
import { LifecycleEventPayloadSchema } from "./lifecycle.ts";
|
|
37
38
|
import { z } from "zod";
|
|
38
39
|
|
|
@@ -171,20 +172,14 @@ export const DESTINATION_ADAPTERS: readonly DestinationAdapter[] = [pipedriveDes
|
|
|
171
172
|
* `extraAdapters`; the validator checks vendor + secrets against these catalogs.
|
|
172
173
|
*/
|
|
173
174
|
export const EXTERNAL_DESTINATION_VENDORS: Readonly<Record<DestinationKind, readonly string[]>> = {
|
|
174
|
-
crm: [
|
|
175
|
+
crm: [],
|
|
175
176
|
calendar: [],
|
|
176
177
|
email: [],
|
|
177
178
|
scheduler: [],
|
|
178
179
|
};
|
|
179
180
|
|
|
180
181
|
/** Per `(kind, vendor)` → required secret names for externally-implemented adapters. */
|
|
181
|
-
export const EXTERNAL_DESTINATION_SECRETS: Readonly<Record<string, readonly string[]>> = {
|
|
182
|
-
"crm:supabase-buffer": [
|
|
183
|
-
"SUPABASE_BUFFER_URL",
|
|
184
|
-
"SUPABASE_BUFFER_SERVICE_KEY",
|
|
185
|
-
"SUPABASE_BUFFER_TENANT_ID",
|
|
186
|
-
],
|
|
187
|
-
};
|
|
182
|
+
export const EXTERNAL_DESTINATION_SECRETS: Readonly<Record<string, readonly string[]>> = {};
|
|
188
183
|
|
|
189
184
|
/** Closed per-kind vendor catalog, derived from the registry + external vendors (RFC-0176/0186). */
|
|
190
185
|
export const DESTINATION_VENDORS_BY_KIND: Readonly<Record<DestinationKind, readonly string[]>> =
|
|
@@ -318,8 +313,7 @@ export function authenticateInbound(headers: Headers, secret: string | undefined
|
|
|
318
313
|
* supplied. `seen` is the optional dedup set (RFC-0176).
|
|
319
314
|
*
|
|
320
315
|
* `extraAdapters` lets a specific site's delivery callback inject additional
|
|
321
|
-
* DestinationAdapters
|
|
322
|
-
* the closed shared registry. They are self-enabling (skip if secrets absent).
|
|
316
|
+
* DestinationAdapters without modifying the closed shared registry. They are self-enabling (skip if secrets absent).
|
|
323
317
|
*/
|
|
324
318
|
export function routeEventToReady(
|
|
325
319
|
event: IntegrationEvent,
|
|
@@ -350,8 +344,8 @@ export interface DeliverEventResult {
|
|
|
350
344
|
* destinations (CRM), with the client's own tokens. Email is NOT here — it is sent via
|
|
351
345
|
* Cloudflare Email Routing (send_email binding) in the callback route. `seen` dedups the
|
|
352
346
|
* destination side on redelivery. Best-effort per sink; the caller decides ack vs retry.
|
|
353
|
-
* `extraAdapters` lets a specific site inject additional DestinationAdapters
|
|
354
|
-
*
|
|
347
|
+
* `extraAdapters` lets a specific site inject additional DestinationAdapters
|
|
348
|
+
* without modifying the closed shared registry.
|
|
355
349
|
*/
|
|
356
350
|
export async function deliverEvent(
|
|
357
351
|
event: IntegrationEvent,
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<MODULE_CONTRACT>
|
|
3
3
|
<purpose>
|
|
4
4
|
RFC-0168: Integration Port barrel — types and pure contracts only. Re-exports from
|
|
5
|
-
port.ts,
|
|
5
|
+
port.ts, funnel.ts, lifecycle.ts, sharding.ts, dispatch.ts, and qstash.ts.
|
|
6
6
|
Type-only consumers import from here to avoid transitively pulling in adapter
|
|
7
7
|
implementations (orchestration.ts → adapters.ts).
|
|
8
8
|
</purpose>
|
|
@@ -22,33 +22,6 @@ export type { IntegrationEvent, DestinationAdapter } from "./port.ts";
|
|
|
22
22
|
export { DESTINATION_KINDS, EXECUTION_MODES, eventToLeadMessage, eventToLead } from "./port.ts";
|
|
23
23
|
export type { DestinationKind, ExecutionMode } from "./port.ts";
|
|
24
24
|
|
|
25
|
-
export type {
|
|
26
|
-
BufferContact,
|
|
27
|
-
BufferOrganization,
|
|
28
|
-
BufferDeal,
|
|
29
|
-
BufferStageTransition,
|
|
30
|
-
BufferFunnelEvent,
|
|
31
|
-
BufferConsentEvent,
|
|
32
|
-
BufferSubscription,
|
|
33
|
-
BufferInvoice,
|
|
34
|
-
BufferUpsertResult,
|
|
35
|
-
OutboxWriteResult,
|
|
36
|
-
CrmBufferWriter,
|
|
37
|
-
CrmBufferReader,
|
|
38
|
-
CrmBufferClient,
|
|
39
|
-
DealPipedriveIdPatch,
|
|
40
|
-
SyncOutboxRow,
|
|
41
|
-
} from "./crm-buffer.ts";
|
|
42
|
-
export {
|
|
43
|
-
BUFFER_DEAL_STAGES,
|
|
44
|
-
FUNNEL_STAGE_TO_BUFFER_STAGE,
|
|
45
|
-
bridgeFunnelStage,
|
|
46
|
-
isFunnelStage,
|
|
47
|
-
SYNC_OUTBOX_STATUSES,
|
|
48
|
-
SYNC_OUTBOX_OPS,
|
|
49
|
-
} from "./crm-buffer.ts";
|
|
50
|
-
export type { BufferDealStage, SyncOutboxStatus, SyncOutboxOp } from "./crm-buffer.ts";
|
|
51
|
-
|
|
52
25
|
export type {
|
|
53
26
|
VisitorFunnelEventPayload,
|
|
54
27
|
FunnelSystemTrigger,
|
|
@@ -65,6 +38,11 @@ export {
|
|
|
65
38
|
VISITOR_BUYER_TYPES,
|
|
66
39
|
FUNNEL_TRANSITIONS,
|
|
67
40
|
isValidFunnelStage,
|
|
41
|
+
BUFFER_DEAL_STAGES,
|
|
42
|
+
FUNNEL_STAGE_TO_BUFFER_STAGE,
|
|
43
|
+
bridgeFunnelStage,
|
|
44
|
+
SYNC_OUTBOX_STATUSES,
|
|
45
|
+
SYNC_OUTBOX_OPS,
|
|
68
46
|
canTransition,
|
|
69
47
|
nextStages,
|
|
70
48
|
reachableStages,
|
|
@@ -79,6 +57,9 @@ export type {
|
|
|
79
57
|
VisitorFunnelIntent,
|
|
80
58
|
VisitorFunnelSource,
|
|
81
59
|
VisitorBuyerType,
|
|
60
|
+
BufferDealStage,
|
|
61
|
+
SyncOutboxStatus,
|
|
62
|
+
SyncOutboxOp,
|
|
82
63
|
} from "./funnel.ts";
|
|
83
64
|
|
|
84
65
|
export type { LifecycleEventPayload } from "./lifecycle.ts";
|
|
@@ -17,9 +17,9 @@ import {
|
|
|
17
17
|
BUFFER_DEAL_STAGES,
|
|
18
18
|
FUNNEL_STAGE_TO_BUFFER_STAGE,
|
|
19
19
|
bridgeFunnelStage,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
VISITOR_FUNNEL_STAGES,
|
|
21
|
+
isValidFunnelStage,
|
|
22
|
+
} from "../funnel.ts";
|
|
23
23
|
|
|
24
24
|
test("the bridge maps every canonical funnel stage onto a valid generic stage", () => {
|
|
25
25
|
const generic = new Set<string>(BUFFER_DEAL_STAGES);
|
|
@@ -39,8 +39,8 @@ test("terminal funnel stages bridge to their generic equivalents", () => {
|
|
|
39
39
|
expect(bridgeFunnelStage("new_session")).toBe("new");
|
|
40
40
|
});
|
|
41
41
|
|
|
42
|
-
test("
|
|
43
|
-
expect(
|
|
44
|
-
expect(
|
|
45
|
-
expect(
|
|
42
|
+
test("isValidFunnelStage guards the canonical catalog", () => {
|
|
43
|
+
expect(isValidFunnelStage("offer_presented")).toBe(true);
|
|
44
|
+
expect(isValidFunnelStage("q_website_tier")).toBe(false); // legacy UChat string
|
|
45
|
+
expect(isValidFunnelStage("new")).toBe(false); // generic buffer stage, not a funnel stage
|
|
46
46
|
});
|