@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,302 +0,0 @@
1
- ---
2
- title: "Capability: load a whole file of records in one upload"
3
- summary: A capability walk for bulk file ingestion. Stand up an ingestion chain (data source → manual-upload tool → interop contract → DAC), mint a presigned upload URL (`api.datalakes.createUploadLink`), PUT a CSV to it, enqueue the file (`api.dataActivationClients.ingestFile`), discover the batch the worker allocates by diffing the logs, and verify every row landed (`api.datasets.search`). For backfills and exports — not row-at-a-time.
4
- industry: subscription
5
- slug: bulk-ingest
6
- vitest_source:
7
- - integration-tests/tests/subscription/run-dac-bulk.test.ts
8
- - integration-tests/tests/subscription/create-dac.test.ts
9
- - integration-tests/tests/subscription/interoperability-contracts.test.ts
10
- - integration-tests/tests/subscription/tools.test.ts
11
- - integration-tests/tests/subscription/data-sources.test.ts
12
- - integration-tests/tests/subscription/bootstrap.test.ts
13
- status: green
14
- ---
15
-
16
- # Capability
17
-
18
- **What you get:** a whole file of records — a CSV export, a backfill — turned
19
- into dataset rows in one upload, instead of one `.ingest` call per row.
20
-
21
- The bulk path is three calls plus a discovery step:
22
-
23
- 1. `datalakes.createUploadLink(...)` → a presigned PUT URL + a storage `key`.
24
- 2. Raw `fetch` PUT of the file bytes to that URL (**plain HTTP, not the SDK**).
25
- 3. `dataActivationClients.ingestFile(..., { key })` → enqueues a background job.
26
- 4. The worker reads the file, allocates a fresh `batch_id`, and fans out
27
- per-row jobs. The `batch_id` is **not returned** — you discover it by diffing
28
- the DAC's logs before vs. after.
29
-
30
- It runs through the same data-activation chain as inline `.ingest`; only the
31
- entry point differs. This is **global** to every datalake. See
32
- `data_activation_clients.md` §6.2 for the reference.
33
-
34
- # Walkthrough
35
-
36
- The `_setup/subscription.md` bootstrap left `api`, `tenantSlug`,
37
- `datalakeSlug`, and `ctx.datalakeId` populated. Steps 001–004 build the same
38
- ingestion chain any AR cookbook uses; 005–008 are the bulk-specific part.
39
-
40
- ## 001 — create the data source
41
-
42
- The chain's first link registers where the rows originate. Its `uri` flows into
43
- each ingested row's `source_uri`.
44
-
45
- ```typescript
46
- const { data: ds } = await api.dataSources.create(tenantSlug, datalakeSlug, {
47
- name: `Bulk Stripe Source ${runSuffix}`,
48
- uri: 'stripe.example.com',
49
- description: 'Stripe billing export — origin of the bulk customer CSV.',
50
- status: 'active',
51
- is_default: false,
52
- })
53
- dataSourceId = ds.id!
54
- ```
55
-
56
- ## 002 — create the manual-upload tool
57
-
58
- A `manual_upload` tool backs both the inline and the file-upload ingest paths —
59
- the rows arrive in the upload, not by the tool fetching them.
60
-
61
- ```typescript
62
- const { data: tool } = await api.tools.create(tenantSlug, datalakeSlug, {
63
- name: `Bulk Manual Upload Tool ${runSuffix}`,
64
- description: 'Manual-upload data-exchange tool backing the bulk-ingest DAC.',
65
- intent: 'data_exchange',
66
- status: 'active',
67
- datalake_id: ctx.datalakeId,
68
- data_source_id: dataSourceId,
69
- body: { tool_body_type: 'manual_upload' },
70
- })
71
- ctx.manualUploadToolId = tool.id!
72
- ```
73
-
74
- ## 003 — create the customer interoperability contract
75
-
76
- The contract maps each inbound CSV row into a `Customer` upsert. Load the
77
- production Stripe customer + MDM Liquid templates from the vendored fixtures
78
- rather than inlining them.
79
-
80
- ```typescript
81
- const { readFileSync } = await import('node:fs')
82
- const { join } = await import('node:path')
83
- const customerTemplate = readFileSync(
84
- join(process.env.COOKBOOK_FIXTURES_DIR!, 'subscription/_customers_subscription_customer.liquid'),
85
- 'utf8',
86
- )
87
- const mdmTemplate = readFileSync(
88
- join(process.env.COOKBOOK_FIXTURES_DIR!, 'subscription/_customers_subscription_mdm.liquid'),
89
- 'utf8',
90
- )
91
-
92
- const { data: contract } = await api.interoperabilityContracts.create(tenantSlug, datalakeSlug, {
93
- name: `Bulk Stripe Customer Contract ${runSuffix}`,
94
- description: 'Stripe customers → Subscription Customer (custom Liquid + MDM input).',
95
- resource_type: 'customer',
96
- template_config: { type: 'custom', body: customerTemplate },
97
- mdm_input_config: { type: 'custom', body: mdmTemplate },
98
- generic_table_id: null,
99
- })
100
- interopContractId = contract.id!
101
- ```
102
-
103
- ## 004 — create the manual-upload DAC
104
-
105
- The Data Activation Client binds tool + data source + contract into one
106
- ingestion endpoint. Its server-derived `slug` is the handle the file is ingested
107
- against.
108
-
109
- ```typescript
110
- const { data: dac } = await api.dataActivationClients.create(tenantSlug, datalakeSlug, {
111
- name: `Bulk Stripe Customer DAC ${runSuffix}`,
112
- description: 'Manual-upload DAC — ingests the bulk Stripe customer CSV.',
113
- tool_id: ctx.manualUploadToolId,
114
- data_source_id: dataSourceId,
115
- tool_call: { tool_call_type: 'manual_upload' },
116
- interoperability_contract_ids: [interopContractId],
117
- })
118
- dacId = dac.id!
119
- ctx.dacSlug = dac.slug!
120
- ```
121
-
122
- ## 005 — mint a presigned upload URL and PUT the CSV
123
-
124
- `createUploadLink` returns a presigned PUT `url` and the storage `key` the
125
- platform will reference. Upload the file body to the URL with a **raw `fetch`** —
126
- this PUT goes straight to object storage, **not** through the SDK. The CSV is the
127
- vendored 4-row Stripe customer export.
128
-
129
- ```typescript
130
- const { readFileSync } = await import('node:fs')
131
- const { join } = await import('node:path')
132
- const csvBody = readFileSync(
133
- join(process.env.COOKBOOK_FIXTURES_DIR!, 'subscription/stripe_customers_batch1.csv'),
134
- 'utf8',
135
- )
136
-
137
- const { data: link } = await api.datalakes.createUploadLink(tenantSlug, datalakeSlug, {
138
- content_type: 'text/csv',
139
- filename: 'stripe_customers_batch1.csv',
140
- })
141
- ctx.uploadKey = link.key!
142
-
143
- const put = await fetch(link.url!, {
144
- method: 'PUT',
145
- headers: { 'Content-Type': 'text/csv' },
146
- body: csvBody,
147
- })
148
- if (put.status !== 200) {
149
- throw new Error(`presigned PUT failed: ${put.status}`)
150
- }
151
- ```
152
-
153
- ## 006 — snapshot the logs, then enqueue the file
154
-
155
- `ingestFile` is fully async — it enqueues a background job and returns a
156
- `job_id`, but **no `batch_id`** (the worker allocates that later). Snapshot the
157
- existing log batch ids first, so step 007 can spot the new one the worker
158
- creates.
159
-
160
- ```typescript
161
- const { data: pre } = await api.dataActivationClients.logs.list(tenantSlug, datalakeSlug, ctx.dacSlug)
162
- ctx.preBatchIds = (pre.data ?? [])
163
- .map((r) => (r as { batch_id?: string }).batch_id)
164
- .filter((b): b is string => typeof b === 'string')
165
-
166
- const { data: job } = await api.dataActivationClients.ingestFile(tenantSlug, datalakeSlug, ctx.dacSlug, {
167
- key: ctx.uploadKey,
168
- })
169
- if (!job.job_id) {
170
- throw new Error('ingestFile did not return a job_id')
171
- }
172
- ```
173
-
174
- ## 007 — discover the batch the worker allocated
175
-
176
- Poll the logs until a **new** batch id (one not in the pre-snapshot) shows a
177
- fully-merged customer ingest: `rows_ingested >= 4` (the four CSV rows) and a
178
- non-empty `output_files` array (the merged data landed in storage).
179
-
180
- ```typescript
181
- const pre = new Set<string>(ctx.preBatchIds)
182
- const deadline = Date.now() + 60_000
183
- let batchId: string | null = null
184
- while (Date.now() < deadline && !batchId) {
185
- const { data } = await api.dataActivationClients.logs.list(tenantSlug, datalakeSlug, ctx.dacSlug)
186
- for (const row of (data.data ?? []) as Array<Record<string, unknown>>) {
187
- const b = row.batch_id
188
- if (typeof b !== 'string' || pre.has(b)) continue
189
- if (row.dataset_table !== 'customers') continue
190
- if (typeof row.rows_ingested !== 'number' || row.rows_ingested < 4) continue
191
- if (!Array.isArray(row.output_files) || row.output_files.length === 0) continue
192
- batchId = b
193
- break
194
- }
195
- if (!batchId) await new Promise((r) => setTimeout(r, 1_000))
196
- }
197
- if (!batchId) {
198
- throw new Error('bulk ingest did not produce a fully-merged batch within 60s')
199
- }
200
- ctx.batchId = batchId
201
- ```
202
-
203
- ## 008 — verify every row landed
204
-
205
- Search the `customer` dataset scoped to that batch. `createUserSearch` compiles
206
- the query (`ra` is the customer dataset alias); `search` returns the rows once
207
- they materialize — poll until all four appear.
208
-
209
- ```typescript
210
- const { data: userSearch } = await api.datasets.createUserSearch(tenantSlug, datalakeSlug, 'customer', {
211
- search_query: `ra.batch_id = '${ctx.batchId}'`,
212
- })
213
- if (userSearch.status !== 'completed') {
214
- throw new Error(`customer user-search did not compile: ${userSearch.status}`)
215
- }
216
-
217
- const deadline = Date.now() + 90_000
218
- let rows: Array<Record<string, unknown>> = []
219
- while (Date.now() < deadline && rows.length < 4) {
220
- const { data: page } = await api.datasets.search(tenantSlug, datalakeSlug, 'customer', {
221
- userSearchId: userSearch.id!,
222
- dataAccessMode: 'unregulated',
223
- })
224
- rows = (page.data ?? []) as Array<Record<string, unknown>>
225
- if (rows.length < 4) await new Promise((r) => setTimeout(r, 1_000))
226
- }
227
- if (rows.length < 4) {
228
- throw new Error(`expected 4 ingested customers, found ${rows.length} within 90s`)
229
- }
230
- ```
231
-
232
- ## 009 — write the integration test
233
-
234
- End the build with a test you keep: run the bulk path in miniature —
235
- mint a link, PUT a tiny inline CSV, enqueue it — and assert each call's
236
- own acknowledgement. The probe rows are `test-`-prefixed so they are
237
- unmistakably synthetic wherever they surface, and the test asserts the
238
- enqueue ack (`job_id`), never synchronous merge completion — the worker
239
- owns that. This block runs live under `make validate-cookbook`.
240
-
241
- ```typescript
242
- // Re-GET — the client must still be there and carry its ingest slug.
243
- const { data: dacRow } = await api.dataActivationClients.get(tenantSlug, datalakeSlug, dacId)
244
- if (dacRow.slug !== ctx.dacSlug) {
245
- throw new Error(`DAC slug drifted on read-back: ${dacRow.slug}`)
246
- }
247
- // Behavioural probe — the three-step bulk path, each step asserted on
248
- // its own response.
249
- const probeCsv =
250
- 'customer_number,customer_type,status,name,email,currency\n' +
251
- `test-PROBE-1-${runSuffix},individual,contracted,test-Grace Hopper,test-grace-${runSuffix}@example.com,USD\n`
252
- const { data: probeLink } = await api.datalakes.createUploadLink(tenantSlug, datalakeSlug, {
253
- content_type: 'text/csv',
254
- filename: `test-bulk-probe-${runSuffix}.csv`,
255
- })
256
- if (!probeLink.url || !probeLink.key) {
257
- throw new Error('createUploadLink returned no url/key')
258
- }
259
- const probePut = await fetch(probeLink.url, {
260
- method: 'PUT',
261
- headers: { 'Content-Type': 'text/csv' },
262
- body: probeCsv,
263
- })
264
- if (probePut.status !== 200) {
265
- throw new Error(`presigned PUT failed: ${probePut.status}`)
266
- }
267
- const { data: probeJob } = await api.dataActivationClients.ingestFile(tenantSlug, datalakeSlug, ctx.dacSlug, {
268
- key: probeLink.key,
269
- })
270
- // job_id is a NUMBER on the wire (an Oban job id), status "scheduled".
271
- if (probeJob.job_id === undefined || probeJob.job_id === null) {
272
- throw new Error('ingestFile probe returned no job_id')
273
- }
274
- ```
275
-
276
- If the probe fails in production, escalate with the failing call's
277
- response — don't retry the enqueue in a loop or reach into the worker's
278
- storage to "help it along".
279
-
280
- # Gotchas
281
-
282
- - **The file PUT is raw HTTP, not the SDK.** `createUploadLink` and `ingestFile`
283
- are SDK calls; the upload itself is a plain `fetch` PUT to the presigned URL.
284
- Don't try to route the bytes through an `api.*` method.
285
- - **`ingestFile` does NOT return the `batch_id`.** It returns only a `job_id`.
286
- The worker allocates the `batch_id` when it reads the file — discover it by
287
- diffing the DAC logs before vs. after (the snapshot in 006).
288
- - **Wait for merge, not just for rows.** A log row with `rows_ingested > 0` but
289
- `output_files: []` is mid-merge — gate on `output_files.length > 0` before
290
- trusting the batch.
291
- - **Search is a two-call, poll-until-ready pattern.** `createUserSearch` compiles
292
- the query and returns `status: 'completed'`; the rows still take a moment to
293
- materialize, so poll `search` until they appear.
294
-
295
- # See also
296
-
297
- - `data_activation_clients.md` §6.2 — `.ingestFile` reference + the bulk flow
298
- - `datalakes.md` — `createUploadLink` (presigned upload) reference
299
- - `_setup/subscription.md` — the bootstrap this walk starts from
300
- - `_fixtures/subscription/` — the vendored Stripe customer CSV + Liquid
301
- - `integration-tests/tests/subscription/run-dac-bulk.test.ts` — the green
302
- test these calls are lifted from