@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,601 @@
1
+ # Workflows
2
+
3
+ A **workflow** is the event-driven decision pipeline that fires on
4
+ top of ingested dataset rows. Every workflow has the same shape:
5
+
6
+ ```
7
+ dataset row event
8
+ │
9
+ ▼
10
+ filter — does this row qualify? (Liquid expression → boolean)
11
+ │
12
+ ▼
13
+ enrichment — optional AI step that derives extra context (see §3)
14
+ │
15
+ ▼
16
+ decision — which action key fires? (Liquid array of strings, OR
17
+ an AI agent's output)
18
+ │
19
+ ▼
20
+ action(s) — scheduled tool calls keyed by decision_key
21
+ (SMS, email, voice, export, ...)
22
+ ```
23
+
24
+ SDK namespace: `api.workflows`. Sub-namespaces for run-time logs:
25
+
26
+ ```
27
+ api.workflows.batchLogs — one log row per workflow run (batch)
28
+ api.workflows.workflowLogs — one log row per dataset row processed
29
+ (workflow execution logs)
30
+ ```
31
+
32
+ Workflows are **Datalake-DB-resident** — the parent datalake must
33
+ be `status: 'ready'` and every referenced resource (tools,
34
+ connected apps, AI agents) must exist before POST.
35
+
36
+ ## 1. Wire shape
37
+
38
+ ```typescript
39
+ import type {
40
+ WorkflowRequestWritable,
41
+ WorkflowResponse,
42
+ } from '@alvera-ai/platform-sdk'
43
+
44
+ const { data: created } = await api.workflows.create(
45
+ tenantSlug,
46
+ datalakeSlug,
47
+ {
48
+ name: 'Appointment Review SMS',
49
+ description: 'Send a review-request SMS after a fulfilled appointment',
50
+ dataset_type: 'appointment', // the dataset this workflow listens on
51
+ status: 'live', // 'live' | 'draft'
52
+ filter_config: {
53
+ type: 'custom',
54
+ body: `{% if appointment.source_uri == "12345.example.com" %}true{% endif %}`,
55
+ output_schema: '{"type":"boolean"}',
56
+ },
57
+ decision_config: {
58
+ type: 'custom',
59
+ body: `["send_appointment_review_sms"]`,
60
+ output_schema: '{"type":"array","items":{"type":"string"}}',
61
+ },
62
+ context_datasets: [
63
+ {
64
+ dataset_type: 'message',
65
+ where_clause: `rm.patient_id = '{{ patient_id }}' AND rm.sent_at > NOW() - INTERVAL '6 months'`,
66
+ limit: 1,
67
+ position: 0,
68
+ },
69
+ ],
70
+ actions: [
71
+ {
72
+ action_type: 'sms',
73
+ tool_id: smsToolId,
74
+ decision_key: 'send_appointment_review_sms',
75
+ position: 0,
76
+ trigger_template: 'now', // 'now' = next available slot
77
+ idempotency_template:
78
+ '{{ patient_id }}-{{ appointment.unregulated_appointment_id }}-{{ decision_key }}',
79
+ connected_app_id: connectedAppId,
80
+ connected_app_route: '/forms/review',
81
+ connected_app_metadata_template:
82
+ '{"appointment_id":"{{ appointment.unregulated_appointment_id }}"}',
83
+ tool_call: {
84
+ tool_call_type: 'sms_request',
85
+ to: { type: 'custom', body: '{{ patient_phone }}' },
86
+ body: { type: 'custom', body: 'Hi {{ patient_first_name }}, please share your feedback at {{ connected_app_form_url }}' },
87
+ sms_type: 'transactional',
88
+ },
89
+ },
90
+ ],
91
+ },
92
+ )
93
+ ```
94
+
95
+ `actions`, `context_datasets`, and (for agent-driven workflows)
96
+ `ai_agents` are all inline arrays — one POST builds the whole
97
+ workflow.
98
+
99
+ ## 2. Rules the type cannot encode
100
+
101
+ ### `filter_config` / `decision_config` are Liquid expressions with typed output
102
+
103
+ Both configs follow the same shape: `{ type, body, output_schema }`.
104
+
105
+ - `filter_config.body` renders to a boolean — the literal string
106
+ `"true"` (non-empty) means PASS; an empty render means SKIP.
107
+ (Same inverted-semantics rule as `interoperability_contracts.md`
108
+ §2 `filter_template`.)
109
+ - `decision_config.body` renders to a JSON array of decision keys.
110
+ Each rendered key fires the matching `action` (by
111
+ `action.decision_key`).
112
+
113
+ `output_schema` is a JSON-schema string that the platform uses
114
+ to validate the rendered output before invoking the next stage.
115
+
116
+ ### Replace-on-PUT semantics for inline arrays
117
+
118
+ `actions`, `context_datasets`, and `ai_agents` (when present)
119
+ use **replace semantics on PUT**. Submitting an update body that
120
+ omits any of these arrays silently disassociates ALL of its
121
+ entries. Always re-supply the full array on every update, even
122
+ when you're only changing `description`.
123
+
124
+ Factor the workflow body construction into a helper that
125
+ accepts the changing fields and returns the full body — same
126
+ pattern used for datalake updates (see `datalakes.md` §5).
127
+
128
+ ### `idempotency_template` prevents duplicate action firings
129
+
130
+ An action's `idempotency_template` renders to a string per
131
+ dataset row; the platform deduplicates so the same idempotency
132
+ key never fires twice. Typical recipe:
133
+
134
+ ```
135
+ '{{ <subject_id> }}-{{ <event_id> }}-{{ decision_key }}'
136
+ ```
137
+
138
+ The placeholder names depend on the workflow's `dataset_type`
139
+ (patient_id + appointment.unregulated_appointment_id for an
140
+ appointment-driven workflow, customer_id + invoice.id for an
141
+ invoice-driven workflow, etc.).
142
+
143
+ ### `trigger_template` controls scheduling
144
+
145
+ A Liquid template rendering to a datetime or one of these
146
+ literals:
147
+
148
+ ```
149
+ 'now' — schedule at the next available slot at-or-after current time
150
+ '' — same as 'now' (empty string also routes to the now-slot)
151
+ ISO 8601 — schedule at the named instant
152
+ ```
153
+
154
+ The action is queued through the platform's scheduling layer
155
+ (action windows, throttling), so even `'now'` may resolve to a
156
+ slot some minutes in the future under load.
157
+
158
+ ### Action-window + queue-throttle render `'now'` non-literal
159
+
160
+ Avoid templates like `{{ "" | now | date: "%Y-%m-%d..." }}` —
161
+ they render a literal current timestamp, which then collides
162
+ with the action-window clamp + queue throttle and pushes the
163
+ slot forward unpredictably. Use the literal `'now'` sentinel
164
+ when you want "fire as soon as possible".
165
+
166
+ ## 3. Agent-driven workflows: AI enrichment + AI-emitted decisions
167
+
168
+ When `decision_config.type` references an AI agent (instead of
169
+ `'custom'` with a literal Liquid body), the workflow gains an
170
+ **enrichment** step between filter and decision:
171
+
172
+ ```
173
+ filter
174
+ │
175
+ ▼
176
+ enrichment — each ai_agent runs, producing typed output
177
+ that the decision template can reference
178
+ │
179
+ ▼
180
+ decision — AI agent emits the decision_key array
181
+ (or a custom decision template references
182
+ agent outputs via Liquid)
183
+ │
184
+ ▼
185
+ action(s)
186
+ ```
187
+
188
+ Bound agents are declared in `ai_agents: []` on the workflow
189
+ body. The full AI agent surface (create body, prompt config,
190
+ input_schema / llm_response_schema, runtime failure codes) lives
191
+ in `ai_agents.md`. The workflow-side embed shape is:
192
+
193
+ ```typescript
194
+ ai_agents: [
195
+ {
196
+ ai_agent_id: aiAgentId, // UUID from api.aiAgents.create
197
+ position: 0, // ordering when multiple agents fire
198
+ context_mapping_config: {
199
+ type: 'custom',
200
+ // Liquid renders to a JSON object whose keys match the
201
+ // referenced agent's input_schema. The values reference
202
+ // the inbound dataset row's fields via `event_dataset.<field>`.
203
+ body: JSON.stringify({
204
+ msg: '{{ event_dataset.message }}',
205
+ submission_id: '{{ event_dataset.submission_id }}',
206
+ }),
207
+ output_schema: '{"type":"object"}',
208
+ },
209
+ },
210
+ ],
211
+ ```
212
+
213
+ The agent's parsed output is exposed in the workflow's runtime
214
+ context at `additional_context.<agent_slug>.<field>` (property
215
+ paths match the agent's `llm_response_schema`). Decision and
216
+ action templates reference it via Liquid:
217
+
218
+ ```liquid
219
+ {{ additional_context.contact-us-triage-categorizer.category }}
220
+ ```
221
+
222
+ Standard (non-agent) workflows omit `ai_agents` — the enrichment
223
+ stage observably runs but emits `{ status: 'skipped' }` in the
224
+ per-step artifact (see §6).
225
+
226
+ ### `event_dataset` is the workflow accessor for the inbound row
227
+
228
+ Workflow templates (filter, context mappings, action bodies)
229
+ reference the inbound dataset row via the `event_dataset`
230
+ variable. Field paths match the columns of the workflow's
231
+ `dataset_type`:
232
+
233
+ ```
234
+ dataset_type: 'appointment' → event_dataset.id, event_dataset.start, ...
235
+ dataset_type: 'generic_table' → event_dataset.<column_name> for each
236
+ column on the bound table
237
+ ```
238
+
239
+ For generic-table-bound workflows, `event_dataset` carries the
240
+ table's columns directly (no separate `row` indirection).
241
+
242
+ ### `skip_mdm_resolution: true` for generic-table workflows
243
+
244
+ Workflows bound to a `dataset_type: 'generic_table'` typically
245
+ set `skip_mdm_resolution: true` on the workflow body. The flag
246
+ opts out of the platform's entity-resolution step on the inbound
247
+ row, which doesn't apply when the row IS the subject (no canonical
248
+ entity to resolve to). Workflows on entity-anchored datasets
249
+ (`patient`, `customer`, `legal_entity`, etc.) leave the flag at
250
+ its default `false`.
251
+
252
+ ## 4. Field ownership
253
+
254
+ **Server-derived (Response-only).** Universal set from
255
+ `type_naming.md`, plus:
256
+
257
+ ```
258
+ actions[*].id UUID per inline action row
259
+ context_datasets[*].id UUID per inline context-dataset row
260
+ ```
261
+
262
+ **Caller-supplied (round-trip).**
263
+
264
+ ```
265
+ name required string
266
+ description optional string
267
+ dataset_type required string — the dataset this listens on
268
+ status required enum — 'live' | 'draft'
269
+ generic_table_id optional UUID — required iff dataset_type='generic_table'
270
+ skip_mdm_resolution optional bool — default false; set true for
271
+ generic-table-bound workflows
272
+ filter_config required embed — { type, body, output_schema }
273
+ decision_config required embed — same shape
274
+ context_datasets required array — inline; replace-on-PUT
275
+ actions required array — inline; replace-on-PUT
276
+ ai_agents optional array — inline; replace-on-PUT (§3)
277
+ ```
278
+
279
+ **Write-only (Request-only).** None.
280
+
281
+ ## 5. Lifecycle
282
+
283
+ ### Create
284
+
285
+ Synchronous. The workflow is immediately runnable when
286
+ `status: 'live'`. `status: 'draft'` is a save-without-arming
287
+ flag — drafts can be re-PUT but won't fire on dataset events.
288
+
289
+ ### Read shapes
290
+
291
+ ```
292
+ .list(tenantSlug, datalakeSlug) Paged: { data, meta }
293
+ .get(tenantSlug, datalakeSlug, idOrSlug) One row
294
+ .metadata(tenantSlug, datalakeSlug, idOrSlug) Agent-facing metadata
295
+ ```
296
+
297
+ ### Run
298
+
299
+ `.run` evaluates the workflow against a SQL-selected row set:
300
+
301
+ ```typescript
302
+ const { data: run } = await api.workflows.run(
303
+ tenantSlug, workflowSlug,
304
+ {
305
+ sql_where_clause: `ra.id = '${appointmentId}'`, // run against the regulated tables
306
+ mode: 'live', // 'live' | 'dry_run'
307
+ manual_override: false, // see below
308
+ },
309
+ )
310
+ // run.batch_id — tagged string (e.g. "manual:<uuid>");
311
+ // the prefix distinguishes manual runs from
312
+ // cron-driven ones
313
+ // run.workflow_run_log_id — UUID — the batch log row (poll via batchLogs)
314
+ // run.enqueued_count — number of rows the WHERE clause matched
315
+ ```
316
+
317
+ `sql_where_clause` accepts a fragment against the dataset's
318
+ regulated tables (`ra` for appointments, `rp` for patients,
319
+ etc. — same alias scheme as `data_activation_clients.md` §6.5).
320
+ The id column is ambiguous across joins; always prefix
321
+ (`ra.id`, `rp.id`).
322
+
323
+ `manual_override: true` **skips BOTH the filter expression AND
324
+ the idempotency check** — the workflow fires regardless of
325
+ filter outcome and regardless of whether the idempotency tuple
326
+ already fired for this row. Useful for forced re-runs in test
327
+ contexts; avoid in production.
328
+
329
+ ### Execute (per-row, per-action)
330
+
331
+ `.execute` skips filter + decision evaluation entirely and
332
+ fires one specific action against one specific dataset row:
333
+
334
+ ```typescript
335
+ const { data: result } = await api.workflows.execute(
336
+ tenantSlug, workflowSlug,
337
+ {
338
+ dataset_id: unregulatedAppointmentId, // UNREGULATED id (not regulated)
339
+ decision_key: 'send_appointment_review_sms',
340
+ mode: 'live', // 'live' | 'dry_run' (same enum as .run)
341
+ manual_override: true, // load-bearing on re-fires
342
+ },
343
+ )
344
+ // result.status — 'pending' | 'completed' | 'filtered' | 'failed'
345
+ // result.scheduled_count — number of action jobs scheduled
346
+ // result.workflow_execution_log_id — UUID of the execution-log row
347
+ ```
348
+
349
+ Note that `dataset_id` is the **unregulated** id, not the
350
+ regulated one. The unregulated tier is what the workflow
351
+ runtime reads its action context from; passing the regulated
352
+ id will fail to resolve. Issue a `dataAccessMode: 'unregulated'`
353
+ search to obtain the right id.
354
+
355
+ `manual_override: true` on `.execute` sets the unique-key
356
+ constraint to `false` on the per-row job, allowing re-fires
357
+ on rows that have already been processed (otherwise the new
358
+ log row sits in `:pending` forever, deduplicated by the
359
+ sampled-event uniqueness check).
360
+
361
+ ### Update
362
+
363
+ `PUT` replays the full body — no PATCH. Replace-on-PUT applies
364
+ to all inline arrays (see §2).
365
+
366
+ ### Delete
367
+
368
+ `DELETE` removes the workflow. In-flight runs decouple their
369
+ lifecycle from the workflow row; deleting one mid-run rejects
370
+ with a 422.
371
+
372
+ ## 6. Run-time logs
373
+
374
+ ### Batch logs — `api.workflows.batchLogs`
375
+
376
+ One row per workflow run (per `.run` call). Lifecycle:
377
+
378
+ ```
379
+ :pending → :completed | :failed | :partial
380
+ ```
381
+
382
+ - `:completed` — every row processed terminally
383
+ - `:failed` — every row's processing errored
384
+ - `:partial` — mixed (some rows succeeded, some filtered, some failed)
385
+
386
+ Methods:
387
+
388
+ ```
389
+ .list(tenantSlug, workflowSlug) Paged
390
+ .get(tenantSlug, workflowSlug, runLogId) One row
391
+ .refresh(tenantSlug, workflowSlug, runLogId) Force a status recompute
392
+ (per-row jobs are async; status
393
+ only flips after explicit refresh)
394
+ .start(tenantSlug, workflowSlug, runLogId) Begin polling for this batch
395
+ .stop(tenantSlug, workflowSlug, runLogId) Stop polling
396
+ ```
397
+
398
+ The `.refresh` verb is **load-bearing** for synchronous
399
+ progress checks — `.get` returns the cached status, but the
400
+ underlying per-row jobs may have completed since the last
401
+ refresh. Call `.refresh` in your poll loop.
402
+
403
+ ### Workflow execution logs — `api.workflows.workflowLogs`
404
+
405
+ One row per dataset row evaluated. Status enum:
406
+
407
+ ```
408
+ :pending → :executing → :completed | :filtered | :failed
409
+ ```
410
+
411
+ Each log carries:
412
+
413
+ - `id`, `workflow_id`, `batch_id` — join keys
414
+ - `status` — terminal state
415
+ - `actions_total` — number of actions wired on the workflow
416
+ - `actions_pending` — actions scheduled but not yet completed by
417
+ their per-action workers
418
+ - `actions_completed` — actions whose worker has finished
419
+ - `actions_skipped` — siblings of the matched action that didn't
420
+ fire (the decision template returned one key; other actions
421
+ with non-matching `decision_key` get :skipped)
422
+ - `error_message` — coarse failure summary (e.g.
423
+ `"AI enrichment failed: <agent_name>"`); rich detail lives
424
+ in `error.json` (§ Per-step artifacts below)
425
+ - `action_execution_logs` — nested rows, one per action that fired
426
+ (`message_body` virtual field is rendered when read via the
427
+ `dataAccessMode: 'regulated'` mode)
428
+
429
+ Methods:
430
+
431
+ ```
432
+ .list(tenantSlug, workflowSlug) Paged
433
+ .get(tenantSlug, workflowSlug, logId, { dataAccessMode? })
434
+ .download(tenantSlug, workflowSlug, logId) Downloads the run's artifact
435
+ ```
436
+
437
+ The `dataAccessMode` query parameter on `.get` controls how
438
+ `message_body` is rendered:
439
+ - `'regulated'` — raw rendered body (with the resolved short
440
+ path `/t/<token>`)
441
+ - `'unregulated'` — tokenised display body
442
+
443
+ ### Per-step artifacts (event, filter, enrichment, error)
444
+
445
+ For each workflow execution, the platform writes JSON artifacts
446
+ to the datalake's regulated cloud-storage bucket. The keys
447
+ follow a fixed convention:
448
+
449
+ ```
450
+ workflows/<workflow_id>/executions/<execution_log_id>/<step>.json
451
+ ```
452
+
453
+ Where `<step>` is one of: `event` | `filter` | `enrichment` |
454
+ `error`.
455
+
456
+ Fetch them via the datalake's `createDownloadLink`:
457
+
458
+ ```typescript
459
+ const { data: link } = await api.datalakes.createDownloadLink(
460
+ tenantSlug, datalakeSlug,
461
+ { bucket: regulatedBucket, key: 'workflows/.../filter.json' },
462
+ )
463
+ const artifact = await (await fetch(link.url)).json()
464
+ ```
465
+
466
+ Artifact bodies:
467
+
468
+ ```
469
+ event.json { event_dataset: { ... full inbound row snapshot ... } }
470
+ filter.json { filter_expression, filter_result: boolean }
471
+ enrichment.json Success-with-agents:
472
+ {
473
+ status: 'completed',
474
+ <agent_slug_1>: { status: 'completed',
475
+ output: { ...matches llm_response_schema... } },
476
+ <agent_slug_2>: { ... },
477
+ }
478
+ Skipped (no ai_agents on the workflow):
479
+ { status: 'skipped' }
480
+ error.json Only present on :failed runs:
481
+ {
482
+ error_code: string,
483
+ stage: string, // 'enrichment' | 'decision' | 'action'
484
+ ai_agent_slug?: string, // present for enrichment failures
485
+ error_message: string, // coarse summary (mirrors WEL.error_message)
486
+ detail: string, // rich provider context (URL, HTTP status, ...)
487
+ // ONLY in error.json, NOT mirrored to the WEL row
488
+ }
489
+ ```
490
+
491
+ The PASS / REJECT branches of the filter both emit
492
+ `filter.json` — the body just differs in `filter_result`.
493
+
494
+ ### `error.json` error codes
495
+
496
+ When `stage: 'enrichment'`, the agent-related codes are
497
+ documented in `ai_agents.md` §7. The full set of `error_code`
498
+ values across stages:
499
+
500
+ ```
501
+ tool_execution_failed enrichment LLM tool's HTTP call failed
502
+ json_decode_failed enrichment provider returned non-JSON
503
+ context_mapping_failed enrichment mapping output didn't match agent's input_schema
504
+ ai_agent_disabled enrichment agent's enabled: false
505
+ ai_agent_not_found enrichment agent was deleted but workflow still references it
506
+ decision_template_failed decision Liquid render or schema validation failed
507
+ action_template_failed action action body Liquid render failed
508
+ ```
509
+
510
+ The `detail` field carries rich context (URL, HTTP status,
511
+ transport error) that lives ONLY in `error.json`. The
512
+ workflow execution log row's `error_message` is intentionally
513
+ coarse so the database column doesn't grow with arbitrary
514
+ provider responses. Always read `error.json` for diagnostics.
515
+
516
+ ## 7. Connected apps as action sinks
517
+
518
+ Workflows that send messages (SMS, email) typically link to a
519
+ **connected app** — a registered consumer of the action's
520
+ rendered URL. See `api.connectedApps`:
521
+
522
+ ```typescript
523
+ await api.connectedApps.create(tenantSlug, datalakeSlug, {
524
+ name: 'Review form',
525
+ description: '...',
526
+ mode: 'self_hosted', // 'self_hosted' | 'managed'
527
+ urls: [{ url: 'https://app.example.com', is_primary: true, label: 'production' }],
528
+ })
529
+ ```
530
+
531
+ Action wiring on the workflow:
532
+ - `connected_app_id` — UUID of the bound connected app
533
+ - `connected_app_route` — path on the app (e.g. `/forms/review`)
534
+ - `connected_app_metadata_template` — Liquid template rendering
535
+ a JSON object that the recipient can introspect after
536
+ resolving the token
537
+
538
+ At fire time, the action's message body's `{{ connected_app_form_url }}`
539
+ expands to `https://<app-primary>/t/<short_path>`. The recipient
540
+ hits that URL; the connected app calls
541
+ `api.connectedApps.resolvePage` with the `short_path` to fetch
542
+ the metadata, then `api.connectedApps.updateMessageTracking` to
543
+ record `opened_at` / `form_submitted_at`.
544
+
545
+ ## 8. Gotchas
546
+
547
+ 1. **`filter_config` follows the same inverted-render rule as
548
+ interoperability contracts: non-empty PASSES, empty SKIPS.**
549
+ Same wording, same trap. See
550
+ `interoperability_contracts.md` §2 for the worked example.
551
+
552
+ 2. **Replace-on-PUT silently drops omitted arrays.** Workflow
553
+ updates must re-supply `actions`, `context_datasets`, and
554
+ `ai_agents` verbatim — otherwise the existing rows are
555
+ deleted. Factor the body construction so create and update
556
+ share one builder.
557
+
558
+ 3. **`sql_where_clause` ids require an alias prefix.** `id` is
559
+ ambiguous across the multi-JOIN base query; use
560
+ `<base_alias>.id` (`ra.id`, `rp.id`, `rc.id`, etc.).
561
+
562
+ 4. **`.execute` takes the UNREGULATED dataset id, `.run` takes
563
+ the REGULATED id.** This asymmetry traps consumers who
564
+ reuse the id they got from a regulated-mode search. Issue
565
+ a fresh search with `dataAccessMode: 'unregulated'` before
566
+ calling `.execute`.
567
+
568
+ 5. **`manual_override: true` on `.execute` is required for
569
+ re-fires.** Without it, the per-row job dedupes against the
570
+ sampled-event uniqueness check and the new log row sits in
571
+ `:pending` indefinitely. The override flips the job's
572
+ unique-constraint behavior so the re-fire is allowed.
573
+
574
+ 6. **`batchLogs.get` returns cached status; call `.refresh`.**
575
+ Per-row jobs are async; `.get` does not recompute. Poll
576
+ `.refresh(runLogId)` in your wait loop.
577
+
578
+ 7. **`trigger_template: 'now'` (literal) is different from
579
+ `'{{ "" | now | date: ... }}'`.** The literal sentinel
580
+ routes to the next available scheduling slot directly;
581
+ the rendered timestamp gets clamped against the action
582
+ window + queue throttle and pushed forward unpredictably.
583
+
584
+ 8. **Enrichment runs for every workflow, even without agents.**
585
+ For standard (non-agent) workflows, `enrichment.json` lands
586
+ with `{ status: 'skipped' }`. Don't treat the artifact's
587
+ absence as the "no AI" signal — read its body.
588
+
589
+ 9. **Use `| to_json`, NEVER `| json`, in Liquid bodies.** When
590
+ serializing nested values into a JSON payload (most commonly
591
+ in an agent's `context_mapping_config`, but also any
592
+ TemplateConfig body that must produce valid JSON), the
593
+ correct filter is `| to_json`. `| json` is NOT in the
594
+ platform's allowlist of custom filters (see `ai_sandbox.md`
595
+ §2 for the closed eleven-filter set). Using it produces
596
+ structurally-invalid JSON output silently — the downstream
597
+ JSON parse step then rejects the rendered string with a
598
+ generic decode error far from the Liquid template's location,
599
+ making it expensive to diagnose. Always write
600
+ `{{ event_dataset | to_json }}`, not
601
+ `{{ event_dataset | json }}`.
package/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # `@alvera-ai/platform-sdk`
2
+
3
+ Typed SDK for the Alvera platform API — manage datalakes, data sources,
4
+ tools, AI agents, action status updaters, and more.
5
+
6
+ ## Install
7
+
8
+ ```sh
9
+ npm install @alvera-ai/platform-sdk
10
+ # or: pnpm add / bun add / yarn add
11
+ ```
12
+
13
+ ## Usage
14
+
15
+ ```ts
16
+ import { AlveraClient } from '@alvera-ai/platform-sdk'
17
+
18
+ const alvera = new AlveraClient({ baseUrl, sessionToken })
19
+ const { data } = await alvera.datalakes.list(tenantSlug)
20
+ ```
21
+
22
+ ## Agent setup
23
+
24
+ This package ships an agent-docs corpus under `.agent/` (it lands at
25
+ `node_modules/@alvera-ai/platform-sdk/.agent/` after install). Coding
26
+ agents that walk `node_modules` discover it automatically.
27
+
28
+ To wire a pointer into your project's `AGENTS.md` (and a `@AGENTS.md`
29
+ import into `CLAUDE.md`), run — no global install needed:
30
+
31
+ ```sh
32
+ npx @alvera-ai/platform-sdk llm-export
33
+ ```
34
+
35
+ It writes an idempotent managed block; re-running replaces only that
36
+ block. Start from the corpus index: `.agent/AGENTS.md`.
37
+
38
+ ## See also
39
+
40
+ - [Root README](../../README.md) — cross-repo overview (platform · SDK · CLI)
41
+ - [`packages/cli/README.md`](../cli/README.md) — the `alvera` CLI verb surface
42
+ - [`docs/cli-architecture.md`](../../docs/cli-architecture.md) — CLI design canon
43
+
44
+ ## License
45
+
46
+ Elastic-2.0
@@ -0,0 +1 @@
1
+ export { };