@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,265 @@
1
+ # AI sandbox — the safety contract
2
+
3
+ Alvera accepts AI-authored fragments from external tools (Claude
4
+ Code, Cursor, ChatGPT, in-tenant AI agents, direct API clients)
5
+ and runs them against tenant data. **None of those fragments
6
+ ever reach arbitrary code execution.** Every author surface — a
7
+ Liquid template body, a SQL fragment, an agent prompt — passes
8
+ through a three-layer sandbox that bounds blast radius.
9
+
10
+ This page names the three layers, the contract each one
11
+ enforces, and where each one's wire-level details live in the
12
+ rest of the corpus. It's the consumer-side landing for "why is
13
+ it safe to put my AI-authored body here".
14
+
15
+ The three layers are independent — any one alone would bound
16
+ the worst case — and they compose into defense-in-depth.
17
+
18
+ ## Layer 1 — Compliance gates (who sees what)
19
+
20
+ Two consumer-set fields decide whether a request reaches raw
21
+ PHI/PII/PCI or only the tokenized projection. Both must
22
+ authorize regulated reach for raw data to flow:
23
+
24
+ ```
25
+ session role AI agent data_access → outcome
26
+ ────────────── ─────────────────── ───────
27
+ member or admin regulated regulated reachable
28
+ member or admin unregulated tokenized only
29
+ member or admin (no agent on path) regulated reachable
30
+ researcher any value tokenized only
31
+ ```
32
+
33
+ - **Session role** is pinned on the membership at invitation
34
+ time (vocabulary A — `'member' | 'researcher' | 'admin'`; the
35
+ resulting session carries the corresponding role-record name
36
+ — see `account_management.md` §3).
37
+ - **AI agent `data_access`** is `'regulated' | 'unregulated'`,
38
+ pinned at agent creation (immutable post-create — see
39
+ `ai_agents.md`). Default is `'unregulated'`; `'regulated'` is
40
+ opt-in.
41
+
42
+ A `researcher` session can never reach raw PHI regardless of how
43
+ the agent is configured. An `'unregulated'` agent can never
44
+ reach raw PHI regardless of session role. Tenant isolation
45
+ (every row carries a tenant id; queries filter on the session's
46
+ tenant) AND-composes with both of these so a request only sees
47
+ the slice that *all three* allow.
48
+
49
+ The composition rule is fail-safe: a workflow spec that omits
50
+ `data_access` is treated as `'unregulated'`. Author-side
51
+ mistakes default to less reach, never more.
52
+
53
+ ## Layer 2 — Liquid sandbox (what can execute)
54
+
55
+ Every consumer-authored template body — interop contract
56
+ `template_config.body`, AI agent `prompt_config.body`, workflow
57
+ filter / decision / action bodies, action `trigger_template`,
58
+ data activation client `row_filter`, tool call `body` templates
59
+ — is a Liquid template. Not arbitrary code. Liquid is the trust
60
+ boundary between consumer fragments and the platform runtime.
61
+
62
+ ### What Liquid bodies CANNOT do
63
+
64
+ - Invoke runtime code. There is no `{% eval %}` or equivalent.
65
+ - Read the filesystem, open sockets, or reach the network.
66
+ - Mutate state outside the render's own local variable scope.
67
+ - Call any platform capability beyond the explicit custom-filter
68
+ allowlist (below).
69
+
70
+ Liquid's stock tag set (`{% if %}`, `{% for %}`, `{% assign %}`,
71
+ `{% unless %}`, `{% capture %}`, etc.) is preserved as-is.
72
+ `{% capture %}` writes to a Liquid-scope variable; it is not a
73
+ runtime eval escape hatch.
74
+
75
+ ### The 11-filter custom allowlist
76
+
77
+ Beyond stock Liquid, the platform exposes exactly **eleven**
78
+ custom filters. Adding new ones requires a platform release —
79
+ consumers cannot extend the set:
80
+
81
+ ```
82
+ e164 Phone string → E.164 format for SMS.
83
+ Example: {{ patient.phone | e164 }} → +15555550201
84
+
85
+ age Years from a birth date.
86
+ Example: {{ patient.birth_date | age }} → 66
87
+
88
+ date Format a datetime, optional add/subtract.
89
+ Example: {{ start | date: '%Y-%m-%d', 'subtract', '24 hours' }}
90
+ → 2026-03-14
91
+
92
+ to_json Encode a map / array to a JSON string.
93
+ Example: {{ event_dataset | to_json }} → {"name":"…"}
94
+
95
+ json_escape Escape a value for safe interpolation inside a
96
+ JSON string literal (quotes, backslashes,
97
+ control chars).
98
+ Example: "name": "{{ p.name | json_escape }}"
99
+ → "name": "O\"Brien"
100
+
101
+ now Current UTC datetime, ISO 8601.
102
+ Example: {{ '' | now }} → 2026-03-15T14:30:00Z
103
+
104
+ uuid Generate a new v4 UUID.
105
+ Example: {{ '' | uuid }} → a1b2c3d4-…
106
+
107
+ parse_date Parse a date string with a format spec.
108
+ Example: {{ '31/12/2023' | parse_date: '{D}/{0M}/{YY}' }}
109
+ → 2023-12-31
110
+
111
+ convert_time Convert a time between formats.
112
+ Example: {{ '10:00 pm' | convert_time: '{h12}:{m} {am}' }}
113
+ → 22:00:00
114
+
115
+ random Random integer in a range.
116
+ Example: {{ 10 | random }} → 0..10
117
+
118
+ tz_offset Current UTC offset for an IANA timezone string.
119
+ Example: {{ timezone | tz_offset }} → -04:00
120
+ ```
121
+
122
+ A common trap: `| json` (Shopify / Jekyll spelling) is NOT in
123
+ the allowlist. The lookalike falls through to a generic
124
+ stringification that produces unparseable output. Always use
125
+ `| to_json`. See `workflows.md` gotcha 9 for the diagnostic
126
+ trail this trap leaves.
127
+
128
+ ### Write-time and render-time validation
129
+
130
+ - **Parse on write.** Every body is parsed before storage.
131
+ Syntactically invalid Liquid is rejected with a 422 on the
132
+ body field — bad templates never reach a render site.
133
+ - **Schema on render.** When the embed declares an
134
+ `output_schema` (most often a JSON Schema describing the
135
+ expected shape of the rendered JSON), the rendered output is
136
+ validated against the schema before reaching the downstream
137
+ consumer. Shape drift surfaces at the point of production, not
138
+ at the point of use.
139
+
140
+ ## Layer 3 — SQL fragment boundary (what queries can do)
141
+
142
+ SQL is the second consumer-author surface: WHERE clauses on
143
+ dataset searches (`api.datasets.createUserSearch.search_query`,
144
+ see `data_activation_clients.md` §6.5). Liquid renders some of
145
+ these from templates; others arrive directly as raw SQL
146
+ fragments. Either path lands at the same boundary.
147
+
148
+ ### What SQL fragments CAN do
149
+
150
+ - SELECT against the regulated tables exposed by the dataset
151
+ search aliases (`rp` for `regulated_patients`, `rc` for
152
+ `regulated_customers`, etc. — see `data_activation_clients.md`
153
+ §6.5 SQL aliases).
154
+ - Filter results into the platform's `search_results`
155
+ accumulator scoped to the materialised search id. This is the
156
+ one write the query path performs, and the platform builds it
157
+ on the consumer's behalf — the consumer authors the WHERE
158
+ clause, not the INSERT.
159
+
160
+ ### What SQL fragments CANNOT do
161
+
162
+ - Read PHI/PII/PCI if Layer 1 disallows it. The dedicated
163
+ query-connection pool runs with reader-tier credentials
164
+ scoped to the session's tenant; cross-tenant reach is
165
+ physically blocked at the connection.
166
+ - INSERT, UPDATE, or DELETE against any table other than the
167
+ materialised search-results accumulator the platform manages.
168
+ - Invoke functions or extensions outside the read-only set
169
+ Postgres grants the pool. A consumer-authored fragment that
170
+ tries to call a write-grant function gets a permission denied
171
+ error from the database, not from the application.
172
+
173
+ ### Parameterisation
174
+
175
+ Values that get interpolated into the SQL (typically via Liquid
176
+ filters that render a value into the WHERE clause) are bound as
177
+ parameters, not concatenated into the SQL string. SQL injection
178
+ via consumer-authored fragments is structurally impossible: the
179
+ fragment is the WHERE *expression*, with values supplied via
180
+ positional bindings the platform builds.
181
+
182
+ ## How the three layers compose
183
+
184
+ ```
185
+ ┌─────────────────────────────────────────────┐
186
+ │ Layer 1 — compliance gates │
187
+ │ session role AND agent data_access │
188
+ │ AND tenant id (RLS) │
189
+ │ Decides: which schema (regulated vs │
190
+ │ unregulated) and which tenant the query │
191
+ │ hits. │
192
+ └────────────────────┬────────────────────────┘
193
+ │
194
+ ┌────────────────────▼────────────────────────┐
195
+ │ Layer 2 — Liquid sandbox │
196
+ │ 11-filter allowlist │
197
+ │ + parse-on-write │
198
+ │ + render-time output_schema check │
199
+ │ Decides: what the consumer-authored body │
200
+ │ can express. No VM reach, no FS/network, │
201
+ │ no eval escape hatch. │
202
+ └────────────────────┬────────────────────────┘
203
+ │
204
+ ┌────────────────────▼────────────────────────┐
205
+ │ Layer 3 — SQL fragment boundary │
206
+ │ parameterised bindings │
207
+ │ + reader-tier connection pool │
208
+ │ + INSERT-only on search_results │
209
+ │ Decides: what SQL fragments the WHERE │
210
+ │ clause path can execute. Read-most, │
211
+ │ write-one. │
212
+ └─────────────────────────────────────────────┘
213
+ ```
214
+
215
+ Each layer is independently sufficient: bypassing any single
216
+ layer still leaves the other two enforcing the contract. The
217
+ compound rule is "all three must permit" — read PHI requires
218
+ authorising session role AND regulated-mode agent (when an
219
+ agent is on the path) AND tenant id match.
220
+
221
+ ## Auditability
222
+
223
+ Every LLM call the platform makes on behalf of a consumer is
224
+ recorded with cloud-storage pointers to the exact input payload
225
+ the agent received and the exact response the provider
226
+ returned. The audit record outlives the host run that produced
227
+ it — a compliance auditor can later replay any agent's input
228
+ and verify what data class (regulated vs tokenized) actually
229
+ reached the model. The audit surface is admin-facing today; the
230
+ guarantees it captures back-stop Layer 1.
231
+
232
+ ## Consumer takeaways
233
+
234
+ 1. **Default to `'unregulated'` agent `data_access`.** Opt into
235
+ `'regulated'` only when the action genuinely needs raw
236
+ values, and pair with a session role that's authorised.
237
+
238
+ 2. **Use only the 11 custom filters** named in Layer 2. Bodies
239
+ that reference other filter names will silently fall through
240
+ to stock Liquid behavior and produce malformed output.
241
+
242
+ 3. **Anchor JSON bodies on `to_json`.** When a template body
243
+ must produce valid JSON (most contexts of MDM input,
244
+ workflow context_mapping, action metadata), wrap nested
245
+ values with `| to_json` and never with `| json`.
246
+
247
+ 4. **Treat SQL WHERE fragments as expressions, not statements.**
248
+ The dataset search surface owns the SELECT and the INSERT
249
+ into `search_results`. Consumer authoring is constrained to
250
+ the WHERE clause; everything else is platform-built.
251
+
252
+ 5. **Pin output shapes with `output_schema`.** Where the embed
253
+ supports it (most TemplateConfig surfaces), populating
254
+ `output_schema` turns render-time shape drift into a clean
255
+ 422 instead of a downstream JSON-decode error two queues
256
+ later.
257
+
258
+ ## Related
259
+
260
+ - `ai_agents.md` §2 — `data_access` mode (Layer 1)
261
+ - `account_management.md` §3 — session role vocabularies (Layer 1)
262
+ - `interoperability_contracts.md` §2 — template_config body shape (Layer 2)
263
+ - `workflows.md` gotcha 9 — the `| to_json` vs `| json` trap (Layer 2)
264
+ - `data_activation_clients.md` §6.5 — dataset search SQL aliases (Layer 3)
265
+ - `mdm.md` — verification surface that composes with Layer 1
@@ -0,0 +1,111 @@
1
+ # Async and readiness
2
+
3
+ Some operations are async on the server. The create response
4
+ returns immediately with the resource in a non-terminal status;
5
+ downstream operations must wait for the resource to reach its
6
+ terminal status before proceeding.
7
+
8
+ ## Canonical wait shape
9
+
10
+ ```typescript
11
+ async function waitUntilReady<T extends { status: string }>(
12
+ fetcher: () => Promise<{ data: T }>,
13
+ isTerminal: (status: string) => 'ready' | 'failed' | 'pending',
14
+ { intervalMs = 1000, timeoutMs = 60_000 } = {},
15
+ ): Promise<T> {
16
+ const deadline = Date.now() + timeoutMs
17
+ while (true) {
18
+ const { data } = await fetcher()
19
+ const state = isTerminal(data.status)
20
+ if (state === 'ready') return data
21
+ if (state === 'failed') {
22
+ throw new Error(`failed: ${JSON.stringify(data)}`)
23
+ }
24
+ if (Date.now() > deadline) {
25
+ throw new Error(`timed out waiting for ready`)
26
+ }
27
+ await new Promise((r) => setTimeout(r, intervalMs))
28
+ }
29
+ }
30
+ ```
31
+
32
+ The shape is the same across resources; only the terminal-
33
+ success / terminal-failure status values differ.
34
+
35
+ ## Per-resource readiness signals
36
+
37
+ The terminal-success and terminal-failure status values are
38
+ resource-specific. Common examples:
39
+
40
+ | Resource | Terminal success | Terminal failure |
41
+ |-----------------------------------|------------------|-------------------------|
42
+ | Datalake (create + migrate) | `ready` | — (no terminal failure) |
43
+ | Data Activation Client run | `success` | `error` |
44
+ | AWS Lambda tool deploy | `deployed` | `failed` |
45
+ | Workflow run | `completed` | `failed` |
46
+
47
+ The Datalake row is the odd one: its top-level `status` enum is
48
+ `new | processing | ready` only — there is no terminal failure
49
+ value. A migration that fails leaves the datalake at
50
+ `processing` (or `new` if it never started). The failure
51
+ surfaces in the admin console's Migration Logs view, which is
52
+ not exposed over the SDK. Consumers polling for datalake
53
+ readiness must therefore rely on `timeoutMs` to bound the wait;
54
+ the `'failed'` branch of `waitUntilReady` never fires for this
55
+ resource.
56
+
57
+ The AWS Lambda tool row keys on `tool.body.stack_status` (the
58
+ CloudFormation deploy status nested under the polymorphic
59
+ `body`), not on the top-level `tool.status` enum (which is
60
+ `draft | active | inactive | error | marked_for_deletion`).
61
+ `tool.status` flips to `'active'` after a successful deploy
62
+ but `stack_status` is the more precise readiness signal — see
63
+ `tools.md` §2 for the per-body field set.
64
+
65
+ Each resource's MD §5 (Lifecycle) names its terminal-success
66
+ and terminal-failure status enums. If your resource isn't in
67
+ the table above, its MD is authoritative.
68
+
69
+ ## Downstream resources MUST wait
70
+
71
+ Downstream operations (creating child resources, running
72
+ workflows, invoking tools) MUST NOT proceed until the parent
73
+ resource is in a terminal-success status. Posting a child
74
+ resource against an unready parent typically surfaces as a
75
+ 500 (server-internal) or a 422 with a "parent not ready"
76
+ detail — both are recoverable but indicate a missing wait in
77
+ the caller.
78
+
79
+ ## Idempotency on create
80
+
81
+ POST creates are NOT idempotent by default. Repeating a POST
82
+ with the same body yields a 422 on uniqueness fields (typically
83
+ the name within scope). Consumers handling retry should:
84
+
85
+ 1. Catch the post-HTTP throw (see `errors.md`).
86
+ 2. Inspect for a uniqueness `detail` on a `source.pointer`
87
+ that names the uniqueness key (usually `/name` or `/slug`).
88
+ 3. Treat as "already created" and fetch the existing row.
89
+
90
+ The integration test suite formalizes this as the
91
+ "idempotent retry path" pattern in places where local state
92
+ may have diverged from server state (e.g. a prior run wrote
93
+ to the server but failed to capture the response locally).
94
+
95
+ ## Polling cost
96
+
97
+ Polling at 1s intervals against the platform's
98
+ `/<resource>/:slug` endpoint is cheap for most resources but
99
+ becomes expensive at high concurrency. For long-running
100
+ operations (lambda deployments, large data-activation-client
101
+ runs), back off the interval (e.g. 2s → 5s → 10s) after the
102
+ first ~30s of waiting.
103
+
104
+ ## What the SDK does NOT auto-poll
105
+
106
+ The typed client returns the immediate POST response — it
107
+ does NOT block until the resource reaches terminal status.
108
+ Consumers explicitly write the wait loop above where it
109
+ matters. This is intentional: not every caller wants to
110
+ block (some kick off async work and return; some need the
111
+ ready signal before downstream).