@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.
- package/.agent/AGENTS.md +440 -0
- package/.agent/account_management.md +455 -0
- package/.agent/action_status_updaters.md +262 -0
- package/.agent/ai_agents.md +423 -0
- package/.agent/ai_sandbox.md +265 -0
- package/.agent/async.md +111 -0
- package/.agent/connected_apps.md +407 -0
- package/.agent/cookbook/_fixtures/README.md +99 -0
- package/.agent/cookbook/_fixtures/accounts_receivable/_customers_accounts_receivable_customer.liquid +32 -0
- package/.agent/cookbook/_fixtures/accounts_receivable/_customers_accounts_receivable_mdm.liquid +20 -0
- package/.agent/cookbook/_fixtures/foundation/_lead_submissions_foundation_generic_table.liquid +33 -0
- package/.agent/cookbook/_fixtures/foundation/_lead_submissions_foundation_legal_entity.liquid +88 -0
- package/.agent/cookbook/_fixtures/foundation/_lead_submissions_foundation_mdm.liquid +48 -0
- package/.agent/cookbook/_fixtures/healthcare/_cahps_appointments_healthcare_appointment.liquid +47 -0
- package/.agent/cookbook/_fixtures/healthcare/_cahps_appointments_healthcare_mdm.liquid +24 -0
- package/.agent/cookbook/_fixtures/healthcare/_cahps_appointments_healthcare_patient.liquid +38 -0
- package/.agent/cookbook/_fixtures/payment_risk/_compliance_screenings_payment_risk_compliance_screening.liquid +59 -0
- package/.agent/cookbook/_fixtures/payment_risk/_compliance_screenings_payment_risk_mdm.liquid +36 -0
- package/.agent/cookbook/_fixtures/payment_risk/_payment_accounts_payment_risk_mdm.liquid +30 -0
- package/.agent/cookbook/_fixtures/payment_risk/_payment_accounts_payment_risk_payment_account.liquid +55 -0
- package/.agent/cookbook/_setup/accounts_receivable.md +282 -0
- package/.agent/cookbook/_setup/foundation.md +277 -0
- package/.agent/cookbook/_setup/healthcare.md +279 -0
- package/.agent/cookbook/_setup/payment_risk.md +283 -0
- package/.agent/cookbook/appointment-review-sms-workflow.md +761 -0
- package/.agent/cookbook/birthday-greeting-sms-trigger.md +656 -0
- package/.agent/cookbook/contact-us-triage-with-llm.md +603 -0
- package/.agent/cookbook/dunning-sms-for-delinquent.md +619 -0
- package/.agent/cookbook/kyc-notification-on-account-activation.md +619 -0
- package/.agent/cookbook/sanctions-screening-with-agent-review.md +711 -0
- package/.agent/cookbook/score-leads-with-llm-categorization.md +602 -0
- package/.agent/cookbook/welcome-sms-for-customers.md +607 -0
- package/.agent/data_activation_clients.md +557 -0
- package/.agent/data_sources.md +234 -0
- package/.agent/datalakes.md +712 -0
- package/.agent/debugging.md +137 -0
- package/.agent/errors.md +196 -0
- package/.agent/generic_tables.md +351 -0
- package/.agent/interoperability_contracts.md +351 -0
- package/.agent/mdm.md +293 -0
- package/.agent/mutations.md +152 -0
- package/.agent/templates.md +98 -0
- package/.agent/tool-call-configs.md +90 -0
- package/.agent/tools.md +546 -0
- package/.agent/type_naming.md +131 -0
- package/.agent/workflows.md +601 -0
- package/README.md +46 -0
- package/dist/bin/platform-sdk.d.mts +1 -0
- package/dist/bin/platform-sdk.mjs +106 -0
- package/dist/bin/platform-sdk.mjs.map +1 -0
- package/dist/index.d.mts +1200 -43201
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1859 -7319
- package/dist/index.mjs.map +1 -1
- package/package.json +19 -10
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
# Connected apps
|
|
2
|
+
|
|
3
|
+
A **connected app** is an external web application registered with
|
|
4
|
+
the platform — typically a customer-facing forms portal hosted on
|
|
5
|
+
Cloudflare Workers / Pages or any other deployment target. The
|
|
6
|
+
platform mints magic links into the app from workflow actions:
|
|
7
|
+
each action renders a URL of the form
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
https://<app-primary-url>/t/<short_path>
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The customer clicks the link, lands on a route the app discovered
|
|
14
|
+
via `/.well-known/routes.json`, and the app calls back to the
|
|
15
|
+
platform to resolve the page (fetch the underlying message + its
|
|
16
|
+
metadata) and update tracking (opened_at, form_submitted_at).
|
|
17
|
+
|
|
18
|
+
SDK namespace: `api.connectedApps`.
|
|
19
|
+
|
|
20
|
+
The namespace splits into **two roles**:
|
|
21
|
+
|
|
22
|
+
- **Datalake-scoped management** — the admin side. CRUD on the
|
|
23
|
+
connected-app row, route discovery + sync. Uses the app's
|
|
24
|
+
UUID `id` as the path key.
|
|
25
|
+
- **Tenant-scoped runtime** — the consumer side. Called BY the
|
|
26
|
+
connected app itself (authenticated with its auto-provisioned
|
|
27
|
+
machine-to-machine API key) when a customer hits a magic link. Uses the app's
|
|
28
|
+
`slug` as the path key.
|
|
29
|
+
|
|
30
|
+
Connected apps are **Datalake-DB-resident** — the parent datalake
|
|
31
|
+
must be `status: 'ready'` before POST.
|
|
32
|
+
|
|
33
|
+
## 1. Wire shape
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
import type {
|
|
37
|
+
ConnectedAppResponse,
|
|
38
|
+
} from '@alvera-ai/platform-sdk'
|
|
39
|
+
|
|
40
|
+
const { data: created } = await api.connectedApps.create(
|
|
41
|
+
tenantSlug,
|
|
42
|
+
datalakeSlug,
|
|
43
|
+
{
|
|
44
|
+
name: 'The Doctors Center',
|
|
45
|
+
description: 'Patient-facing forms portal',
|
|
46
|
+
mode: 'self_hosted', // 'self_hosted' (only mode active today; see §2)
|
|
47
|
+
urls: [
|
|
48
|
+
{
|
|
49
|
+
url: 'https://the-doctors-center.pages.dev', // app's deployed origin
|
|
50
|
+
is_primary: true, // exactly one URL must be primary
|
|
51
|
+
label: 'Production',
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
)
|
|
56
|
+
// created.id, created.slug — server-derived
|
|
57
|
+
// created.status === 'pending' (transient) → 'synced' after route fetch
|
|
58
|
+
// created.api_key_id — auto-provisioned M2M key id (see §2)
|
|
59
|
+
// created.routes — array populated from .well-known/routes.json
|
|
60
|
+
// (empty until first sync)
|
|
61
|
+
// created.last_synced_at — set when routes successfully validated
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The platform validates `/.well-known/routes.json` at the primary
|
|
65
|
+
URL during create. If validation succeeds, routes are saved and
|
|
66
|
+
the status flips to `'synced'`. If it fails, the row persists
|
|
67
|
+
with `status: 'error'` and the failure message in `error`.
|
|
68
|
+
|
|
69
|
+
## 2. Rules the type cannot encode
|
|
70
|
+
|
|
71
|
+
### URLs: at least one, exactly one primary
|
|
72
|
+
|
|
73
|
+
The `urls` array requires at least one entry, and **exactly one
|
|
74
|
+
entry must have `is_primary: true`**. The primary URL is the host
|
|
75
|
+
the platform fetches `/.well-known/routes.json` from and the
|
|
76
|
+
host that magic links are rooted at. Submitting zero primaries
|
|
77
|
+
or multiple primaries returns a 422 on `/urls`.
|
|
78
|
+
|
|
79
|
+
Additional URLs (staging, preview) are stored for reference but
|
|
80
|
+
aren't used for route discovery or link minting.
|
|
81
|
+
|
|
82
|
+
### Route discovery is automatic + re-triggerable
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
/.well-known/routes.json served by the connected app at its primary URL.
|
|
86
|
+
Must return application/json with a JSON array
|
|
87
|
+
of route objects:
|
|
88
|
+
|
|
89
|
+
[
|
|
90
|
+
{ "name": "Review Form", "path": "/forms/review", "description": "..." },
|
|
91
|
+
{ "name": "CAHPS Survey", "path": "/forms/cahps", "description": "..." }
|
|
92
|
+
]
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Validation rules at fetch time:
|
|
96
|
+
|
|
97
|
+
- Response content-type must be `application/json`
|
|
98
|
+
- Body must be a JSON array
|
|
99
|
+
- Each route requires `name` (string) and `path` (string);
|
|
100
|
+
`description` is optional
|
|
101
|
+
|
|
102
|
+
The platform fetches the manifest at two moments:
|
|
103
|
+
|
|
104
|
+
1. **At create time** — automatic. Failure flips the row to
|
|
105
|
+
`status: 'error'` (the row still persists; routes stay empty).
|
|
106
|
+
2. **On explicit `.syncRoutes(id)`** — operator-triggered re-fetch.
|
|
107
|
+
Updates the saved routes and `last_synced_at`.
|
|
108
|
+
|
|
109
|
+
The SDK does NOT auto-re-sync routes on a schedule. Re-sync is
|
|
110
|
+
deliberate.
|
|
111
|
+
|
|
112
|
+
### Modes: only `self_hosted` is active today
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
mode: 'self_hosted' active — operator runs the deployment
|
|
116
|
+
themselves (e.g. `wrangler deploy`); the
|
|
117
|
+
platform registers the resulting URL and
|
|
118
|
+
discovers routes from it.
|
|
119
|
+
mode: 'managed' reserved for a future Cloudflare-Pages
|
|
120
|
+
auto-deploy flow. Not provisioned at
|
|
121
|
+
runtime today — submitting it succeeds
|
|
122
|
+
structurally but no deploy fires.
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Treat `'self_hosted'` as the only practical choice for current
|
|
126
|
+
consumers.
|
|
127
|
+
|
|
128
|
+
### machine-to-machine API key is auto-provisioned
|
|
129
|
+
|
|
130
|
+
Creating a connected app automatically provisions a machine-to-
|
|
131
|
+
machine API key named `ConnectedApp: <app name>` with the `api`
|
|
132
|
+
role. The key is exposed on the response once (typically rendered
|
|
133
|
+
in the create-flow UI) so the operator can configure it as a
|
|
134
|
+
secret in the deployed app's environment. The platform stores only
|
|
135
|
+
the key's id (`api_key_id` on the response).
|
|
136
|
+
|
|
137
|
+
The connected app deployment should be configured with:
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
ALVERA_API_KEY="<auto-provisioned key>"
|
|
141
|
+
ALVERA_DATALAKE_ID="<datalake-uuid>"
|
|
142
|
+
ALVERA_API_URL="https://<platform-host>"
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Deleting the connected app deletes the API key.
|
|
146
|
+
|
|
147
|
+
### `slug` is the runtime reference; `id` is the admin reference
|
|
148
|
+
|
|
149
|
+
Management endpoints (`list`, `get`, `update`, `syncRoutes`,
|
|
150
|
+
`metadataDetails`) take the UUID `id` as the path key — the
|
|
151
|
+
datalake-scoped admin context.
|
|
152
|
+
|
|
153
|
+
Runtime endpoints (`resolvePage`, `updateMessageTracking`) take
|
|
154
|
+
the `slug` and skip the datalake path segment entirely. This is
|
|
155
|
+
deliberate: the connected app deployment is authenticated with
|
|
156
|
+
the tenant-scoped machine-to-machine API key and may not know which datalake
|
|
157
|
+
its row lives under.
|
|
158
|
+
|
|
159
|
+
## 3. Field ownership
|
|
160
|
+
|
|
161
|
+
**Server-derived (Response-only).** Universal set from
|
|
162
|
+
`type_naming.md`, plus:
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
status enum — 'pending' | 'deploying' | 'deployed' | 'synced' | 'error'
|
|
166
|
+
— for the dominant `'self_hosted'` mode the status
|
|
167
|
+
flows 'pending' → 'synced' (or 'error' on a
|
|
168
|
+
failed route fetch). 'deploying' / 'deployed'
|
|
169
|
+
are reserved for the future `'managed'`
|
|
170
|
+
Cloudflare-Pages auto-deploy flow (see §2)
|
|
171
|
+
and won't appear on self-hosted rows today.
|
|
172
|
+
last_synced_at string — ISO 8601; set at successful route sync
|
|
173
|
+
error string — populated when status === 'error'
|
|
174
|
+
routes array — discovered from .well-known/routes.json
|
|
175
|
+
(each: { name, path, description? })
|
|
176
|
+
api_key_id UUID — id of the auto-provisioned M2M key
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**Caller-supplied (round-trip).**
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
name required string
|
|
183
|
+
description optional string
|
|
184
|
+
mode required enum — 'self_hosted' (only active)
|
|
185
|
+
urls required array — see §2; exactly one primary
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**Write-only (Request-only).** None. The auto-provisioned API key's
|
|
189
|
+
plaintext value is returned ONCE on the create response (not on
|
|
190
|
+
subsequent reads), but it isn't a "write-only" field in the
|
|
191
|
+
caller-supplied sense.
|
|
192
|
+
|
|
193
|
+
## 4. Error envelopes
|
|
194
|
+
|
|
195
|
+
Standard JSON:API envelopes per `errors.md`. Common rejections:
|
|
196
|
+
|
|
197
|
+
| `source.pointer` | Cause |
|
|
198
|
+
|-------------------------------|--------------------------------------------------------|
|
|
199
|
+
| `/urls` | empty, missing primary, or multiple primaries |
|
|
200
|
+
| `/urls/0/url` | not a valid URL |
|
|
201
|
+
| `/mode` | value not in enum |
|
|
202
|
+
| `/name` | uniqueness within datalake — server error literal: "name must be unique within a datalake" |
|
|
203
|
+
|
|
204
|
+
Route-fetch failures (primary URL unreachable, manifest malformed)
|
|
205
|
+
do NOT reject the create — the row persists with `status: 'error'`
|
|
206
|
+
and `error` populated. Use `.syncRoutes(id)` to retry after fixing
|
|
207
|
+
the deployment.
|
|
208
|
+
|
|
209
|
+
## 5. Lifecycle
|
|
210
|
+
|
|
211
|
+
### Create
|
|
212
|
+
|
|
213
|
+
Synchronous. The platform attempts a single fetch of
|
|
214
|
+
`/.well-known/routes.json` at the primary URL during create.
|
|
215
|
+
Success → `status: 'synced'` and `routes` populated. Failure →
|
|
216
|
+
`status: 'error'` and `error` populated; the row still exists
|
|
217
|
+
and can be retried via `.syncRoutes`.
|
|
218
|
+
|
|
219
|
+
### Read shapes — management
|
|
220
|
+
|
|
221
|
+
```
|
|
222
|
+
.list(tenantSlug, datalakeSlug) Paged: { data, meta }
|
|
223
|
+
.get(tenantSlug, datalakeSlug, id) One row, by UUID
|
|
224
|
+
.metadata(tenantSlug, datalakeSlug) Markdown catalog
|
|
225
|
+
.metadataDetails(tenantSlug, datalakeSlug, Markdown for one app
|
|
226
|
+
id)
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Sync routes
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
const { data: ack } = await api.connectedApps.syncRoutes(
|
|
233
|
+
tenantSlug, datalakeSlug, appId,
|
|
234
|
+
)
|
|
235
|
+
// 202 Accepted — the sync runs in a background worker:
|
|
236
|
+
// ack.status — 'enqueued'
|
|
237
|
+
// ack.job_id — background job id
|
|
238
|
+
// ack.connected_app_id — echo of the app id
|
|
239
|
+
// ack.enqueued_at — enqueue timestamp
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
This is the explicit "re-fetch and save" verb — asynchronous. The
|
|
243
|
+
202 acknowledgement proves the app id resolved and a sync job was
|
|
244
|
+
accepted; the worker then refreshes `routes` + `last_synced_at` and
|
|
245
|
+
lands `status: 'synced'` (or `'error'`) on the app row. Poll
|
|
246
|
+
`.get()` for the outcome. Use it after deploying a new manifest,
|
|
247
|
+
fixing a primary URL outage, or recovering from `status: 'error'`.
|
|
248
|
+
|
|
249
|
+
Discovery failure modes the platform handles explicitly — each
|
|
250
|
+
leaves the row at `status: 'error'` with `error` populated:
|
|
251
|
+
|
|
252
|
+
```
|
|
253
|
+
HTTP 404 from primary URL — manifest not found
|
|
254
|
+
HTTP 5xx from primary URL — server error at the app
|
|
255
|
+
request timeout — primary URL slow / hung
|
|
256
|
+
TCP connection refused — deployment not running
|
|
257
|
+
response content-type != application/json — wrong endpoint or proxy
|
|
258
|
+
response body is not a JSON array — manifest schema wrong
|
|
259
|
+
route entry missing required `name` — per-route schema gap
|
|
260
|
+
route entry missing required `path` — per-route schema gap
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Per-route schema failures stop the sync — the platform validates
|
|
264
|
+
every entry in the array and refuses partial saves. Empty
|
|
265
|
+
manifests (`[]`) succeed with zero routes registered.
|
|
266
|
+
|
|
267
|
+
The primary URL has a trailing slash stripped before the
|
|
268
|
+
`/.well-known/routes.json` join, so both `https://app.example.com`
|
|
269
|
+
and `https://app.example.com/` produce the same fetch URL.
|
|
270
|
+
|
|
271
|
+
### Update
|
|
272
|
+
|
|
273
|
+
`PUT` replays the full body. Mutating the primary URL does NOT
|
|
274
|
+
auto-trigger a route sync — call `.syncRoutes` after the update if
|
|
275
|
+
the new primary URL serves a different manifest.
|
|
276
|
+
|
|
277
|
+
### Delete
|
|
278
|
+
|
|
279
|
+
`DELETE` removes the connected app AND the auto-provisioned API
|
|
280
|
+
key. Workflows that still reference the app's id in action
|
|
281
|
+
`connected_app_id` fields fail at run time with the action's
|
|
282
|
+
template-render step erroring on the missing relation. Detach
|
|
283
|
+
workflow actions first.
|
|
284
|
+
|
|
285
|
+
Delete latency depends on the app's `mode`:
|
|
286
|
+
|
|
287
|
+
- `mode: 'self_hosted'` — synchronous. The row is gone when
|
|
288
|
+
the DELETE response returns.
|
|
289
|
+
- `mode: 'managed'` — async. The DELETE response returns
|
|
290
|
+
immediately, but the actual teardown (Cloudflare Pages
|
|
291
|
+
project deletion + DNS unregistration) runs in a background
|
|
292
|
+
worker. The row's `status` transitions through
|
|
293
|
+
`'deploying'` (during teardown) before disappearing. Poll
|
|
294
|
+
`.get()` until 404 to confirm removal.
|
|
295
|
+
|
|
296
|
+
## 6. Runtime: how magic links resolve
|
|
297
|
+
|
|
298
|
+
This section covers the runtime callers — the connected app
|
|
299
|
+
deployment itself, not the admin.
|
|
300
|
+
|
|
301
|
+
At workflow run time, an action with `connected_app_id` +
|
|
302
|
+
`connected_app_route` renders a message body that contains a
|
|
303
|
+
URL of the form:
|
|
304
|
+
|
|
305
|
+
```
|
|
306
|
+
https://<primary-url>/t/<short_path>
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
The customer clicks the link, lands on the app, and the app
|
|
310
|
+
resolves the page:
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
const { data: page } = await api.connectedApps.resolvePage(
|
|
314
|
+
tenantSlug, slug, // tenant + connected-app slug
|
|
315
|
+
{
|
|
316
|
+
short_path: '<the-short-path-from-the-URL>',
|
|
317
|
+
user_agent: req.headers.get('user-agent') ?? '',
|
|
318
|
+
},
|
|
319
|
+
)
|
|
320
|
+
// page.message — the rendered message row
|
|
321
|
+
// page.message.body — the regulated rendered body (raw)
|
|
322
|
+
// page.route_path — the route the link was minted against
|
|
323
|
+
// (e.g. '/forms/review')
|
|
324
|
+
// page.metadata — parsed `connected_app_metadata_template`
|
|
325
|
+
// output from the workflow action; arbitrary JSON
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
The app routes to the page named by `route_path`, renders the
|
|
329
|
+
form using `metadata` for prefill, and updates tracking when
|
|
330
|
+
the customer opens or submits the form:
|
|
331
|
+
|
|
332
|
+
```typescript
|
|
333
|
+
await api.connectedApps.updateMessageTracking(
|
|
334
|
+
tenantSlug, slug,
|
|
335
|
+
{
|
|
336
|
+
short_path: '<same-short-path>',
|
|
337
|
+
opened_at: new Date().toISOString(),
|
|
338
|
+
form_submitted_at: new Date().toISOString(), // only when form completes
|
|
339
|
+
},
|
|
340
|
+
)
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
Both calls are authenticated with the connected app's
|
|
344
|
+
auto-provisioned machine-to-machine API key (typically configured as
|
|
345
|
+
`ALVERA_API_KEY` in the deployment environment).
|
|
346
|
+
|
|
347
|
+
### Workflow-side wiring
|
|
348
|
+
|
|
349
|
+
A workflow action that emits a magic link must set:
|
|
350
|
+
|
|
351
|
+
```
|
|
352
|
+
connected_app_id UUID — the registered app
|
|
353
|
+
connected_app_route string — must match a discovered route's path
|
|
354
|
+
(e.g. '/forms/review')
|
|
355
|
+
connected_app_metadata_template string — Liquid template rendering a JSON
|
|
356
|
+
object the resolved page returns
|
|
357
|
+
to the app (arbitrary shape; the
|
|
358
|
+
app decides how to use it for
|
|
359
|
+
prefill, headers, etc.)
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
See `workflows.md` §7 for the action-level field detail.
|
|
363
|
+
|
|
364
|
+
## 7. Gotchas
|
|
365
|
+
|
|
366
|
+
1. **Exactly one URL must be `is_primary: true`.** Zero or
|
|
367
|
+
multiple primaries returns 422. The primary is what the
|
|
368
|
+
platform fetches routes from AND what magic links are minted
|
|
369
|
+
against; additional URLs are reference-only.
|
|
370
|
+
|
|
371
|
+
2. **`status: 'error'` after create is recoverable.** A failed
|
|
372
|
+
route fetch (manifest 404, malformed JSON, primary URL DNS
|
|
373
|
+
failure) doesn't reject the row. Fix the deployment and call
|
|
374
|
+
`.syncRoutes(id)` to retry.
|
|
375
|
+
|
|
376
|
+
3. **Mode `'managed'` is reserved.** It's structurally valid in
|
|
377
|
+
the request type but no deploy fires today. Use `'self_hosted'`
|
|
378
|
+
and run your own deployment.
|
|
379
|
+
|
|
380
|
+
4. **`.syncRoutes` is NOT automatic.** The platform only fetches
|
|
381
|
+
the manifest at create time and on explicit re-sync. Operators
|
|
382
|
+
who change their `/.well-known/routes.json` between deploys
|
|
383
|
+
must trigger a sync.
|
|
384
|
+
|
|
385
|
+
5. **Runtime calls use `slug`, not `id`.** `.resolvePage` and
|
|
386
|
+
`.updateMessageTracking` skip the datalake path segment and
|
|
387
|
+
key on the app's slug + tenant. The auto-provisioned M2M key
|
|
388
|
+
is tenant-scoped, so the connected-app deployment authenticates
|
|
389
|
+
without knowing the datalake.
|
|
390
|
+
|
|
391
|
+
6. **`connected_app_route` on a workflow action must match a
|
|
392
|
+
discovered route.** Action templates reference
|
|
393
|
+
`{{ connected_app_form_url }}` which the platform constructs
|
|
394
|
+
from the bound app's primary URL + the route path. A route
|
|
395
|
+
that hasn't been synced into the app's `routes` array still
|
|
396
|
+
accepts the action at create time but the magic-link
|
|
397
|
+
resolution fails when a customer clicks the link.
|
|
398
|
+
|
|
399
|
+
7. **The auto-provisioned API key is revealed ONCE.** It appears
|
|
400
|
+
on the create response and is never returned again. The
|
|
401
|
+
create-flow caller is responsible for capturing the plaintext
|
|
402
|
+
value and configuring it into the connected-app deployment's
|
|
403
|
+
environment. The platform stores only the key id on the row.
|
|
404
|
+
|
|
405
|
+
8. **Route manifests must be JSON arrays, not objects.** A
|
|
406
|
+
common mistake is wrapping the routes in `{ "routes": [...] }`
|
|
407
|
+
— the manifest's top-level must be the array itself.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Cookbook fixtures
|
|
2
|
+
|
|
3
|
+
Vendored Liquid templates and CSV fixtures the
|
|
4
|
+
[cookbook recipes](../) load at runtime via
|
|
5
|
+
`fs.readFileSync`. Organized by industry so the corpus stays
|
|
6
|
+
self-describing — an agent crawling this directory can list
|
|
7
|
+
every template available to every cookbook in a given industry
|
|
8
|
+
by reading one path.
|
|
9
|
+
|
|
10
|
+
## Convention
|
|
11
|
+
|
|
12
|
+
Cookbook code reads fixtures via the
|
|
13
|
+
`COOKBOOK_FIXTURES_DIR` environment variable. The validator
|
|
14
|
+
(`make validate-cookbook`) exports this variable to the spawned
|
|
15
|
+
`bun test` process; any operator script that runs cookbook code
|
|
16
|
+
directly must set the same variable to the absolute path of
|
|
17
|
+
this directory.
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import fs from 'node:fs'
|
|
21
|
+
import path from 'node:path'
|
|
22
|
+
|
|
23
|
+
const LE_TEMPLATE = fs.readFileSync(
|
|
24
|
+
path.join(
|
|
25
|
+
process.env.COOKBOOK_FIXTURES_DIR!,
|
|
26
|
+
'foundation/_lead_submissions_foundation_legal_entity.liquid',
|
|
27
|
+
),
|
|
28
|
+
'utf8',
|
|
29
|
+
)
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Same env-var pattern as the `ALVERA_BASE_URL` /
|
|
33
|
+
`ALVERA_ROOT_EMAIL` / `ALVERA_ROOT_PASSWORD` credentials the
|
|
34
|
+
generated specs already consume.
|
|
35
|
+
|
|
36
|
+
## Why fixtures instead of inline templates
|
|
37
|
+
|
|
38
|
+
Inlining each template verbatim inside the cookbook markdown
|
|
39
|
+
would bloat each recipe to several hundred extra lines of
|
|
40
|
+
Liquid noise, which obscures the cookbook's actual lesson (the
|
|
41
|
+
sequence of SDK calls plus the assertions). Loading templates
|
|
42
|
+
by path preserves cookbook readability AND keeps recipes
|
|
43
|
+
executable — the cookbook is shipped as part of the
|
|
44
|
+
`@alvera-ai/platform-sdk` npm package, the fixtures ship
|
|
45
|
+
alongside it via the package's `files: [.agent]` field, and
|
|
46
|
+
the env-var dereference works for any consumer that installed
|
|
47
|
+
the package.
|
|
48
|
+
|
|
49
|
+
## Provenance
|
|
50
|
+
|
|
51
|
+
Templates are copied verbatim from
|
|
52
|
+
[platform/priv/liquid_templates/](https://github.com/alvera-ai/platform/tree/develop/priv/liquid_templates),
|
|
53
|
+
the production source-of-truth. The same vendoring chain
|
|
54
|
+
already exists in
|
|
55
|
+
`platform/integration-tests/tests/<industry>/fixtures/templates/`;
|
|
56
|
+
this directory extends that chain into the SDK package so the
|
|
57
|
+
cookbook corpus is self-contained at npm-install time.
|
|
58
|
+
|
|
59
|
+
When a production template changes, re-copy the file here and
|
|
60
|
+
re-run `make validate-cookbook` to confirm the cookbook still
|
|
61
|
+
passes against the new template shape.
|
|
62
|
+
|
|
63
|
+
## Per-industry inventory
|
|
64
|
+
|
|
65
|
+
### `foundation/`
|
|
66
|
+
|
|
67
|
+
| File | Consumed by cookbook(s) | Source-of-truth |
|
|
68
|
+
|---|---|---|
|
|
69
|
+
| `_lead_submissions_foundation_legal_entity.liquid` | `birthday-greeting-sms-trigger`, `score-leads-with-llm-categorization` | `priv/liquid_templates/data_activation/interoperability/foundation/airflow/_lead_submissions_foundation_legal_entity.liquid` |
|
|
70
|
+
| `_lead_submissions_foundation_generic_table.liquid` | `score-leads-with-llm-categorization` | `priv/liquid_templates/data_activation/interoperability/foundation/airflow/_lead_submissions_foundation_generic_table.liquid` |
|
|
71
|
+
| `_lead_submissions_foundation_mdm.liquid` | `score-leads-with-llm-categorization` | `priv/liquid_templates/data_activation/interoperability/foundation/airflow/_lead_submissions_foundation_mdm.liquid` |
|
|
72
|
+
|
|
73
|
+
### `healthcare/`
|
|
74
|
+
|
|
75
|
+
| File | Consumed by cookbook(s) | Source-of-truth |
|
|
76
|
+
|---|---|---|
|
|
77
|
+
| `_cahps_appointments_healthcare_patient.liquid` | `appointment-review-sms-workflow` | `priv/liquid_templates/data_activation/interoperability/healthcare/fhir4/athena/_cahps_appointments_healthcare_patient.liquid` |
|
|
78
|
+
| `_cahps_appointments_healthcare_appointment.liquid` | `appointment-review-sms-workflow` | `priv/liquid_templates/data_activation/interoperability/healthcare/fhir4/athena/_cahps_appointments_healthcare_appointment.liquid` |
|
|
79
|
+
| `_cahps_appointments_healthcare_mdm.liquid` | `appointment-review-sms-workflow` | `priv/liquid_templates/data_activation/interoperability/healthcare/fhir4/athena/_cahps_appointments_healthcare_mdm.liquid` |
|
|
80
|
+
|
|
81
|
+
The `contact-us-triage-with-llm` cookbook ingests into a generic
|
|
82
|
+
table via the auto-provisioned identity contract, so it loads no
|
|
83
|
+
vendored template.
|
|
84
|
+
|
|
85
|
+
### `accounts_receivable/`
|
|
86
|
+
|
|
87
|
+
| File | Consumed by cookbook(s) | Source-of-truth |
|
|
88
|
+
|---|---|---|
|
|
89
|
+
| `_customers_accounts_receivable_customer.liquid` | `welcome-sms-for-customers`, `dunning-sms-for-delinquent` | `priv/liquid_templates/data_activation/interoperability/accounts_receivable/stripe/_customers_accounts_receivable_customer.liquid` |
|
|
90
|
+
| `_customers_accounts_receivable_mdm.liquid` | `welcome-sms-for-customers`, `dunning-sms-for-delinquent` | `priv/liquid_templates/data_activation/interoperability/accounts_receivable/stripe/_customers_accounts_receivable_mdm.liquid` |
|
|
91
|
+
|
|
92
|
+
### `payment_risk/`
|
|
93
|
+
|
|
94
|
+
| File | Consumed by cookbook(s) | Source-of-truth |
|
|
95
|
+
|---|---|---|
|
|
96
|
+
| `_payment_accounts_payment_risk_payment_account.liquid` | `kyc-notification-on-account-activation` | `priv/liquid_templates/data_activation/interoperability/payment_risk/atomic_fi/_payment_accounts_payment_risk_payment_account.liquid` |
|
|
97
|
+
| `_payment_accounts_payment_risk_mdm.liquid` | `kyc-notification-on-account-activation` | `priv/liquid_templates/data_activation/interoperability/payment_risk/atomic_fi/_payment_accounts_payment_risk_mdm.liquid` |
|
|
98
|
+
| `_compliance_screenings_payment_risk_compliance_screening.liquid` | `sanctions-screening-with-agent-review` | `priv/liquid_templates/data_activation/interoperability/payment_risk/atomic_fi/_compliance_screenings_payment_risk_compliance_screening.liquid` |
|
|
99
|
+
| `_compliance_screenings_payment_risk_mdm.liquid` | `sanctions-screening-with-agent-review` | `priv/liquid_templates/data_activation/interoperability/payment_risk/atomic_fi/_compliance_screenings_payment_risk_mdm.liquid` |
|
package/.agent/cookbook/_fixtures/accounts_receivable/_customers_accounts_receivable_customer.liquid
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{% comment %}Stripe Customers → Accounts Receivable Customer attrs{% endcomment %}
|
|
2
|
+
{% assign p = msg %}
|
|
3
|
+
{
|
|
4
|
+
"customer_type": "{{ p.customer_type | default: "individual" | json_escape }}",
|
|
5
|
+
"status": "{{ p.status | default: "prospect" | json_escape }}",
|
|
6
|
+
"customer_number": "{{ p.customer_number | json_escape }}",
|
|
7
|
+
{% if p.currency and p.currency != "" %}"currency": "{{ p.currency | json_escape }}",{% endif %}
|
|
8
|
+
{% if p.delinquent and p.delinquent != "" %}"delinquent": {{ p.delinquent }},{% endif %}
|
|
9
|
+
{% if p.tax_exempt and p.tax_exempt != "" %}"tax_exempt": "{{ p.tax_exempt | json_escape }}",{% endif %}
|
|
10
|
+
{% if p.preferred_locales and p.preferred_locales != "" %}{% assign locales = p.preferred_locales | split: "|" %}"preferred_locales": [{% for l in locales %}"{{ l | json_escape }}"{% unless forloop.last %},{% endunless %}{% endfor %}],{% endif %}
|
|
11
|
+
"regulated_customer": {
|
|
12
|
+
{% if p.name and p.name != "" %}"name": "{{ p.name | json_escape }}",{% endif %}
|
|
13
|
+
{% if p.email and p.email != "" %}"email": "{{ p.email | json_escape }}",{% endif %}
|
|
14
|
+
{% if p.phone and p.phone != "" %}"phone": "{{ p.phone | json_escape }}",{% endif %}
|
|
15
|
+
{% if p.tax_id and p.tax_id != "" %}"tax_id": "{{ p.tax_id | json_escape }}",{% endif %}
|
|
16
|
+
{% if p.address_line1 and p.address_line1 != "" %}"address_line1": "{{ p.address_line1 | json_escape }}",{% endif %}
|
|
17
|
+
{% if p.address_line2 and p.address_line2 != "" %}"address_line2": "{{ p.address_line2 | json_escape }}",{% endif %}
|
|
18
|
+
{% if p.address_city and p.address_city != "" %}"address_city": "{{ p.address_city | json_escape }}",{% endif %}
|
|
19
|
+
{% if p.address_state and p.address_state != "" %}"address_state": "{{ p.address_state | json_escape }}",{% endif %}
|
|
20
|
+
{% if p.address_postal_code and p.address_postal_code != "" %}"address_postal_code": "{{ p.address_postal_code | json_escape }}",{% endif %}
|
|
21
|
+
{% if p.address_country and p.address_country != "" %}"address_country": "{{ p.address_country | json_escape }}",{% endif %}
|
|
22
|
+
{% if p.shipping_name and p.shipping_name != "" %}"shipping_name": "{{ p.shipping_name | json_escape }}",{% endif %}
|
|
23
|
+
{% if p.shipping_phone and p.shipping_phone != "" %}"shipping_phone": "{{ p.shipping_phone | json_escape }}",{% endif %}
|
|
24
|
+
{% if p.shipping_address_line1 and p.shipping_address_line1 != "" %}"shipping_address_line1": "{{ p.shipping_address_line1 | json_escape }}",{% endif %}
|
|
25
|
+
{% if p.shipping_address_line2 and p.shipping_address_line2 != "" %}"shipping_address_line2": "{{ p.shipping_address_line2 | json_escape }}",{% endif %}
|
|
26
|
+
{% if p.shipping_address_city and p.shipping_address_city != "" %}"shipping_address_city": "{{ p.shipping_address_city | json_escape }}",{% endif %}
|
|
27
|
+
{% if p.shipping_address_state and p.shipping_address_state != "" %}"shipping_address_state": "{{ p.shipping_address_state | json_escape }}",{% endif %}
|
|
28
|
+
{% if p.shipping_address_postal_code and p.shipping_address_postal_code != "" %}"shipping_address_postal_code": "{{ p.shipping_address_postal_code | json_escape }}",{% endif %}
|
|
29
|
+
"shipping_address_country": "{{ p.shipping_address_country | default: "" | json_escape }}"
|
|
30
|
+
},
|
|
31
|
+
"source_uri": "{{ p.source_uri | default: "" | json_escape }}"
|
|
32
|
+
}
|
package/.agent/cookbook/_fixtures/accounts_receivable/_customers_accounts_receivable_mdm.liquid
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{% comment %}Stripe Customers → Accounts Receivable MDM Input{% endcomment %}
|
|
2
|
+
{% assign p = msg %}
|
|
3
|
+
{
|
|
4
|
+
"identifiers": [
|
|
5
|
+
{
|
|
6
|
+
"system": "urn:{{ p.source_uri | json_escape }}:customer-number",
|
|
7
|
+
"value": "{{ p.customer_number | json_escape }}"
|
|
8
|
+
}
|
|
9
|
+
],
|
|
10
|
+
{% assign space_parts = p.name | split: " " %}
|
|
11
|
+
{% if space_parts.size > 1 %}
|
|
12
|
+
"given_name": "{{ space_parts | first | json_escape }}",
|
|
13
|
+
"family_name": "{{ space_parts | last | json_escape }}",
|
|
14
|
+
{% else %}
|
|
15
|
+
"family_name": "{{ p.name | strip | json_escape }}",
|
|
16
|
+
{% endif %}
|
|
17
|
+
{% if p.phone and p.phone != "" %}"phone": "{{ p.phone | json_escape }}",{% endif %}
|
|
18
|
+
{% if p.email and p.email != "" %}"email": "{{ p.email | json_escape }}",{% endif %}
|
|
19
|
+
"gender": "unknown"
|
|
20
|
+
}
|
package/.agent/cookbook/_fixtures/foundation/_lead_submissions_foundation_generic_table.liquid
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{% comment %}
|
|
2
|
+
Foundation Airflow Lead Form → `lead_submissions` generic_table row.
|
|
3
|
+
|
|
4
|
+
Maps a single Google-Sheet lead row into the `lead_submissions` GT
|
|
5
|
+
upsert shape — one JSON field per GT column. Used by Contract B
|
|
6
|
+
(`resource_type: "generic_table"`, `generic_table_id` pinned to the
|
|
7
|
+
`lead_submissions` GT). Contract B also carries an
|
|
8
|
+
`mdm_input_config` (separate template) so the GT event emitted on
|
|
9
|
+
upsert pins `mdm_subject_id == legal_entity.id` (the LE that
|
|
10
|
+
Contract A wrote / Foundation MDM resolved by digital_identifier
|
|
11
|
+
email dedupe).
|
|
12
|
+
|
|
13
|
+
GT columns:
|
|
14
|
+
* `submission_id` — vendor-supplied unique sheet row id (unique col,
|
|
15
|
+
so re-ingest of the same submission_id idempotently updates the
|
|
16
|
+
same row instead of inserting a duplicate)
|
|
17
|
+
* `name`, `email`, `phone` — tokenized PII
|
|
18
|
+
* `company` — empty string for individual leads, non-empty for
|
|
19
|
+
business leads (mirrors Contract A's branch logic)
|
|
20
|
+
* `message` — free-text lead body
|
|
21
|
+
* `lead_source` — utm_source / sheet tab id, surfaced for downstream
|
|
22
|
+
attribution dashboards
|
|
23
|
+
{% endcomment %}
|
|
24
|
+
{% assign p = msg %}
|
|
25
|
+
{
|
|
26
|
+
"submission_id": "{{ p.submission_id | json_escape }}",
|
|
27
|
+
"name": "{{ p.name | default: "" | json_escape }}",
|
|
28
|
+
"email": "{{ p.email | default: "" | json_escape }}",
|
|
29
|
+
"phone": "{{ p.phone | default: "" | json_escape }}",
|
|
30
|
+
"company": "{{ p.company | default: "" | json_escape }}",
|
|
31
|
+
"message": "{{ p.message | default: "" | json_escape }}",
|
|
32
|
+
"lead_source": "{{ p.lead_source | default: "" | json_escape }}"
|
|
33
|
+
}
|