@alvera-ai/platform-sdk 0.17.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/.agent/AGENTS.md +82 -144
  2. package/.agent/account_management.md +2 -2
  3. package/.agent/action_logs.md +4 -4
  4. package/.agent/ai_agents.md +28 -21
  5. package/.agent/ai_sandbox.md +49 -39
  6. package/.agent/connected_apps.md +3 -3
  7. package/.agent/cookbook/_fixtures/README.md +1 -1
  8. package/.agent/cookbook/_fixtures/{foundation → organic-marketing}/_lead_submissions_foundation_generic_table.liquid +1 -1
  9. package/.agent/cookbook/_fixtures/organic-marketing/_lead_submissions_foundation_legal_entity.liquid +80 -0
  10. package/.agent/cookbook/_fixtures/{foundation → organic-marketing}/_lead_submissions_foundation_mdm.liquid +2 -1
  11. package/.agent/cookbook/_fixtures/payments-compliance/_compliance_screenings_generic_table.liquid +57 -0
  12. package/.agent/cookbook/_fixtures/payments-compliance/_compliance_screenings_legal_entity.liquid +30 -0
  13. package/.agent/cookbook/_fixtures/payments-compliance/_compliance_screenings_mdm.liquid +44 -0
  14. package/.agent/cookbook/_fixtures/payments-compliance/_payment_accounts_generic_table.liquid +57 -0
  15. package/.agent/cookbook/_fixtures/payments-compliance/_payment_accounts_legal_entity.liquid +36 -0
  16. package/.agent/cookbook/_fixtures/payments-compliance/_payment_accounts_mdm.liquid +41 -0
  17. package/.agent/cookbook/_fixtures/primary-care-feedback/_cahps_appointments_generic_table.liquid +70 -0
  18. package/.agent/cookbook/_fixtures/primary-care-feedback/_cahps_appointments_legal_entity.liquid +52 -0
  19. package/.agent/cookbook/_fixtures/primary-care-feedback/_cahps_appointments_mdm.liquid +42 -0
  20. package/.agent/cookbook/_fixtures/subscription-saas/_customers_subscription_generic_table.liquid +38 -0
  21. package/.agent/cookbook/_fixtures/subscription-saas/_customers_subscription_legal_entity.liquid +48 -0
  22. package/.agent/cookbook/_fixtures/subscription-saas/_customers_subscription_mdm.liquid +49 -0
  23. package/.agent/cookbook/organic-marketing.md +2801 -0
  24. package/.agent/cookbook/payments-compliance.md +2180 -0
  25. package/.agent/cookbook/primary-care.md +2175 -0
  26. package/.agent/cookbook/subscription-saas.md +2403 -0
  27. package/.agent/data_activation_clients.md +65 -52
  28. package/.agent/datalakes.md +338 -171
  29. package/.agent/errors.md +3 -3
  30. package/.agent/generic_tables.md +151 -62
  31. package/.agent/interoperability_contracts.md +57 -22
  32. package/.agent/mdm.md +136 -153
  33. package/.agent/messages.md +36 -34
  34. package/.agent/mock-services.md +1 -1
  35. package/.agent/mutations.md +2 -2
  36. package/.agent/templates.md +14 -13
  37. package/.agent/tools.md +63 -21
  38. package/.agent/type_naming.md +13 -13
  39. package/.agent/workflows.md +99 -53
  40. package/README.md +2 -2
  41. package/dist/bin/platform-sdk.mjs +33 -47
  42. package/dist/bin/platform-sdk.mjs.map +1 -1
  43. package/dist/index.d.mts +565 -379
  44. package/dist/index.d.mts.map +1 -1
  45. package/dist/index.mjs +494 -59
  46. package/dist/index.mjs.map +1 -1
  47. package/package.json +4 -3
  48. package/.agent/cookbook/_fixtures/foundation/_lead_submissions_foundation_legal_entity.liquid +0 -88
  49. package/.agent/cookbook/_fixtures/healthcare/_cahps_appointments_healthcare_appointment.liquid +0 -47
  50. package/.agent/cookbook/_fixtures/healthcare/_cahps_appointments_healthcare_mdm.liquid +0 -24
  51. package/.agent/cookbook/_fixtures/healthcare/_cahps_appointments_healthcare_patient.liquid +0 -38
  52. package/.agent/cookbook/_fixtures/payments/_compliance_screenings_payments_compliance_screening.liquid +0 -59
  53. package/.agent/cookbook/_fixtures/payments/_compliance_screenings_payments_mdm.liquid +0 -36
  54. package/.agent/cookbook/_fixtures/payments/_payment_accounts_payments_mdm.liquid +0 -30
  55. package/.agent/cookbook/_fixtures/payments/_payment_accounts_payments_payment_account.liquid +0 -55
  56. package/.agent/cookbook/_fixtures/subscription/_customers_subscription_mdm.liquid +0 -20
  57. package/.agent/cookbook/_setup/foundation.md +0 -359
  58. package/.agent/cookbook/_setup/healthcare.md +0 -361
  59. package/.agent/cookbook/_setup/payments.md +0 -365
  60. package/.agent/cookbook/_setup/subscription.md +0 -364
  61. package/.agent/cookbook/action-status-updaters.md +0 -278
  62. package/.agent/cookbook/ai-agent-invoke.md +0 -279
  63. package/.agent/cookbook/appointment-review-sms-workflow.md +0 -801
  64. package/.agent/cookbook/birthday-greeting-sms-trigger.md +0 -696
  65. package/.agent/cookbook/bulk-ingest.md +0 -302
  66. package/.agent/cookbook/contact-us-triage-with-llm.md +0 -663
  67. package/.agent/cookbook/dunning-sms-for-delinquent.md +0 -659
  68. package/.agent/cookbook/generic-tables.md +0 -244
  69. package/.agent/cookbook/invite-team.md +0 -200
  70. package/.agent/cookbook/kyc-notification-on-account-activation.md +0 -661
  71. package/.agent/cookbook/marketing-campaign-send.md +0 -1044
  72. package/.agent/cookbook/paginated-restapi-poller.md +0 -383
  73. package/.agent/cookbook/rest-fetch.md +0 -273
  74. package/.agent/cookbook/sanctions-screening-with-agent-review.md +0 -773
  75. package/.agent/cookbook/score-leads-with-llm-categorization.md +0 -665
  76. package/.agent/cookbook/system-templates.md +0 -165
  77. package/.agent/cookbook/talk-to-data.md +0 -178
  78. package/.agent/cookbook/triage-prospects-by-priority.md +0 -571
  79. package/.agent/cookbook/welcome-sms-for-customers.md +0 -647
  80. /package/.agent/cookbook/_fixtures/{healthcare → primary-care-feedback}/memorandum-of-association-01.png +0 -0
  81. /package/.agent/cookbook/_fixtures/{healthcare → primary-care-feedback}/sample_two_page.pdf +0 -0
  82. /package/.agent/cookbook/_fixtures/{subscription → subscription-saas}/_customers_subscription_customer.liquid +0 -0
  83. /package/.agent/cookbook/_fixtures/{subscription → subscription-saas}/stripe_customers_batch1.csv +0 -0
@@ -1,365 +0,0 @@
1
- ---
2
- title: Payments industry bootstrap setup
3
- summary: Auth as root, sign up + confirm a fresh industry-admin user, create a payments tenant + datalake. Shared by every payments scenario cookbook.
4
- industry: payments
5
- slug: payments
6
- vitest_source:
7
- - integration-tests/tests/payments/bootstrap.test.ts
8
- status: green
9
- ---
10
-
11
- # Problem
12
-
13
- Every payments scenario cookbook needs the same starting
14
- point: a fresh tenant on the platform with a payments-domain
15
- datalake attached, accessed by an industry-admin user. Authoring
16
- this prelude inline inside every scenario cookbook would
17
- duplicate the auth + tenant + datalake chain across two cookbooks
18
- (and six more across the other industries). The validator
19
- auto-discovers this file from each cookbook's
20
- `industry: payments` front-matter and inlines its numbered
21
- steps ahead of the scenario's own, so the generated bun:test spec
22
- is hermetic without the cookbook author having to copy-paste this
23
- prelude.
24
-
25
- # Composition
26
-
27
- | Resource provisioned | Owner |
28
- |-------------------------------|-------------|
29
- | Root session | setup |
30
- | Industry-admin user (`sarah`) | setup |
31
- | Payments tenant | setup |
32
- | Payments datalake | setup |
33
-
34
- # Walkthrough
35
-
36
- ## 001 — root admin signs in and provisions a fresh industry-admin user
37
-
38
- Authenticate as the platform's root admin (`admin@dev.local` /
39
- `devpassword` in local dev) via the tenantless bootstrap login —
40
- keyless by structural necessity (no tenant exists yet to scope a
41
- key to), and a dev/test-only surface. Then sign up a per-run industry-admin
42
- user (`sarah`) under a unique email derived from `runSuffix`, and
43
- confirm them so they can sign in. Both the signup and the confirm
44
- endpoints require root scope.
45
-
46
- ```typescript
47
- ctx.rootSession = await createBootstrapSession({
48
- baseUrl: process.env.ALVERA_BASE_URL!,
49
- email: process.env.ALVERA_ROOT_EMAIL!,
50
- password: process.env.ALVERA_ROOT_PASSWORD!,
51
- })
52
- ctx.rootApi = createIsolatedPlatformApi({
53
- baseUrl: process.env.ALVERA_BASE_URL!,
54
- sessionToken: ctx.rootSession.sessionToken,
55
- apiKey: '',
56
- })
57
-
58
- ctx.sarahEmail = `cookbook-sarah-${runSuffix}@dev.local`
59
- ctx.sarahPassword = 'CookbookPass1!'
60
-
61
- const signUpResp = await ctx.rootApi.admin.signUp({
62
- email: ctx.sarahEmail,
63
- password: ctx.sarahPassword,
64
- first_name: 'Cookbook',
65
- last_name: 'Sarah',
66
- })
67
- ctx.sarahUserId = signUpResp.data.id
68
- await ctx.rootApi.admin.confirmUser(ctx.sarahUserId)
69
- ```
70
-
71
- ## 002 — sarah signs in (tenantless) and creates the payments tenant
72
-
73
- Sarah signs in for the first time without a tenant scope (no
74
- tenant exists yet for her), then immediately creates a fresh
75
- payments tenant. The tenant's server-derived `slug` is
76
- captured into the closure-scoped `tenantSlug` slot for downstream
77
- steps.
78
-
79
- ```typescript
80
- ctx.sarahTenantlessSession = await createBootstrapSession({
81
- baseUrl: process.env.ALVERA_BASE_URL!,
82
- email: ctx.sarahEmail,
83
- password: ctx.sarahPassword,
84
- })
85
- ctx.sarahTenantlessApi = createIsolatedPlatformApi({
86
- baseUrl: process.env.ALVERA_BASE_URL!,
87
- sessionToken: ctx.sarahTenantlessSession.sessionToken,
88
- apiKey: '',
89
- })
90
-
91
- const tenantResp = await ctx.sarahTenantlessApi.tenants.create({
92
- name: `Cookbook Payments ${runSuffix}`,
93
- })
94
- tenantSlug = tenantResp.data.slug!
95
- ```
96
-
97
- ## 003 — sarah signs in tenant-scoped — the canonical client
98
-
99
- Re-mint sarah's session with the new tenant slug. This bearer is
100
- the canonical tenant-scoped client every subsequent step uses; it
101
- is assigned to the closure-scoped `api` slot so the scenario
102
- cookbook's steps inherit it via inlining.
103
-
104
- ```typescript
105
- // A tenant-scoped login requires X-API-Key — mint a public_api key for
106
- // the fresh tenant via the platform-admin side door (root Bearer). In the
107
- // web console this is Settings -> API Keys; any of the tenant's keys
108
- // satisfies the login gate, and the minted Bearer inherits this key's
109
- // origin policy.
110
- const { data: mintedKey } = await ctx.rootApi.admin.createTenantApiKey(tenantSlug, {
111
- name: `Cookbook Bootstrap Key ${runSuffix}`,
112
- data_access_mode: 'unregulated',
113
- })
114
- ctx.tenantApiKey = mintedKey.api_key
115
-
116
- const sarahTenantSession = await createSession({
117
- baseUrl: process.env.ALVERA_BASE_URL!,
118
- email: ctx.sarahEmail,
119
- password: ctx.sarahPassword,
120
- tenantSlug,
121
- apiKey: ctx.tenantApiKey,
122
- })
123
- api = createIsolatedPlatformApi({
124
- baseUrl: process.env.ALVERA_BASE_URL!,
125
- sessionToken: sarahTenantSession.sessionToken,
126
- apiKey: ctx.tenantApiKey,
127
- })
128
- ```
129
-
130
- ## 004 — sarah creates the payments datalake
131
-
132
- Provision a payments-domain datalake on the new tenant. The
133
- DB schemas are scoped to the cookbook run via `runSuffix` so
134
- parallel cookbook runs do not collide on schema names. Local-dev
135
- defaults (`postgres` on `localhost:5432`, `alvera_dev_payments`)
136
- match the seeded `dev.exs` setup; LocalStack S3
137
- (`localhost:4566`) serves the unregulated + regulated buckets.
138
- The datalake's server-derived `slug` is captured into
139
- `datalakeSlug`.
140
-
141
- ```typescript
142
- const DB_HOST = 'localhost'
143
- const DB_PORT = 5432
144
- const DB_USER = 'postgres'
145
- const DB_PASS = 'postgres'
146
- const DB_NAME = 'alvera_dev_payments'
147
- const UNREG_SCHEMA = `cookbook_${runSuffix}_unreg`
148
- const REG_SCHEMA = `cookbook_${runSuffix}_reg`
149
-
150
- const S3 = {
151
- cloud_storage_type: 'aws' as const,
152
- region: 'us-east-1',
153
- access_key_id: 'test',
154
- secret_access_key: 'test',
155
- endpoint: 'http://localhost:4566',
156
- }
157
-
158
- const datalakeResp = await api.datalakes.create(tenantSlug, {
159
- name: `Cookbook Payments Datalake ${runSuffix}`,
160
- description: 'Payments datalake provisioned by cookbook doctest.',
161
- data_domain: 'payments',
162
- timezone: 'America/New_York',
163
- pool_size: 5,
164
-
165
- unregulated_db_writer_host: DB_HOST,
166
- unregulated_db_writer_port: DB_PORT,
167
- unregulated_db_writer_name: DB_NAME,
168
- unregulated_db_writer_schema: UNREG_SCHEMA,
169
- unregulated_db_writer_auth_method: 'password',
170
- unregulated_db_writer_user: DB_USER,
171
- unregulated_db_writer_pass: DB_PASS,
172
- unregulated_db_writer_enable_ssl: false,
173
- unregulated_db_reader_host: DB_HOST,
174
- unregulated_db_reader_port: DB_PORT,
175
- unregulated_db_reader_name: DB_NAME,
176
- unregulated_db_reader_schema: UNREG_SCHEMA,
177
- unregulated_db_reader_auth_method: 'password',
178
- unregulated_db_reader_user: DB_USER,
179
- unregulated_db_reader_pass: DB_PASS,
180
- unregulated_db_reader_enable_ssl: false,
181
-
182
- regulated_data_db_writer_host: DB_HOST,
183
- regulated_data_db_writer_port: DB_PORT,
184
- regulated_data_db_writer_name: DB_NAME,
185
- regulated_data_db_writer_schema: REG_SCHEMA,
186
- regulated_data_db_writer_auth_method: 'password',
187
- regulated_data_db_writer_user: DB_USER,
188
- regulated_data_db_writer_pass: DB_PASS,
189
- regulated_data_db_writer_enable_ssl: false,
190
- regulated_data_db_reader_host: DB_HOST,
191
- regulated_data_db_reader_port: DB_PORT,
192
- regulated_data_db_reader_name: DB_NAME,
193
- regulated_data_db_reader_schema: REG_SCHEMA,
194
- regulated_data_db_reader_auth_method: 'password',
195
- regulated_data_db_reader_user: DB_USER,
196
- regulated_data_db_reader_pass: DB_PASS,
197
- regulated_data_db_reader_enable_ssl: false,
198
-
199
- unregulated_cloud_storage: { ...S3, bucket: 'payments-lake-unregulated' },
200
- regulated_cloud_storage: { ...S3, bucket: 'payments-lake-regulated' },
201
- })
202
- datalakeSlug = datalakeResp.data.slug!
203
- // `tools.create` and a few other endpoints accept the datalake by
204
- // UUID in the request body rather than only the slug in the URL,
205
- // so keep it on `ctx` for downstream steps that need it.
206
- ctx.datalakeId = datalakeResp.data.id!
207
- ```
208
-
209
- ## 005 — enqueue the datalake migrations
210
-
211
- `datalakes.create` persists the datalake row in `:new` status but
212
- does not itself run the schema migrations. Migration is a
213
- separately-triggered async job so the operator controls when the
214
- (potentially slow) per-industry DDL runs. `datalakes.migrate`
215
- enqueues the `DatalakeMigrationWorker` Oban job and returns
216
- immediately with `status: 'enqueued'` plus the Oban `job_id`. The
217
- poll in §006 then waits for that worker to finish — without this
218
- call the datalake would sit at `:new` forever.
219
-
220
- ```typescript
221
- const migrateResp = await api.datalakes.migrate(tenantSlug, datalakeSlug)
222
- if (migrateResp.data.status !== 'enqueued') {
223
- throw new Error(
224
- `datalake migration not enqueued (status: ${migrateResp.data.status})`,
225
- )
226
- }
227
- ```
228
-
229
- ## 006 — poll the datalake until status is ready
230
-
231
- Datalake migrations are async — the §005 migrate call enqueued a
232
- `DatalakeMigrationWorker` Oban job that runs the per-industry
233
- schema migrations (the regulated payment-account, compliance-
234
- screening, and legal-entity tables and their join tables),
235
- deploys PostgREST roles, and provisions the service-account bot
236
- user. Downstream resource creates (tools, workflows) only need
237
- `datalakeId` to be persisted (which it is the moment §004
238
- returns), but any scenario step that ingests into the
239
- regulated/unregulated DBs or runs a dataset search requires
240
- `status: 'ready'`. Polling here makes every payments scenario
241
- cookbook deterministic regardless of how long the cold migration
242
- takes on a given host; the loop exits the moment the datalake
243
- reaches `:ready`, so the 5-minute cap is paid only in failure
244
- mode.
245
-
246
- ```typescript
247
- const READY_TIMEOUT_MS = 5 * 60_000
248
- const READY_POLL_MS = 5_000
249
- const deadline = Date.now() + READY_TIMEOUT_MS
250
- let datalakeStatus: string | undefined
251
- while (Date.now() < deadline) {
252
- const { data } = await api.datalakes.get(tenantSlug, ctx.datalakeId)
253
- datalakeStatus = data.status
254
- if (datalakeStatus === 'ready') break
255
- await new Promise((r) => setTimeout(r, READY_POLL_MS))
256
- }
257
- if (datalakeStatus !== 'ready') {
258
- throw new Error(
259
- `datalake did not reach :ready within ${READY_TIMEOUT_MS}ms (last status: ${datalakeStatus})`,
260
- )
261
- }
262
- ```
263
-
264
- ## 007 — a reusable "wait until the run has fired" helper
265
-
266
- `workflows.run` only **schedules** a run. It records it and returns
267
- immediately with `workflow_run_id` / `status` / `scheduled_at`; the
268
- `workflow_run_log_id` and `batch_id` a scenario needs are written
269
- later, when the run actually fires, and are read back from
270
- `workflowRuns.get`.
271
-
272
- Two traps live in that gap, so every payments scenario shares one
273
- helper rather than re-deriving it:
274
-
275
- - **Poll until `workflow_run_log_id` is a string — NOT until `status`
276
- leaves `'scheduled'`.** Those are different moments: the run reaches
277
- `processing` first and writes the log id a beat later. A predicate on
278
- status alone releases you to read a `null`, and because
279
- `typeof null === 'object'` the symptom is a baffling *"expected
280
- string, got object"* rather than an obvious nil.
281
- - **Raise on `failed` carrying `failure_reason` rather than polling to
282
- the deadline.** A scenario blocked on a run that will never fire
283
- should say why on the first read, not thirty seconds later behind a
284
- generic timeout.
285
-
286
- The helper is stored on the shared `ctx` bag rather than declared as a
287
- plain function because each numbered step compiles into its own `it()`
288
- block — a bare `function` here would not be in scope for the steps that
289
- call it.
290
-
291
- ```typescript
292
- const FIRED_TIMEOUT_MS = 60_000
293
- const FIRED_POLL_MS = 1_000
294
-
295
- ctx.waitForFiredRun = async (
296
- runDatalakeSlug: string,
297
- runId: string,
298
- timeoutMs: number = FIRED_TIMEOUT_MS,
299
- ): Promise<{ workflowRunLogId: string; batchId: string | null }> => {
300
- const deadline = Date.now() + timeoutMs
301
- let lastStatus: string | undefined
302
- while (Date.now() < deadline) {
303
- const { data } = await api.workflowRuns.get(tenantSlug, runDatalakeSlug, runId)
304
- lastStatus = data.status
305
- if (data.status === 'failed') {
306
- throw new Error(
307
- `workflow run ${runId} failed: ${data.failure_reason ?? 'no failure_reason given'}`,
308
- )
309
- }
310
- if (typeof data.workflow_run_log_id === 'string') {
311
- return { workflowRunLogId: data.workflow_run_log_id, batchId: data.batch_id ?? null }
312
- }
313
- await new Promise((r) => setTimeout(r, FIRED_POLL_MS))
314
- }
315
- throw new Error(
316
- `workflow run ${runId} never fired within ${timeoutMs}ms (last status: ${lastStatus})`,
317
- )
318
- }
319
- ```
320
-
321
- # Rollback
322
-
323
- The cookbook doctest harness does not currently tear down the
324
- created tenant / datalake / user — each run mints fresh names via
325
- `runSuffix` so reruns do not collide, and the seeded local DB is
326
- cheap to reset (`mix ecto.reset` on the platform). When the
327
- validator graduates to continuous integration at Milestone 18
328
- (running against the mockoon-backed mock env at `ALVERA_ENV=mock`),
329
- no real state is created at all.
330
-
331
- # Outcome
332
-
333
- After this setup runs, the closure-scoped slots are populated as:
334
-
335
- - `api` — a tenant-scoped `PlatformApi` client authenticated as
336
- the industry-admin user
337
- - `tenantSlug` — server-derived slug of the fresh payments
338
- tenant
339
- - `datalakeSlug` — server-derived slug of the fresh payments
340
- datalake
341
- - `ctx.sarahEmail` / `ctx.sarahPassword` — the per-run admin
342
- credentials (kept on `ctx` because no scenario cookbook needs
343
- to re-authenticate by default)
344
- - `ctx.tenantApiKey` — the tenant's publishable API key (minted in
345
- setup via `admin.createTenantApiKey`); thread it into any
346
- additional tenant-scoped `createSession` a scenario performs
347
- - `ctx.waitForFiredRun(datalakeSlug, runId)` — polls `workflowRuns.get`
348
- until a scheduled run has actually fired, then returns its
349
- `{ workflowRunLogId, batchId }`. Use it after every `workflows.run`;
350
- reading `workflow_run_log_id` off the run response returns `null`
351
- because run-workflow only schedules (see §007)
352
-
353
- Scenario cookbooks under `industry: payments` start at their
354
- own `§001` with these slots already populated.
355
-
356
- # See also
357
-
358
- - `.agent/datalakes.md` — datalake create body shape (regulated +
359
- unregulated tier configuration)
360
- - `.agent/AGENTS.md` § SDK auth + client construction — root vs
361
- tenantless vs tenant-scoped session scopes
362
- - `integration-tests/tests/payments/bootstrap.test.ts` — the
363
- green test these snippets are lifted from (the sibling datalake
364
- creation and the sanity probes are out of scope for this lean
365
- cookbook setup)