@alvera-ai/platform-sdk 0.10.0-rc.8 → 0.11.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 (66) hide show
  1. package/.agent/AGENTS.md +499 -0
  2. package/.agent/account_management.md +456 -0
  3. package/.agent/action_status_updaters.md +264 -0
  4. package/.agent/ai_agents.md +462 -0
  5. package/.agent/ai_sandbox.md +265 -0
  6. package/.agent/async.md +112 -0
  7. package/.agent/connected_apps.md +408 -0
  8. package/.agent/cookbook/_fixtures/README.md +106 -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/accounts_receivable/stripe_customers_batch1.csv +5 -0
  12. package/.agent/cookbook/_fixtures/foundation/_lead_submissions_foundation_generic_table.liquid +33 -0
  13. package/.agent/cookbook/_fixtures/foundation/_lead_submissions_foundation_legal_entity.liquid +88 -0
  14. package/.agent/cookbook/_fixtures/foundation/_lead_submissions_foundation_mdm.liquid +48 -0
  15. package/.agent/cookbook/_fixtures/healthcare/_cahps_appointments_healthcare_appointment.liquid +47 -0
  16. package/.agent/cookbook/_fixtures/healthcare/_cahps_appointments_healthcare_mdm.liquid +24 -0
  17. package/.agent/cookbook/_fixtures/healthcare/_cahps_appointments_healthcare_patient.liquid +38 -0
  18. package/.agent/cookbook/_fixtures/healthcare/memorandum-of-association-01.png +0 -0
  19. package/.agent/cookbook/_fixtures/healthcare/sample_two_page.pdf +43 -0
  20. package/.agent/cookbook/_fixtures/payment_risk/_compliance_screenings_payment_risk_compliance_screening.liquid +59 -0
  21. package/.agent/cookbook/_fixtures/payment_risk/_compliance_screenings_payment_risk_mdm.liquid +36 -0
  22. package/.agent/cookbook/_fixtures/payment_risk/_payment_accounts_payment_risk_mdm.liquid +30 -0
  23. package/.agent/cookbook/_fixtures/payment_risk/_payment_accounts_payment_risk_payment_account.liquid +55 -0
  24. package/.agent/cookbook/_setup/accounts_receivable.md +282 -0
  25. package/.agent/cookbook/_setup/foundation.md +277 -0
  26. package/.agent/cookbook/_setup/healthcare.md +279 -0
  27. package/.agent/cookbook/_setup/payment_risk.md +283 -0
  28. package/.agent/cookbook/action-status-updaters.md +212 -0
  29. package/.agent/cookbook/ai-agent-invoke.md +243 -0
  30. package/.agent/cookbook/appointment-review-sms-workflow.md +761 -0
  31. package/.agent/cookbook/birthday-greeting-sms-trigger.md +656 -0
  32. package/.agent/cookbook/bulk-ingest.md +254 -0
  33. package/.agent/cookbook/contact-us-triage-with-llm.md +622 -0
  34. package/.agent/cookbook/custom-tables.md +201 -0
  35. package/.agent/cookbook/dunning-sms-for-delinquent.md +619 -0
  36. package/.agent/cookbook/invite-team.md +194 -0
  37. package/.agent/cookbook/kyc-notification-on-account-activation.md +619 -0
  38. package/.agent/cookbook/rest-fetch.md +246 -0
  39. package/.agent/cookbook/sanctions-screening-with-agent-review.md +733 -0
  40. package/.agent/cookbook/score-leads-with-llm-categorization.md +624 -0
  41. package/.agent/cookbook/system-templates.md +129 -0
  42. package/.agent/cookbook/triage-prospects-by-priority.md +533 -0
  43. package/.agent/cookbook/welcome-sms-for-customers.md +607 -0
  44. package/.agent/data_activation_clients.md +559 -0
  45. package/.agent/data_sources.md +235 -0
  46. package/.agent/datalakes.md +714 -0
  47. package/.agent/debugging.md +137 -0
  48. package/.agent/errors.md +190 -0
  49. package/.agent/generic_tables.md +351 -0
  50. package/.agent/interoperability_contracts.md +417 -0
  51. package/.agent/mdm.md +293 -0
  52. package/.agent/mutations.md +126 -0
  53. package/.agent/templates.md +98 -0
  54. package/.agent/tool-call-configs.md +90 -0
  55. package/.agent/tools.md +547 -0
  56. package/.agent/type_naming.md +129 -0
  57. package/.agent/workflows.md +617 -0
  58. package/README.md +178 -0
  59. package/dist/bin/platform-sdk.d.mts +1 -0
  60. package/dist/bin/platform-sdk.mjs +106 -0
  61. package/dist/bin/platform-sdk.mjs.map +1 -0
  62. package/dist/index.d.mts +1310 -44116
  63. package/dist/index.d.mts.map +1 -1
  64. package/dist/index.mjs +1194 -7344
  65. package/dist/index.mjs.map +1 -1
  66. package/package.json +19 -10
@@ -0,0 +1,417 @@
1
+ # Interoperability contracts
2
+
3
+ An **interoperability contract** is the transformation step between
4
+ a raw inbound row (CSV, JSON, etc.) and a typed datalake resource.
5
+ Each contract carries:
6
+
7
+ - a `resource_type` (the target shape — `patient`, `customer`,
8
+ `payment_account`, `legal_entity`, `generic_table`, etc.)
9
+ - a `template_config` (the Liquid template that renders the inbound
10
+ row into the target shape)
11
+ - an `mdm_input_config` (the Liquid template that renders the
12
+ matching subject identity for entity resolution — see `mdm.md`
13
+ for the full MDM surface, including the `api.mdm.verify` verb
14
+ invoked at workflow action time)
15
+ - an optional `filter_template` (a Liquid pre-filter that decides
16
+ whether a row enters the pipeline at all)
17
+
18
+ SDK namespace: `api.interoperabilityContracts`.
19
+
20
+ The contract is **Datalake-DB-resident** — `create()` requires the
21
+ parent datalake at `status: 'ready'` (see `datalakes.md` §5).
22
+
23
+ ## 1. Wire shape
24
+
25
+ ```typescript
26
+ import type {
27
+ InteroperabilityContractRequestWritable,
28
+ InteroperabilityContractResponse,
29
+ } from '@alvera-ai/platform-sdk'
30
+
31
+ const { data: created } = await api.interoperabilityContracts.create(
32
+ tenantSlug,
33
+ datalakeSlug,
34
+ {
35
+ name: 'Inbound CRM → Customer',
36
+ description: 'Vendor CRM rows → AccountsReceivable Customer',
37
+ resource_type: 'customer',
38
+ filter_template: undefined, // optional
39
+ template_config: { type: 'custom', body: liquidBody },
40
+ mdm_input_config: { type: 'custom', body: mdmBody },
41
+ generic_table_id: null, // see §2
42
+ },
43
+ )
44
+ // created.id, created.slug — server-derived
45
+ // created.type — mirrors template_config.type
46
+ // created.resource_type === body.resource_type
47
+ ```
48
+
49
+ Both polymorphic embeds use the SAME shared `TemplateConfig`
50
+ shape with discriminator `type`:
51
+
52
+ ```
53
+ template_config.type 'system' | 'custom' | 'identity' | 'null'
54
+ mdm_input_config.type 'system' | 'custom' | 'identity' | 'null'
55
+ ```
56
+
57
+ Default for both is `'null'`. The wire accepts all four values
58
+ on each surface; semantic narrowing is a runtime concern, not
59
+ a schema one:
60
+
61
+ - `custom` — caller-supplied `body` string (Liquid template).
62
+ Pair with `body`; leave `path` null.
63
+ - `system` — references a platform-shipped template by
64
+ filesystem path. Pair with `path` (required); leave `body`
65
+ null. See `templates.md`.
66
+ - `identity` — pass-through. The inbound row is forwarded
67
+ unchanged. Used by the auto-created identity contracts for
68
+ generic tables. Leave both `body` and `path` null.
69
+ - `null` — disabled / "row IS the subject" on the mdm surface.
70
+ Leave both `body` and `path` null.
71
+
72
+ ### TemplateConfig embed fields
73
+
74
+ Both `template_config` and `mdm_input_config` are instances of
75
+ the shared `TemplateConfig` embed:
76
+
77
+ ```
78
+ type required enum — 'system' | 'custom' | 'identity' | 'null' (default 'null')
79
+ body optional string — Liquid template body; REQUIRED iff type === 'custom'
80
+ path optional string — filesystem path; REQUIRED iff type === 'system'
81
+ output_schema server-injected — JSON Schema { type: 'object' } for this surface; not caller-set
82
+ ```
83
+
84
+ Server validation enforces the body/path pairing — supplying
85
+ `body` with `type: 'system'`, or `path` with `type: 'custom'`,
86
+ returns 422.
87
+
88
+ ## 2. Rules the type cannot encode
89
+
90
+ ### `generic_table_id` is conditionally required
91
+
92
+ When `resource_type === 'generic_table'`, the body MUST include a
93
+ non-null `generic_table_id` identifying the target table. For
94
+ every other `resource_type`, the field must be `null`. The
95
+ TypeScript type permits either value; the validator and server
96
+ enforce the conditional rule.
97
+
98
+ ### `resource_type` is industry-scoped
99
+
100
+ The valid set of `resource_type` values is determined by the
101
+ parent datalake's `data_domain`. Generic concepts like
102
+ `'generic_table'` are accepted everywhere; domain-anchored
103
+ concepts (`'patient'`, `'customer'`, `'payment_account'`,
104
+ `'legal_entity'`, ...) only resolve within their owning industry.
105
+ Don't hardcode a fixed enum across industries — discover the
106
+ valid set at runtime via the platform's domain catalog (see
107
+ `datalakes.md` §5 `.systemDatasets`).
108
+
109
+ ### `filter_template` has INVERTED semantics
110
+
111
+ This is the single highest-value gotcha on this surface. A
112
+ `filter_template` is a Liquid template that decides whether a
113
+ row enters the pipeline:
114
+
115
+ - **Empty render → PASS** (row enters the pipeline).
116
+ - **Non-empty render → SKIP** (row is dropped).
117
+
118
+ This is the opposite of most filter DSLs ("truthy = keep"). A
119
+ correctly-shaped filter looks like:
120
+
121
+ ```liquid
122
+ {% if msg.row.should_drop == 'Y' %}drop{% endif %}
123
+ ```
124
+
125
+ Renders `"drop"` when the condition fires (SKIP); renders empty
126
+ otherwise (PASS). Inverting `{% unless %}...{% endunless %}` is
127
+ equivalent and stylistically common — but always anchor on the
128
+ empty-render-passes rule, not on the Liquid keyword.
129
+
130
+ If you omit `filter_template`, every row passes through.
131
+
132
+ ### `template_config` body is Liquid, with row data on `msg`
133
+
134
+ Templates receive the inbound row on the `msg` variable. The
135
+ exact accessor depends on the source shape — CSV-shaped sources
136
+ typically use `msg.<column>` (with `msg.row.<column>` available
137
+ for nested cases); JSON-shaped sources mirror the inbound JSON
138
+ under `msg.<field>`. The render output is JSON — the platform
139
+ parses the rendered string as JSON and treats it as the target
140
+ resource body.
141
+
142
+ A render that produces invalid JSON fails at sandbox-run with
143
+ `stage: 'failed'` (see §5). Always sandbox-run a contract before
144
+ binding it to a live data activation client.
145
+
146
+ ## 3. Field ownership
147
+
148
+ **Server-derived (Response-only).** Universal set from
149
+ `type_naming.md`, plus:
150
+
151
+ ```
152
+ type string — mirrors template_config.type at create time
153
+ ```
154
+
155
+ **Caller-supplied (round-trip).**
156
+
157
+ ```
158
+ name required string — agent-facing label
159
+ description optional string — narrative
160
+ resource_type required string — target shape (industry-scoped)
161
+ filter_template optional string — Liquid pre-filter (inverted semantics)
162
+ template_config required embed — { type, body?, path? }
163
+ mdm_input_config optional embed — { type, body?, path? }; omit equals { type: 'null' }
164
+ generic_table_id required UUID|null — required iff resource_type='generic_table'
165
+ ```
166
+
167
+ **Write-only (Request-only).** None — every caller-supplied field
168
+ round-trips on the response.
169
+
170
+ ## 4. Error envelopes
171
+
172
+ Standard JSON:API envelopes per `errors.md`. Common rejections:
173
+
174
+ | `source.pointer` | Cause |
175
+ |------------------------------|--------------------------------------------------|
176
+ | `/resource_type` | value not valid for the parent datalake's domain |
177
+ | `/template_config/type` | enum mismatch |
178
+ | `/template_config/body` | empty when `type === 'custom'` |
179
+ | `/mdm_input_config/type` | enum mismatch |
180
+ | `/generic_table_id` | missing when resource_type='generic_table', |
181
+ | | or non-null when not |
182
+ | `/name` | uniqueness within datalake |
183
+ | `errors.base` | "System-created contracts cannot be edited" (PUT) or "...deleted" (DELETE) — see §5 |
184
+
185
+ ## 5. Lifecycle
186
+
187
+ ### Create
188
+
189
+ Synchronous. The create response is immediately usable — no async
190
+ deployment phase. Templates are compiled lazily at sandbox-run
191
+ time and at activation-client fan-out time, so a `body`
192
+ containing invalid Liquid syntax will surface at first use, not
193
+ at create. Always sandbox-run before binding to a live data
194
+ activation client.
195
+
196
+ ### Sandbox run — `.run(slug, msg)`
197
+
198
+ The contract's templates can be exercised against a sample row
199
+ WITHOUT actually ingesting:
200
+
201
+ ```typescript
202
+ const { data: result } = await api.interoperabilityContracts.run(
203
+ tenantSlug, datalakeSlug, contractSlug,
204
+ { /* sample row, same shape as inbound CSV/JSON */ },
205
+ )
206
+ // result.stage === 'completed' | 'failed'
207
+ // result.filter_result === 'pass' | 'skip'
208
+ // result.transformed object | null — the rendered resource body
209
+ ```
210
+
211
+ Use this verb as a CI / pre-bind verification step: produce a
212
+ representative sample row, run it through `.run`, and assert
213
+ the `transformed` output shape matches the consumer's
214
+ expectations. If `filter_result === 'skip'`, the row was filtered
215
+ out before rendering — useful for verifying the filter semantics
216
+ on a known-skip example.
217
+
218
+ ### Generic-table contracts: auto-created identity contracts
219
+
220
+ When you create a generic table (see `generic_tables.md`), the
221
+ platform automatically creates an **identity contract** bound
222
+ to that table:
223
+
224
+ ```
225
+ new generic table (T) → auto-created contract with
226
+ resource_type: 'generic_table'
227
+ generic_table_id: T.id
228
+ template_config.type: 'identity'
229
+ ```
230
+
231
+ Discover this contract via `.list()` + filter on
232
+ `generic_table_id`:
233
+
234
+ ```typescript
235
+ const { data: all } = await api.interoperabilityContracts.list(
236
+ tenantSlug, datalakeSlug,
237
+ )
238
+ const identityContract = all.data?.find(
239
+ (c) => c.generic_table_id === tableId && c.type === 'identity',
240
+ )
241
+ ```
242
+
243
+ Multiple contracts MAY bind to the same generic table — one
244
+ identity (auto) plus any number of caller-created `custom`
245
+ contracts. Distinguish by `type` when iterating. The platform
246
+ enforces at most ONE identity contract per generic table;
247
+ attempts to create a second `type: 'identity'` contract for
248
+ the same table are rejected.
249
+
250
+ ### Read shapes
251
+
252
+ ```
253
+ .list(tenantSlug, datalakeSlug) Paged: { data, meta }
254
+ .get(tenantSlug, datalakeSlug, slug) One row, by slug
255
+ .metadata(tenantSlug, datalakeSlug, slug) Markdown string — one contract
256
+ .run(tenantSlug, datalakeSlug, slug, msg) Sandbox runner (above)
257
+ ```
258
+
259
+ ### Delete
260
+
261
+ `delete()` removes the contract. Contracts referenced by active
262
+ data activation clients reject with a 422 naming the dependency.
263
+
264
+ **System-created contracts cannot be edited or deleted.** The
265
+ contract record carries a `system_created` flag (server-derived).
266
+ When `system_created === true`, both `update()` and `delete()`
267
+ reject with 422 on `/base` and a literal message:
268
+
269
+ ```
270
+ PUT → "System-created contracts cannot be edited"
271
+ DELETE → "System-created contracts cannot be deleted"
272
+ ```
273
+
274
+ System-created contracts include the auto-generated identity
275
+ contracts the platform provisions when a generic table is
276
+ created (see §5 "Generic-table contracts: auto-created identity
277
+ contracts") and any industry-built templates the platform ships
278
+ with the datalake's data_domain. User-created contracts of any
279
+ `type` (including caller-created `type: 'identity'` rows bound
280
+ to a generic table) ARE editable and deletable — the gate is
281
+ `system_created`, not `type`.
282
+
283
+ The flag IS exposed on the `InteroperabilityContractResponse`,
284
+ so consumer agents can pre-filter before attempting a mutation:
285
+
286
+ ```typescript
287
+ const { data: list } = await api.interoperabilityContracts.list(
288
+ tenantSlug, datalakeSlug,
289
+ )
290
+ const editable = list.data.filter((c) => !c.system_created)
291
+ ```
292
+
293
+ ## 6. Nesting AI agents on the contract body
294
+
295
+ A contract can run AI-agent **enrichment** during ingestion. Agents
296
+ are **nested directly on the contract body** — the create/update body
297
+ accepts an `interoperability_contract_ai_agents` array.
298
+ There is no separate attach/detach endpoint, and **no per-join
299
+ checksum**: the nested agents fold into the contract's own checksum.
300
+
301
+ ```typescript
302
+ // Nest on create. `position` is REQUIRED.
303
+ const { data: contract } = await api.interoperabilityContracts.create(
304
+ tenantSlug, datalakeSlug,
305
+ {
306
+ name: 'MOA Enrichment Contract',
307
+ resource_type: 'legal_entity',
308
+ template_config: { type: 'custom', body: '{{ name }}' },
309
+ mdm_input_config: { type: 'null' },
310
+ // …other contract fields…
311
+ interoperability_contract_ai_agents: [
312
+ {
313
+ ai_agent_id: createdAgentId,
314
+ position: 0, // ordering when multiple agents fire; REQUIRED
315
+ context_mapping_config: {
316
+ type: 'custom',
317
+ // Liquid renders to a JSON object whose keys match the agent's
318
+ // input_schema; values reference the inbound row via `msg.<field>`.
319
+ body: JSON.stringify({ name: '{{ msg.company_name }}' }),
320
+ // A contract join AUTHORS output_schema — unlike a workflow
321
+ // join, the server does NOT pin it here.
322
+ output_schema: '{"type":"object","properties":{"lead":{"type":"string"}}}',
323
+ },
324
+ },
325
+ ],
326
+ },
327
+ )
328
+ contract.checksum // the PARENT checksum — the nested agents fold into it
329
+ ```
330
+
331
+ Adding, repositioning, and removing agents are all expressed by
332
+ re-supplying the full set on the contract's `update()` — `update()`
333
+ replaces the whole array, and any item you omit is removed:
334
+
335
+ | Operation | How | Result |
336
+ |------------|--------------------------------------------------|--------|
337
+ | add / nest | include in the array on create or update | contract checksum reflects the agents |
338
+ | reposition | `update()` with the same agent at a new `position`| full-set replace |
339
+ | detach | `update()` omitting that agent (or `[]` to clear all)| the omitted agent is removed |
340
+ | drift | contract `checksum()` endpoint over the full body| the would-be contract checksum (no persist) |
341
+
342
+ A contract `get()` surfaces its nested agents as
343
+ `interoperability_contract_ai_agents: [...]`. The array is writable on
344
+ the contract body (create + update).
345
+
346
+ **Runtime.** Enrichment fires during ingestion,
347
+ **not** on the sandbox `.run`. Attached agents run in `position`
348
+ order: each agent's `context_mapping_config` renders against the row,
349
+ the result is validated against the agent's `input_schema`, sent to
350
+ the bound `llm_enrichment` tool, and the parsed response is merged
351
+ under `msg.enrichment.<agent_slug>` for the contract's
352
+ `template_config` to read. So attaching an agent to a DAC-wired
353
+ contract makes ingestion run enrichment per row — keep agents off
354
+ contracts whose only job is a straight-through upsert.
355
+
356
+ See `ai_agents.md` §3 for the agent create body and the shared attach
357
+ surface, and `workflows.md` for the workflow side of the same join.
358
+
359
+ ## 7. Gotchas
360
+
361
+ 1. **`filter_template` is empty-passes, non-empty-skips.**
362
+ Already documented in §2 — repeated here because it's the
363
+ #1 source of "ingests nothing / ingests too much" silent
364
+ failures. Always anchor on this rule.
365
+
366
+ 2. **`mdm_input_config` is optional; omitting equals `{ type:
367
+ 'null' }`.** The shared TemplateConfig embed defaults `type`
368
+ to `'null'`, so a contract that omits `mdm_input_config`
369
+ behaves as "the row IS the subject — no entity resolution
370
+ runs". Declaring `{ type: 'null' }` explicitly is allowed
371
+ for readability but not required.
372
+
373
+ 3. **`resource_type` is industry-scoped.** A datalake with
374
+ `data_domain: 'foundation'` accepts `'legal_entity'` but
375
+ rejects `'patient'`. The error surfaces as
376
+ `/resource_type` 422 at create time. Don't ship a fixed
377
+ enum across industries.
378
+
379
+ 4. **Sandbox-run is not optional in practice.** A template that
380
+ compiles but renders structurally-invalid JSON, or that
381
+ silently drops fields, won't surface until live ingestion —
382
+ and at that point the failures are batched and harder to
383
+ debug. Run `.run(slug, sampleRow)` against a representative
384
+ row for every new contract before binding it to a data
385
+ activation client.
386
+
387
+ 5. **Generic-table identity contracts are auto-created; don't
388
+ create them yourself.** Creating a generic table fires a
389
+ side-effect that creates a `type: 'identity'` contract bound
390
+ to the new table. Creating a second identity contract for the
391
+ same table is rejected. To add custom transformation on top
392
+ of a generic table, create an additional `type: 'custom'`
393
+ contract bound to the same `generic_table_id`.
394
+
395
+ 6. **Templates use `msg` to access the row.** Liquid receives
396
+ the row under the `msg` variable. CSV columns are typically
397
+ `msg.<column>`; nested cases use `msg.row.<column>`. JSON
398
+ sources expose the inbound fields under `msg.<field>`. Read
399
+ any platform-shipped template (see `templates.md`) to learn
400
+ the exact convention for your row shape.
401
+
402
+ 7. **No partial updates.** `update()` replaces the whole resource
403
+ — resupply the full body (see `mutations.md`). Re-supply
404
+ `template_config` / `mdm_input_config` verbatim if not
405
+ changing them.
406
+
407
+ 8. **Custom Liquid filters are a closed allowlist of eleven.**
408
+ The platform exposes exactly eleven custom filters beyond
409
+ stock Liquid (`e164`, `age`, `date`, `to_json`,
410
+ `json_escape`, `now`, `uuid`, `parse_date`, `convert_time`,
411
+ `random`, `tz_offset`). Other filter names — including the
412
+ Shopify-style `| json` — fall through to stock Liquid and
413
+ produce malformed output silently. See `ai_sandbox.md` §2
414
+ for the full per-filter reference and the safety rationale
415
+ for the closed set. The allowlist is the same in
416
+ `template_config.body`, `mdm_input_config.body`, and
417
+ `filter_template`.