@alvera-ai/platform-sdk 0.10.0-rc.2 → 0.10.0-rc.21

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 (55) hide show
  1. package/.agent/AGENTS.md +440 -0
  2. package/.agent/account_management.md +455 -0
  3. package/.agent/action_status_updaters.md +262 -0
  4. package/.agent/ai_agents.md +423 -0
  5. package/.agent/ai_sandbox.md +265 -0
  6. package/.agent/async.md +111 -0
  7. package/.agent/connected_apps.md +407 -0
  8. package/.agent/cookbook/_fixtures/README.md +99 -0
  9. package/.agent/cookbook/_fixtures/accounts_receivable/_customers_accounts_receivable_customer.liquid +32 -0
  10. package/.agent/cookbook/_fixtures/accounts_receivable/_customers_accounts_receivable_mdm.liquid +20 -0
  11. package/.agent/cookbook/_fixtures/foundation/_lead_submissions_foundation_generic_table.liquid +33 -0
  12. package/.agent/cookbook/_fixtures/foundation/_lead_submissions_foundation_legal_entity.liquid +88 -0
  13. package/.agent/cookbook/_fixtures/foundation/_lead_submissions_foundation_mdm.liquid +48 -0
  14. package/.agent/cookbook/_fixtures/healthcare/_cahps_appointments_healthcare_appointment.liquid +47 -0
  15. package/.agent/cookbook/_fixtures/healthcare/_cahps_appointments_healthcare_mdm.liquid +24 -0
  16. package/.agent/cookbook/_fixtures/healthcare/_cahps_appointments_healthcare_patient.liquid +38 -0
  17. package/.agent/cookbook/_fixtures/payment_risk/_compliance_screenings_payment_risk_compliance_screening.liquid +59 -0
  18. package/.agent/cookbook/_fixtures/payment_risk/_compliance_screenings_payment_risk_mdm.liquid +36 -0
  19. package/.agent/cookbook/_fixtures/payment_risk/_payment_accounts_payment_risk_mdm.liquid +30 -0
  20. package/.agent/cookbook/_fixtures/payment_risk/_payment_accounts_payment_risk_payment_account.liquid +55 -0
  21. package/.agent/cookbook/_setup/accounts_receivable.md +282 -0
  22. package/.agent/cookbook/_setup/foundation.md +277 -0
  23. package/.agent/cookbook/_setup/healthcare.md +279 -0
  24. package/.agent/cookbook/_setup/payment_risk.md +283 -0
  25. package/.agent/cookbook/appointment-review-sms-workflow.md +761 -0
  26. package/.agent/cookbook/birthday-greeting-sms-trigger.md +656 -0
  27. package/.agent/cookbook/contact-us-triage-with-llm.md +603 -0
  28. package/.agent/cookbook/dunning-sms-for-delinquent.md +619 -0
  29. package/.agent/cookbook/kyc-notification-on-account-activation.md +619 -0
  30. package/.agent/cookbook/sanctions-screening-with-agent-review.md +711 -0
  31. package/.agent/cookbook/score-leads-with-llm-categorization.md +602 -0
  32. package/.agent/cookbook/welcome-sms-for-customers.md +607 -0
  33. package/.agent/data_activation_clients.md +557 -0
  34. package/.agent/data_sources.md +234 -0
  35. package/.agent/datalakes.md +712 -0
  36. package/.agent/debugging.md +137 -0
  37. package/.agent/errors.md +196 -0
  38. package/.agent/generic_tables.md +351 -0
  39. package/.agent/interoperability_contracts.md +351 -0
  40. package/.agent/mdm.md +293 -0
  41. package/.agent/mutations.md +152 -0
  42. package/.agent/templates.md +98 -0
  43. package/.agent/tool-call-configs.md +90 -0
  44. package/.agent/tools.md +546 -0
  45. package/.agent/type_naming.md +131 -0
  46. package/.agent/workflows.md +601 -0
  47. package/README.md +46 -0
  48. package/dist/bin/platform-sdk.d.mts +1 -0
  49. package/dist/bin/platform-sdk.mjs +106 -0
  50. package/dist/bin/platform-sdk.mjs.map +1 -0
  51. package/dist/index.d.mts +1200 -43201
  52. package/dist/index.d.mts.map +1 -1
  53. package/dist/index.mjs +1859 -7319
  54. package/dist/index.mjs.map +1 -1
  55. package/package.json +19 -10
@@ -0,0 +1,407 @@
1
+ # Connected apps
2
+
3
+ A **connected app** is an external web application registered with
4
+ the platform — typically a customer-facing forms portal hosted on
5
+ Cloudflare Workers / Pages or any other deployment target. The
6
+ platform mints magic links into the app from workflow actions:
7
+ each action renders a URL of the form
8
+
9
+ ```
10
+ https://<app-primary-url>/t/<short_path>
11
+ ```
12
+
13
+ The customer clicks the link, lands on a route the app discovered
14
+ via `/.well-known/routes.json`, and the app calls back to the
15
+ platform to resolve the page (fetch the underlying message + its
16
+ metadata) and update tracking (opened_at, form_submitted_at).
17
+
18
+ SDK namespace: `api.connectedApps`.
19
+
20
+ The namespace splits into **two roles**:
21
+
22
+ - **Datalake-scoped management** — the admin side. CRUD on the
23
+ connected-app row, route discovery + sync. Uses the app's
24
+ UUID `id` as the path key.
25
+ - **Tenant-scoped runtime** — the consumer side. Called BY the
26
+ connected app itself (authenticated with its auto-provisioned
27
+ machine-to-machine API key) when a customer hits a magic link. Uses the app's
28
+ `slug` as the path key.
29
+
30
+ Connected apps are **Datalake-DB-resident** — the parent datalake
31
+ must be `status: 'ready'` before POST.
32
+
33
+ ## 1. Wire shape
34
+
35
+ ```typescript
36
+ import type {
37
+ ConnectedAppResponse,
38
+ } from '@alvera-ai/platform-sdk'
39
+
40
+ const { data: created } = await api.connectedApps.create(
41
+ tenantSlug,
42
+ datalakeSlug,
43
+ {
44
+ name: 'The Doctors Center',
45
+ description: 'Patient-facing forms portal',
46
+ mode: 'self_hosted', // 'self_hosted' (only mode active today; see §2)
47
+ urls: [
48
+ {
49
+ url: 'https://the-doctors-center.pages.dev', // app's deployed origin
50
+ is_primary: true, // exactly one URL must be primary
51
+ label: 'Production',
52
+ },
53
+ ],
54
+ },
55
+ )
56
+ // created.id, created.slug — server-derived
57
+ // created.status === 'pending' (transient) → 'synced' after route fetch
58
+ // created.api_key_id — auto-provisioned M2M key id (see §2)
59
+ // created.routes — array populated from .well-known/routes.json
60
+ // (empty until first sync)
61
+ // created.last_synced_at — set when routes successfully validated
62
+ ```
63
+
64
+ The platform validates `/.well-known/routes.json` at the primary
65
+ URL during create. If validation succeeds, routes are saved and
66
+ the status flips to `'synced'`. If it fails, the row persists
67
+ with `status: 'error'` and the failure message in `error`.
68
+
69
+ ## 2. Rules the type cannot encode
70
+
71
+ ### URLs: at least one, exactly one primary
72
+
73
+ The `urls` array requires at least one entry, and **exactly one
74
+ entry must have `is_primary: true`**. The primary URL is the host
75
+ the platform fetches `/.well-known/routes.json` from and the
76
+ host that magic links are rooted at. Submitting zero primaries
77
+ or multiple primaries returns a 422 on `/urls`.
78
+
79
+ Additional URLs (staging, preview) are stored for reference but
80
+ aren't used for route discovery or link minting.
81
+
82
+ ### Route discovery is automatic + re-triggerable
83
+
84
+ ```
85
+ /.well-known/routes.json served by the connected app at its primary URL.
86
+ Must return application/json with a JSON array
87
+ of route objects:
88
+
89
+ [
90
+ { "name": "Review Form", "path": "/forms/review", "description": "..." },
91
+ { "name": "CAHPS Survey", "path": "/forms/cahps", "description": "..." }
92
+ ]
93
+ ```
94
+
95
+ Validation rules at fetch time:
96
+
97
+ - Response content-type must be `application/json`
98
+ - Body must be a JSON array
99
+ - Each route requires `name` (string) and `path` (string);
100
+ `description` is optional
101
+
102
+ The platform fetches the manifest at two moments:
103
+
104
+ 1. **At create time** — automatic. Failure flips the row to
105
+ `status: 'error'` (the row still persists; routes stay empty).
106
+ 2. **On explicit `.syncRoutes(id)`** — operator-triggered re-fetch.
107
+ Updates the saved routes and `last_synced_at`.
108
+
109
+ The SDK does NOT auto-re-sync routes on a schedule. Re-sync is
110
+ deliberate.
111
+
112
+ ### Modes: only `self_hosted` is active today
113
+
114
+ ```
115
+ mode: 'self_hosted' active — operator runs the deployment
116
+ themselves (e.g. `wrangler deploy`); the
117
+ platform registers the resulting URL and
118
+ discovers routes from it.
119
+ mode: 'managed' reserved for a future Cloudflare-Pages
120
+ auto-deploy flow. Not provisioned at
121
+ runtime today — submitting it succeeds
122
+ structurally but no deploy fires.
123
+ ```
124
+
125
+ Treat `'self_hosted'` as the only practical choice for current
126
+ consumers.
127
+
128
+ ### machine-to-machine API key is auto-provisioned
129
+
130
+ Creating a connected app automatically provisions a machine-to-
131
+ machine API key named `ConnectedApp: <app name>` with the `api`
132
+ role. The key is exposed on the response once (typically rendered
133
+ in the create-flow UI) so the operator can configure it as a
134
+ secret in the deployed app's environment. The platform stores only
135
+ the key's id (`api_key_id` on the response).
136
+
137
+ The connected app deployment should be configured with:
138
+
139
+ ```
140
+ ALVERA_API_KEY="<auto-provisioned key>"
141
+ ALVERA_DATALAKE_ID="<datalake-uuid>"
142
+ ALVERA_API_URL="https://<platform-host>"
143
+ ```
144
+
145
+ Deleting the connected app deletes the API key.
146
+
147
+ ### `slug` is the runtime reference; `id` is the admin reference
148
+
149
+ Management endpoints (`list`, `get`, `update`, `syncRoutes`,
150
+ `metadataDetails`) take the UUID `id` as the path key — the
151
+ datalake-scoped admin context.
152
+
153
+ Runtime endpoints (`resolvePage`, `updateMessageTracking`) take
154
+ the `slug` and skip the datalake path segment entirely. This is
155
+ deliberate: the connected app deployment is authenticated with
156
+ the tenant-scoped machine-to-machine API key and may not know which datalake
157
+ its row lives under.
158
+
159
+ ## 3. Field ownership
160
+
161
+ **Server-derived (Response-only).** Universal set from
162
+ `type_naming.md`, plus:
163
+
164
+ ```
165
+ status enum — 'pending' | 'deploying' | 'deployed' | 'synced' | 'error'
166
+ — for the dominant `'self_hosted'` mode the status
167
+ flows 'pending' → 'synced' (or 'error' on a
168
+ failed route fetch). 'deploying' / 'deployed'
169
+ are reserved for the future `'managed'`
170
+ Cloudflare-Pages auto-deploy flow (see §2)
171
+ and won't appear on self-hosted rows today.
172
+ last_synced_at string — ISO 8601; set at successful route sync
173
+ error string — populated when status === 'error'
174
+ routes array — discovered from .well-known/routes.json
175
+ (each: { name, path, description? })
176
+ api_key_id UUID — id of the auto-provisioned M2M key
177
+ ```
178
+
179
+ **Caller-supplied (round-trip).**
180
+
181
+ ```
182
+ name required string
183
+ description optional string
184
+ mode required enum — 'self_hosted' (only active)
185
+ urls required array — see §2; exactly one primary
186
+ ```
187
+
188
+ **Write-only (Request-only).** None. The auto-provisioned API key's
189
+ plaintext value is returned ONCE on the create response (not on
190
+ subsequent reads), but it isn't a "write-only" field in the
191
+ caller-supplied sense.
192
+
193
+ ## 4. Error envelopes
194
+
195
+ Standard JSON:API envelopes per `errors.md`. Common rejections:
196
+
197
+ | `source.pointer` | Cause |
198
+ |-------------------------------|--------------------------------------------------------|
199
+ | `/urls` | empty, missing primary, or multiple primaries |
200
+ | `/urls/0/url` | not a valid URL |
201
+ | `/mode` | value not in enum |
202
+ | `/name` | uniqueness within datalake — server error literal: "name must be unique within a datalake" |
203
+
204
+ Route-fetch failures (primary URL unreachable, manifest malformed)
205
+ do NOT reject the create — the row persists with `status: 'error'`
206
+ and `error` populated. Use `.syncRoutes(id)` to retry after fixing
207
+ the deployment.
208
+
209
+ ## 5. Lifecycle
210
+
211
+ ### Create
212
+
213
+ Synchronous. The platform attempts a single fetch of
214
+ `/.well-known/routes.json` at the primary URL during create.
215
+ Success → `status: 'synced'` and `routes` populated. Failure →
216
+ `status: 'error'` and `error` populated; the row still exists
217
+ and can be retried via `.syncRoutes`.
218
+
219
+ ### Read shapes — management
220
+
221
+ ```
222
+ .list(tenantSlug, datalakeSlug) Paged: { data, meta }
223
+ .get(tenantSlug, datalakeSlug, id) One row, by UUID
224
+ .metadata(tenantSlug, datalakeSlug) Markdown catalog
225
+ .metadataDetails(tenantSlug, datalakeSlug, Markdown for one app
226
+ id)
227
+ ```
228
+
229
+ ### Sync routes
230
+
231
+ ```typescript
232
+ const { data: ack } = await api.connectedApps.syncRoutes(
233
+ tenantSlug, datalakeSlug, appId,
234
+ )
235
+ // 202 Accepted — the sync runs in a background worker:
236
+ // ack.status — 'enqueued'
237
+ // ack.job_id — background job id
238
+ // ack.connected_app_id — echo of the app id
239
+ // ack.enqueued_at — enqueue timestamp
240
+ ```
241
+
242
+ This is the explicit "re-fetch and save" verb — asynchronous. The
243
+ 202 acknowledgement proves the app id resolved and a sync job was
244
+ accepted; the worker then refreshes `routes` + `last_synced_at` and
245
+ lands `status: 'synced'` (or `'error'`) on the app row. Poll
246
+ `.get()` for the outcome. Use it after deploying a new manifest,
247
+ fixing a primary URL outage, or recovering from `status: 'error'`.
248
+
249
+ Discovery failure modes the platform handles explicitly — each
250
+ leaves the row at `status: 'error'` with `error` populated:
251
+
252
+ ```
253
+ HTTP 404 from primary URL — manifest not found
254
+ HTTP 5xx from primary URL — server error at the app
255
+ request timeout — primary URL slow / hung
256
+ TCP connection refused — deployment not running
257
+ response content-type != application/json — wrong endpoint or proxy
258
+ response body is not a JSON array — manifest schema wrong
259
+ route entry missing required `name` — per-route schema gap
260
+ route entry missing required `path` — per-route schema gap
261
+ ```
262
+
263
+ Per-route schema failures stop the sync — the platform validates
264
+ every entry in the array and refuses partial saves. Empty
265
+ manifests (`[]`) succeed with zero routes registered.
266
+
267
+ The primary URL has a trailing slash stripped before the
268
+ `/.well-known/routes.json` join, so both `https://app.example.com`
269
+ and `https://app.example.com/` produce the same fetch URL.
270
+
271
+ ### Update
272
+
273
+ `PUT` replays the full body. Mutating the primary URL does NOT
274
+ auto-trigger a route sync — call `.syncRoutes` after the update if
275
+ the new primary URL serves a different manifest.
276
+
277
+ ### Delete
278
+
279
+ `DELETE` removes the connected app AND the auto-provisioned API
280
+ key. Workflows that still reference the app's id in action
281
+ `connected_app_id` fields fail at run time with the action's
282
+ template-render step erroring on the missing relation. Detach
283
+ workflow actions first.
284
+
285
+ Delete latency depends on the app's `mode`:
286
+
287
+ - `mode: 'self_hosted'` — synchronous. The row is gone when
288
+ the DELETE response returns.
289
+ - `mode: 'managed'` — async. The DELETE response returns
290
+ immediately, but the actual teardown (Cloudflare Pages
291
+ project deletion + DNS unregistration) runs in a background
292
+ worker. The row's `status` transitions through
293
+ `'deploying'` (during teardown) before disappearing. Poll
294
+ `.get()` until 404 to confirm removal.
295
+
296
+ ## 6. Runtime: how magic links resolve
297
+
298
+ This section covers the runtime callers — the connected app
299
+ deployment itself, not the admin.
300
+
301
+ At workflow run time, an action with `connected_app_id` +
302
+ `connected_app_route` renders a message body that contains a
303
+ URL of the form:
304
+
305
+ ```
306
+ https://<primary-url>/t/<short_path>
307
+ ```
308
+
309
+ The customer clicks the link, lands on the app, and the app
310
+ resolves the page:
311
+
312
+ ```typescript
313
+ const { data: page } = await api.connectedApps.resolvePage(
314
+ tenantSlug, slug, // tenant + connected-app slug
315
+ {
316
+ short_path: '<the-short-path-from-the-URL>',
317
+ user_agent: req.headers.get('user-agent') ?? '',
318
+ },
319
+ )
320
+ // page.message — the rendered message row
321
+ // page.message.body — the regulated rendered body (raw)
322
+ // page.route_path — the route the link was minted against
323
+ // (e.g. '/forms/review')
324
+ // page.metadata — parsed `connected_app_metadata_template`
325
+ // output from the workflow action; arbitrary JSON
326
+ ```
327
+
328
+ The app routes to the page named by `route_path`, renders the
329
+ form using `metadata` for prefill, and updates tracking when
330
+ the customer opens or submits the form:
331
+
332
+ ```typescript
333
+ await api.connectedApps.updateMessageTracking(
334
+ tenantSlug, slug,
335
+ {
336
+ short_path: '<same-short-path>',
337
+ opened_at: new Date().toISOString(),
338
+ form_submitted_at: new Date().toISOString(), // only when form completes
339
+ },
340
+ )
341
+ ```
342
+
343
+ Both calls are authenticated with the connected app's
344
+ auto-provisioned machine-to-machine API key (typically configured as
345
+ `ALVERA_API_KEY` in the deployment environment).
346
+
347
+ ### Workflow-side wiring
348
+
349
+ A workflow action that emits a magic link must set:
350
+
351
+ ```
352
+ connected_app_id UUID — the registered app
353
+ connected_app_route string — must match a discovered route's path
354
+ (e.g. '/forms/review')
355
+ connected_app_metadata_template string — Liquid template rendering a JSON
356
+ object the resolved page returns
357
+ to the app (arbitrary shape; the
358
+ app decides how to use it for
359
+ prefill, headers, etc.)
360
+ ```
361
+
362
+ See `workflows.md` §7 for the action-level field detail.
363
+
364
+ ## 7. Gotchas
365
+
366
+ 1. **Exactly one URL must be `is_primary: true`.** Zero or
367
+ multiple primaries returns 422. The primary is what the
368
+ platform fetches routes from AND what magic links are minted
369
+ against; additional URLs are reference-only.
370
+
371
+ 2. **`status: 'error'` after create is recoverable.** A failed
372
+ route fetch (manifest 404, malformed JSON, primary URL DNS
373
+ failure) doesn't reject the row. Fix the deployment and call
374
+ `.syncRoutes(id)` to retry.
375
+
376
+ 3. **Mode `'managed'` is reserved.** It's structurally valid in
377
+ the request type but no deploy fires today. Use `'self_hosted'`
378
+ and run your own deployment.
379
+
380
+ 4. **`.syncRoutes` is NOT automatic.** The platform only fetches
381
+ the manifest at create time and on explicit re-sync. Operators
382
+ who change their `/.well-known/routes.json` between deploys
383
+ must trigger a sync.
384
+
385
+ 5. **Runtime calls use `slug`, not `id`.** `.resolvePage` and
386
+ `.updateMessageTracking` skip the datalake path segment and
387
+ key on the app's slug + tenant. The auto-provisioned M2M key
388
+ is tenant-scoped, so the connected-app deployment authenticates
389
+ without knowing the datalake.
390
+
391
+ 6. **`connected_app_route` on a workflow action must match a
392
+ discovered route.** Action templates reference
393
+ `{{ connected_app_form_url }}` which the platform constructs
394
+ from the bound app's primary URL + the route path. A route
395
+ that hasn't been synced into the app's `routes` array still
396
+ accepts the action at create time but the magic-link
397
+ resolution fails when a customer clicks the link.
398
+
399
+ 7. **The auto-provisioned API key is revealed ONCE.** It appears
400
+ on the create response and is never returned again. The
401
+ create-flow caller is responsible for capturing the plaintext
402
+ value and configuring it into the connected-app deployment's
403
+ environment. The platform stores only the key id on the row.
404
+
405
+ 8. **Route manifests must be JSON arrays, not objects.** A
406
+ common mistake is wrapping the routes in `{ "routes": [...] }`
407
+ — the manifest's top-level must be the array itself.
@@ -0,0 +1,99 @@
1
+ # Cookbook fixtures
2
+
3
+ Vendored Liquid templates and CSV fixtures the
4
+ [cookbook recipes](../) load at runtime via
5
+ `fs.readFileSync`. Organized by industry so the corpus stays
6
+ self-describing — an agent crawling this directory can list
7
+ every template available to every cookbook in a given industry
8
+ by reading one path.
9
+
10
+ ## Convention
11
+
12
+ Cookbook code reads fixtures via the
13
+ `COOKBOOK_FIXTURES_DIR` environment variable. The validator
14
+ (`make validate-cookbook`) exports this variable to the spawned
15
+ `bun test` process; any operator script that runs cookbook code
16
+ directly must set the same variable to the absolute path of
17
+ this directory.
18
+
19
+ ```typescript
20
+ import fs from 'node:fs'
21
+ import path from 'node:path'
22
+
23
+ const LE_TEMPLATE = fs.readFileSync(
24
+ path.join(
25
+ process.env.COOKBOOK_FIXTURES_DIR!,
26
+ 'foundation/_lead_submissions_foundation_legal_entity.liquid',
27
+ ),
28
+ 'utf8',
29
+ )
30
+ ```
31
+
32
+ Same env-var pattern as the `ALVERA_BASE_URL` /
33
+ `ALVERA_ROOT_EMAIL` / `ALVERA_ROOT_PASSWORD` credentials the
34
+ generated specs already consume.
35
+
36
+ ## Why fixtures instead of inline templates
37
+
38
+ Inlining each template verbatim inside the cookbook markdown
39
+ would bloat each recipe to several hundred extra lines of
40
+ Liquid noise, which obscures the cookbook's actual lesson (the
41
+ sequence of SDK calls plus the assertions). Loading templates
42
+ by path preserves cookbook readability AND keeps recipes
43
+ executable — the cookbook is shipped as part of the
44
+ `@alvera-ai/platform-sdk` npm package, the fixtures ship
45
+ alongside it via the package's `files: [.agent]` field, and
46
+ the env-var dereference works for any consumer that installed
47
+ the package.
48
+
49
+ ## Provenance
50
+
51
+ Templates are copied verbatim from
52
+ [platform/priv/liquid_templates/](https://github.com/alvera-ai/platform/tree/develop/priv/liquid_templates),
53
+ the production source-of-truth. The same vendoring chain
54
+ already exists in
55
+ `platform/integration-tests/tests/<industry>/fixtures/templates/`;
56
+ this directory extends that chain into the SDK package so the
57
+ cookbook corpus is self-contained at npm-install time.
58
+
59
+ When a production template changes, re-copy the file here and
60
+ re-run `make validate-cookbook` to confirm the cookbook still
61
+ passes against the new template shape.
62
+
63
+ ## Per-industry inventory
64
+
65
+ ### `foundation/`
66
+
67
+ | File | Consumed by cookbook(s) | Source-of-truth |
68
+ |---|---|---|
69
+ | `_lead_submissions_foundation_legal_entity.liquid` | `birthday-greeting-sms-trigger`, `score-leads-with-llm-categorization` | `priv/liquid_templates/data_activation/interoperability/foundation/airflow/_lead_submissions_foundation_legal_entity.liquid` |
70
+ | `_lead_submissions_foundation_generic_table.liquid` | `score-leads-with-llm-categorization` | `priv/liquid_templates/data_activation/interoperability/foundation/airflow/_lead_submissions_foundation_generic_table.liquid` |
71
+ | `_lead_submissions_foundation_mdm.liquid` | `score-leads-with-llm-categorization` | `priv/liquid_templates/data_activation/interoperability/foundation/airflow/_lead_submissions_foundation_mdm.liquid` |
72
+
73
+ ### `healthcare/`
74
+
75
+ | File | Consumed by cookbook(s) | Source-of-truth |
76
+ |---|---|---|
77
+ | `_cahps_appointments_healthcare_patient.liquid` | `appointment-review-sms-workflow` | `priv/liquid_templates/data_activation/interoperability/healthcare/fhir4/athena/_cahps_appointments_healthcare_patient.liquid` |
78
+ | `_cahps_appointments_healthcare_appointment.liquid` | `appointment-review-sms-workflow` | `priv/liquid_templates/data_activation/interoperability/healthcare/fhir4/athena/_cahps_appointments_healthcare_appointment.liquid` |
79
+ | `_cahps_appointments_healthcare_mdm.liquid` | `appointment-review-sms-workflow` | `priv/liquid_templates/data_activation/interoperability/healthcare/fhir4/athena/_cahps_appointments_healthcare_mdm.liquid` |
80
+
81
+ The `contact-us-triage-with-llm` cookbook ingests into a generic
82
+ table via the auto-provisioned identity contract, so it loads no
83
+ vendored template.
84
+
85
+ ### `accounts_receivable/`
86
+
87
+ | File | Consumed by cookbook(s) | Source-of-truth |
88
+ |---|---|---|
89
+ | `_customers_accounts_receivable_customer.liquid` | `welcome-sms-for-customers`, `dunning-sms-for-delinquent` | `priv/liquid_templates/data_activation/interoperability/accounts_receivable/stripe/_customers_accounts_receivable_customer.liquid` |
90
+ | `_customers_accounts_receivable_mdm.liquid` | `welcome-sms-for-customers`, `dunning-sms-for-delinquent` | `priv/liquid_templates/data_activation/interoperability/accounts_receivable/stripe/_customers_accounts_receivable_mdm.liquid` |
91
+
92
+ ### `payment_risk/`
93
+
94
+ | File | Consumed by cookbook(s) | Source-of-truth |
95
+ |---|---|---|
96
+ | `_payment_accounts_payment_risk_payment_account.liquid` | `kyc-notification-on-account-activation` | `priv/liquid_templates/data_activation/interoperability/payment_risk/atomic_fi/_payment_accounts_payment_risk_payment_account.liquid` |
97
+ | `_payment_accounts_payment_risk_mdm.liquid` | `kyc-notification-on-account-activation` | `priv/liquid_templates/data_activation/interoperability/payment_risk/atomic_fi/_payment_accounts_payment_risk_mdm.liquid` |
98
+ | `_compliance_screenings_payment_risk_compliance_screening.liquid` | `sanctions-screening-with-agent-review` | `priv/liquid_templates/data_activation/interoperability/payment_risk/atomic_fi/_compliance_screenings_payment_risk_compliance_screening.liquid` |
99
+ | `_compliance_screenings_payment_risk_mdm.liquid` | `sanctions-screening-with-agent-review` | `priv/liquid_templates/data_activation/interoperability/payment_risk/atomic_fi/_compliance_screenings_payment_risk_mdm.liquid` |
@@ -0,0 +1,32 @@
1
+ {% comment %}Stripe Customers → Accounts Receivable Customer attrs{% endcomment %}
2
+ {% assign p = msg %}
3
+ {
4
+ "customer_type": "{{ p.customer_type | default: "individual" | json_escape }}",
5
+ "status": "{{ p.status | default: "prospect" | json_escape }}",
6
+ "customer_number": "{{ p.customer_number | json_escape }}",
7
+ {% if p.currency and p.currency != "" %}"currency": "{{ p.currency | json_escape }}",{% endif %}
8
+ {% if p.delinquent and p.delinquent != "" %}"delinquent": {{ p.delinquent }},{% endif %}
9
+ {% if p.tax_exempt and p.tax_exempt != "" %}"tax_exempt": "{{ p.tax_exempt | json_escape }}",{% endif %}
10
+ {% if p.preferred_locales and p.preferred_locales != "" %}{% assign locales = p.preferred_locales | split: "|" %}"preferred_locales": [{% for l in locales %}"{{ l | json_escape }}"{% unless forloop.last %},{% endunless %}{% endfor %}],{% endif %}
11
+ "regulated_customer": {
12
+ {% if p.name and p.name != "" %}"name": "{{ p.name | json_escape }}",{% endif %}
13
+ {% if p.email and p.email != "" %}"email": "{{ p.email | json_escape }}",{% endif %}
14
+ {% if p.phone and p.phone != "" %}"phone": "{{ p.phone | json_escape }}",{% endif %}
15
+ {% if p.tax_id and p.tax_id != "" %}"tax_id": "{{ p.tax_id | json_escape }}",{% endif %}
16
+ {% if p.address_line1 and p.address_line1 != "" %}"address_line1": "{{ p.address_line1 | json_escape }}",{% endif %}
17
+ {% if p.address_line2 and p.address_line2 != "" %}"address_line2": "{{ p.address_line2 | json_escape }}",{% endif %}
18
+ {% if p.address_city and p.address_city != "" %}"address_city": "{{ p.address_city | json_escape }}",{% endif %}
19
+ {% if p.address_state and p.address_state != "" %}"address_state": "{{ p.address_state | json_escape }}",{% endif %}
20
+ {% if p.address_postal_code and p.address_postal_code != "" %}"address_postal_code": "{{ p.address_postal_code | json_escape }}",{% endif %}
21
+ {% if p.address_country and p.address_country != "" %}"address_country": "{{ p.address_country | json_escape }}",{% endif %}
22
+ {% if p.shipping_name and p.shipping_name != "" %}"shipping_name": "{{ p.shipping_name | json_escape }}",{% endif %}
23
+ {% if p.shipping_phone and p.shipping_phone != "" %}"shipping_phone": "{{ p.shipping_phone | json_escape }}",{% endif %}
24
+ {% if p.shipping_address_line1 and p.shipping_address_line1 != "" %}"shipping_address_line1": "{{ p.shipping_address_line1 | json_escape }}",{% endif %}
25
+ {% if p.shipping_address_line2 and p.shipping_address_line2 != "" %}"shipping_address_line2": "{{ p.shipping_address_line2 | json_escape }}",{% endif %}
26
+ {% if p.shipping_address_city and p.shipping_address_city != "" %}"shipping_address_city": "{{ p.shipping_address_city | json_escape }}",{% endif %}
27
+ {% if p.shipping_address_state and p.shipping_address_state != "" %}"shipping_address_state": "{{ p.shipping_address_state | json_escape }}",{% endif %}
28
+ {% if p.shipping_address_postal_code and p.shipping_address_postal_code != "" %}"shipping_address_postal_code": "{{ p.shipping_address_postal_code | json_escape }}",{% endif %}
29
+ "shipping_address_country": "{{ p.shipping_address_country | default: "" | json_escape }}"
30
+ },
31
+ "source_uri": "{{ p.source_uri | default: "" | json_escape }}"
32
+ }
@@ -0,0 +1,20 @@
1
+ {% comment %}Stripe Customers → Accounts Receivable MDM Input{% endcomment %}
2
+ {% assign p = msg %}
3
+ {
4
+ "identifiers": [
5
+ {
6
+ "system": "urn:{{ p.source_uri | json_escape }}:customer-number",
7
+ "value": "{{ p.customer_number | json_escape }}"
8
+ }
9
+ ],
10
+ {% assign space_parts = p.name | split: " " %}
11
+ {% if space_parts.size > 1 %}
12
+ "given_name": "{{ space_parts | first | json_escape }}",
13
+ "family_name": "{{ space_parts | last | json_escape }}",
14
+ {% else %}
15
+ "family_name": "{{ p.name | strip | json_escape }}",
16
+ {% endif %}
17
+ {% if p.phone and p.phone != "" %}"phone": "{{ p.phone | json_escape }}",{% endif %}
18
+ {% if p.email and p.email != "" %}"email": "{{ p.email | json_escape }}",{% endif %}
19
+ "gender": "unknown"
20
+ }
@@ -0,0 +1,33 @@
1
+ {% comment %}
2
+ Foundation Airflow Lead Form → `lead_submissions` generic_table row.
3
+
4
+ Maps a single Google-Sheet lead row into the `lead_submissions` GT
5
+ upsert shape — one JSON field per GT column. Used by Contract B
6
+ (`resource_type: "generic_table"`, `generic_table_id` pinned to the
7
+ `lead_submissions` GT). Contract B also carries an
8
+ `mdm_input_config` (separate template) so the GT event emitted on
9
+ upsert pins `mdm_subject_id == legal_entity.id` (the LE that
10
+ Contract A wrote / Foundation MDM resolved by digital_identifier
11
+ email dedupe).
12
+
13
+ GT columns:
14
+ * `submission_id` — vendor-supplied unique sheet row id (unique col,
15
+ so re-ingest of the same submission_id idempotently updates the
16
+ same row instead of inserting a duplicate)
17
+ * `name`, `email`, `phone` — tokenized PII
18
+ * `company` — empty string for individual leads, non-empty for
19
+ business leads (mirrors Contract A's branch logic)
20
+ * `message` — free-text lead body
21
+ * `lead_source` — utm_source / sheet tab id, surfaced for downstream
22
+ attribution dashboards
23
+ {% endcomment %}
24
+ {% assign p = msg %}
25
+ {
26
+ "submission_id": "{{ p.submission_id | json_escape }}",
27
+ "name": "{{ p.name | default: "" | json_escape }}",
28
+ "email": "{{ p.email | default: "" | json_escape }}",
29
+ "phone": "{{ p.phone | default: "" | json_escape }}",
30
+ "company": "{{ p.company | default: "" | json_escape }}",
31
+ "message": "{{ p.message | default: "" | json_escape }}",
32
+ "lead_source": "{{ p.lead_source | default: "" | json_escape }}"
33
+ }