@alvera-ai/platform-sdk 0.12.1 → 0.13.0-next.gf3d2a4a
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 +121 -19
- package/.agent/account_management.md +79 -38
- package/.agent/action_logs.md +144 -0
- package/.agent/action_status_updaters.md +237 -53
- package/.agent/ai_agents.md +149 -53
- package/.agent/ai_sandbox.md +11 -4
- package/.agent/async.md +16 -6
- package/.agent/connected_apps.md +116 -4
- package/.agent/cookbook/_fixtures/README.md +9 -9
- package/.agent/cookbook/_fixtures/{payment_risk/_compliance_screenings_payment_risk_compliance_screening.liquid → payments/_compliance_screenings_payments_compliance_screening.liquid} +1 -1
- package/.agent/cookbook/_fixtures/{payment_risk/_compliance_screenings_payment_risk_mdm.liquid → payments/_compliance_screenings_payments_mdm.liquid} +1 -1
- package/.agent/cookbook/_fixtures/{payment_risk/_payment_accounts_payment_risk_mdm.liquid → payments/_payment_accounts_payments_mdm.liquid} +1 -1
- package/.agent/cookbook/_fixtures/{payment_risk/_payment_accounts_payment_risk_payment_account.liquid → payments/_payment_accounts_payments_payment_account.liquid} +1 -1
- package/.agent/cookbook/_fixtures/{accounts_receivable/_customers_accounts_receivable_customer.liquid → subscription/_customers_subscription_customer.liquid} +1 -1
- package/.agent/cookbook/_fixtures/{accounts_receivable/_customers_accounts_receivable_mdm.liquid → subscription/_customers_subscription_mdm.liquid} +1 -1
- package/.agent/cookbook/_setup/foundation.md +24 -4
- package/.agent/cookbook/_setup/healthcare.md +24 -4
- package/.agent/cookbook/_setup/{payment_risk.md → payments.md} +51 -31
- package/.agent/cookbook/_setup/{accounts_receivable.md → subscription.md} +51 -31
- package/.agent/cookbook/action-status-updaters.md +25 -18
- package/.agent/cookbook/bulk-ingest.md +15 -15
- package/.agent/cookbook/dunning-sms-for-delinquent.md +21 -21
- package/.agent/cookbook/{custom-tables.md → generic-tables.md} +44 -41
- package/.agent/cookbook/invite-team.md +19 -13
- package/.agent/cookbook/kyc-notification-on-account-activation.md +26 -26
- package/.agent/cookbook/marketing-campaign-send.md +1003 -0
- package/.agent/cookbook/rest-fetch.md +9 -9
- package/.agent/cookbook/sanctions-screening-with-agent-review.md +22 -22
- package/.agent/cookbook/system-templates.md +15 -15
- package/.agent/cookbook/triage-prospects-by-priority.md +17 -17
- package/.agent/cookbook/welcome-sms-for-customers.md +21 -21
- package/.agent/data_activation_clients.md +177 -42
- package/.agent/data_sources.md +55 -37
- package/.agent/datalakes.md +30 -17
- package/.agent/debugging.md +48 -14
- package/.agent/errors.md +39 -17
- package/.agent/generic_tables.md +150 -51
- package/.agent/interoperability_contracts.md +114 -15
- package/.agent/mdm.md +171 -93
- package/.agent/messages.md +252 -0
- package/.agent/templates.md +21 -4
- package/.agent/tool-call-configs.md +1 -1
- package/.agent/tools.md +150 -60
- package/.agent/type_naming.md +79 -18
- package/.agent/workflows.md +408 -62
- package/LICENSE +21 -93
- package/README.md +2 -2
- package/dist/bin/platform-sdk.mjs +53 -1
- package/dist/bin/platform-sdk.mjs.map +1 -1
- package/dist/index.d.mts +582 -66
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +654 -105
- package/dist/index.mjs.map +1 -1
- package/package.json +10 -6
- /package/.agent/cookbook/_fixtures/{accounts_receivable → subscription}/stripe_customers_batch1.csv +0 -0
package/.agent/ai_sandbox.md
CHANGED
|
@@ -72,9 +72,9 @@ Liquid's stock tag set (`{% if %}`, `{% for %}`, `{% assign %}`,
|
|
|
72
72
|
`{% capture %}` writes to a Liquid-scope variable; it is not a
|
|
73
73
|
runtime eval escape hatch.
|
|
74
74
|
|
|
75
|
-
### The
|
|
75
|
+
### The 13-filter custom allowlist
|
|
76
76
|
|
|
77
|
-
Beyond stock Liquid, the platform exposes exactly **
|
|
77
|
+
Beyond stock Liquid, the platform exposes exactly **thirteen**
|
|
78
78
|
custom filters. Adding new ones requires a platform release —
|
|
79
79
|
consumers cannot extend the set:
|
|
80
80
|
|
|
@@ -101,6 +101,13 @@ consumers cannot extend the set:
|
|
|
101
101
|
now Current UTC datetime, ISO 8601.
|
|
102
102
|
Example: {{ '' | now }} → 2026-03-15T14:30:00Z
|
|
103
103
|
|
|
104
|
+
minutes_ago Unix ms N minutes before a unix-ms input. Bounds
|
|
105
|
+
the start of an ActionStatusUpdater poll window.
|
|
106
|
+
Example: {{ now | minutes_ago: 45 }} → 1773585000000
|
|
107
|
+
|
|
108
|
+
hours_ago Unix ms N hours before a unix-ms input.
|
|
109
|
+
Example: {{ now | hours_ago: 2 }} → 1773578400000
|
|
110
|
+
|
|
104
111
|
uuid Generate a new v4 UUID.
|
|
105
112
|
Example: {{ '' | uuid }} → a1b2c3d4-…
|
|
106
113
|
|
|
@@ -218,7 +225,7 @@ it sits inside the same Layer-3 boundary:
|
|
|
218
225
|
│
|
|
219
226
|
┌────────────────────▼────────────────────────┐
|
|
220
227
|
│ Layer 2 — Liquid sandbox │
|
|
221
|
-
│
|
|
228
|
+
│ 13-filter allowlist │
|
|
222
229
|
│ + parse-on-write │
|
|
223
230
|
│ + render-time output_schema check │
|
|
224
231
|
│ Decides: what the consumer-authored body │
|
|
@@ -260,7 +267,7 @@ guarantees it captures back-stop Layer 1.
|
|
|
260
267
|
`'regulated'` only when the action genuinely needs raw
|
|
261
268
|
values, and pair with a session role that's authorised.
|
|
262
269
|
|
|
263
|
-
2. **Use only the
|
|
270
|
+
2. **Use only the 13 custom filters** named in Layer 2. Bodies
|
|
264
271
|
that reference other filter names will silently fall through
|
|
265
272
|
to stock Liquid behavior and produce malformed output.
|
|
266
273
|
|
package/.agent/async.md
CHANGED
|
@@ -40,10 +40,16 @@ resource-specific. Common examples:
|
|
|
40
40
|
| Resource | Terminal success | Terminal failure |
|
|
41
41
|
|-----------------------------------|------------------|-------------------------|
|
|
42
42
|
| Datalake (create + migrate) | `ready` | — (no terminal failure) |
|
|
43
|
-
|
|
|
44
|
-
| AWS Lambda tool deploy | `deployed` | `failed` |
|
|
43
|
+
| AWS Lambda tool deploy | `create_complete`| `create_failed` |
|
|
45
44
|
| Workflow run | `completed` | `failed` |
|
|
46
45
|
|
|
46
|
+
**A data-activation-client run is NOT on this list — it is not a
|
|
47
|
+
`waitUntilReady` status-poll target.** `.ingest` / `.ingestFile` /
|
|
48
|
+
`.runManually` return a `202` + a `batch_id` (not a saved row and not
|
|
49
|
+
a pollable status enum); progress is tracked by diffing the DAC's log
|
|
50
|
+
and dataset rows by that `batch_id`, not by polling a `success`/`error`
|
|
51
|
+
status. See `data_activation_clients.md` §6.
|
|
52
|
+
|
|
47
53
|
The Datalake row is the odd one: its top-level `status` enum is
|
|
48
54
|
`new | processing | ready` only — there is no terminal failure
|
|
49
55
|
value. A migration that fails leaves the datalake at
|
|
@@ -59,7 +65,10 @@ CloudFormation deploy status nested under the polymorphic
|
|
|
59
65
|
`body`), not on the top-level `tool.status` enum (which is
|
|
60
66
|
`draft | active | inactive | error | marked_for_deletion`).
|
|
61
67
|
`tool.status` flips to `'active'` after a successful deploy
|
|
62
|
-
but `stack_status` is the more precise readiness signal
|
|
68
|
+
but `stack_status` is the more precise readiness signal: its
|
|
69
|
+
terminal-success value is the CloudFormation `create_complete`
|
|
70
|
+
(`update_complete` on a redeploy), and a failed deploy lands on
|
|
71
|
+
a `*_failed` state (`create_failed`, `rollback_failed`, …) — see
|
|
63
72
|
`tools.md` §2 for the per-body field set.
|
|
64
73
|
|
|
65
74
|
Each resource's MD §5 (Lifecycle) names its terminal-success
|
|
@@ -79,9 +88,10 @@ the caller.
|
|
|
79
88
|
## Idempotency on create
|
|
80
89
|
|
|
81
90
|
`create()` calls are NOT idempotent by default. Repeating a
|
|
82
|
-
`create()` with the same body yields a
|
|
83
|
-
(typically the name within scope
|
|
84
|
-
|
|
91
|
+
`create()` with the same body yields a **409 Conflict** on
|
|
92
|
+
uniqueness fields (typically the name within scope — uniqueness
|
|
93
|
+
collisions are 409, not 422; see `errors.md`). Consumers handling
|
|
94
|
+
retry should:
|
|
85
95
|
|
|
86
96
|
1. Catch the thrown error after the call returns (see `errors.md`).
|
|
87
97
|
2. Inspect for a uniqueness `detail` on a `source.pointer`
|
package/.agent/connected_apps.md
CHANGED
|
@@ -27,6 +27,15 @@ The namespace splits into **two roles**:
|
|
|
27
27
|
machine-to-machine API key) when a customer hits a magic link. Uses the app's
|
|
28
28
|
`slug` as the path key.
|
|
29
29
|
|
|
30
|
+
**Connected apps are single-tenant, full stop.** A connected app
|
|
31
|
+
belongs to exactly ONE tenant (and one datalake under it): its
|
|
32
|
+
auto-provisioned M2M key is tenant-scoped, its magic links resolve
|
|
33
|
+
against that tenant's data, and its publishable browser key carries
|
|
34
|
+
that tenant's origin allowlist. Do NOT design a connected app that
|
|
35
|
+
serves multiple tenants from one deployment — per tenant, register
|
|
36
|
+
a separate connected app (separate keys, separate routes). There is
|
|
37
|
+
no cross-tenant resolution path, by construction.
|
|
38
|
+
|
|
30
39
|
Connected apps are **Datalake-DB-resident** — the parent datalake
|
|
31
40
|
must be `status: 'ready'` before you `create()`.
|
|
32
41
|
|
|
@@ -79,6 +88,15 @@ or multiple primaries returns a 422 on `/urls`.
|
|
|
79
88
|
Additional URLs (staging, preview) are stored for reference but
|
|
80
89
|
aren't used for route discovery or link minting.
|
|
81
90
|
|
|
91
|
+
**`urls` models environments, NOT tracking domains.** The list is
|
|
92
|
+
production / staging / preview deployments of the *same* app — and the
|
|
93
|
+
one flagged `is_primary` silently becomes the domain of **every** magic
|
|
94
|
+
link this app mints (see §6). It is NOT a way to serve links from
|
|
95
|
+
several tracking domains: to mint links on two different domains you
|
|
96
|
+
need **two connected apps**, one per domain. An agent that sees
|
|
97
|
+
`urls: [...]` and reaches for "add another tracking domain here" gets
|
|
98
|
+
this wrong — reach for a second connected app instead.
|
|
99
|
+
|
|
82
100
|
### Route discovery is automatic + re-triggerable
|
|
83
101
|
|
|
84
102
|
```
|
|
@@ -219,7 +237,8 @@ and can be retried via `.syncRoutes`.
|
|
|
219
237
|
### Read shapes — management
|
|
220
238
|
|
|
221
239
|
```
|
|
222
|
-
.list(tenantSlug, datalakeSlug)
|
|
240
|
+
.list(tenantSlug, datalakeSlug, query?) Paged: { data, meta }
|
|
241
|
+
(full Flop query — type_naming.md)
|
|
223
242
|
.get(tenantSlug, datalakeSlug, id) One row, by UUID
|
|
224
243
|
.metadata(tenantSlug, datalakeSlug) Markdown catalog
|
|
225
244
|
.metadataDetails(tenantSlug, datalakeSlug, Markdown for one app
|
|
@@ -246,6 +265,13 @@ lands `status: 'synced'` (or `'error'`) on the app row asynchronously.
|
|
|
246
265
|
Poll `.get()` for the outcome. Use it after deploying a new manifest,
|
|
247
266
|
fixing a primary URL outage, or recovering from `status: 'error'`.
|
|
248
267
|
|
|
268
|
+
**Route-sync is the only "does this domain work" check the platform
|
|
269
|
+
performs — there is NO DNS verification.** The platform never resolves
|
|
270
|
+
or validates the primary URL's DNS/ownership; it just attempts the
|
|
271
|
+
HTTP fetch of `/.well-known/routes.json`. A `status: 'synced'` means
|
|
272
|
+
"the manifest was fetched and parsed", not "this domain is verified as
|
|
273
|
+
yours". Domain trust is your responsibility, not a platform gate.
|
|
274
|
+
|
|
249
275
|
Discovery failure modes the platform handles explicitly — each
|
|
250
276
|
leaves the row at `status: 'error'` with `error` populated:
|
|
251
277
|
|
|
@@ -294,7 +320,62 @@ Delete latency depends on the app's `mode`:
|
|
|
294
320
|
`'deploying'` (during teardown) before disappearing. Poll
|
|
295
321
|
`.get()` until 404 to confirm removal.
|
|
296
322
|
|
|
297
|
-
## 6.
|
|
323
|
+
## 6. How a tracked link is minted
|
|
324
|
+
|
|
325
|
+
Before the runtime resolve flow, understand what the platform does at
|
|
326
|
+
fire time to produce the link. When a workflow action that carries a
|
|
327
|
+
`connected_app_id` executes, the platform mints a **page token** and
|
|
328
|
+
injects its URL into the message body:
|
|
329
|
+
|
|
330
|
+
```
|
|
331
|
+
action fires
|
|
332
|
+
│ action.connected_app_id + connected_app_route
|
|
333
|
+
│ + rendered connected_app_metadata_template (Liquid → JSON)
|
|
334
|
+
▼
|
|
335
|
+
page token row ── keyed on the MDM SUBJECT id + the route,
|
|
336
|
+
│ carrying the rendered metadata JSON INSIDE it
|
|
337
|
+
▼
|
|
338
|
+
{{ connected_app_form_url }} = https://<primary-url>/t/<url_hash>
|
|
339
|
+
│ (primary-url = the connected app's is_primary URL)
|
|
340
|
+
▼
|
|
341
|
+
message row created with the link in its body
|
|
342
|
+
│
|
|
343
|
+
▼
|
|
344
|
+
page token backfilled with that message's id ← wires token → message
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
Key consequences to design around:
|
|
348
|
+
|
|
349
|
+
- **The `connected_app_metadata_template` is how per-recipient data
|
|
350
|
+
reaches the app without hard-coding a URL.** It's a Liquid template
|
|
351
|
+
that renders to a JSON object, stored *inside* the page token; the
|
|
352
|
+
app reads it back at `resolvePage` time (as `page.metadata`) and uses
|
|
353
|
+
it to prefill / redirect that specific recipient. This is the
|
|
354
|
+
mechanism for "send each recipient to their own destination" — put
|
|
355
|
+
the per-recipient values in the metadata template, not in the URL.
|
|
356
|
+
|
|
357
|
+
- **No MDM subject ⇒ no page token ⇒ no link.** The page token
|
|
358
|
+
*requires* a subject id (it is keyed on the resolved MDM subject). If
|
|
359
|
+
the row didn't resolve to a subject, the mint fails — but **the send
|
|
360
|
+
still goes out**: the mint failure is a logged warning, not an action
|
|
361
|
+
failure, so `{{ connected_app_form_url }}` simply renders **empty**
|
|
362
|
+
and the recipient gets a message with a missing link. A
|
|
363
|
+
message-with-no-link is the symptom of a workflow whose
|
|
364
|
+
`skip_mdm_resolution` was left `true`, or whose row carries no entity
|
|
365
|
+
FK (see `workflows.md` §3). If your action mints links, the workflow
|
|
366
|
+
MUST resolve a subject.
|
|
367
|
+
|
|
368
|
+
- **Tracked links expire — 7 days by default.** The page token is
|
|
369
|
+
stamped with a TTL (604,800 s) at mint; `resolvePage` checks
|
|
370
|
+
`expires_at` and an expired token fails to resolve (HTTP 410).
|
|
371
|
+
"How long does a campaign link stay live?" — a week, unless the
|
|
372
|
+
platform default changes. Long-lead campaigns must account for it.
|
|
373
|
+
|
|
374
|
+
The `{{ connected_app_form_url }}` variable is available only in action
|
|
375
|
+
bodies whose action has a `connected_app_id`; it renders empty
|
|
376
|
+
otherwise.
|
|
377
|
+
|
|
378
|
+
## 7. Runtime: how magic links resolve
|
|
298
379
|
|
|
299
380
|
This section covers the runtime callers — the connected app
|
|
300
381
|
deployment itself, not the admin.
|
|
@@ -343,7 +424,16 @@ await api.connectedApps.updateMessageTracking(
|
|
|
343
424
|
|
|
344
425
|
Both calls are authenticated with the connected app's
|
|
345
426
|
auto-provisioned machine-to-machine API key (typically configured as
|
|
346
|
-
`ALVERA_API_KEY` in the deployment environment).
|
|
427
|
+
`ALVERA_API_KEY` in the deployment environment). Both are also on
|
|
428
|
+
the `public_api` (publishable-key) allowlist — alongside `ingest`,
|
|
429
|
+
`ingest-file`, and `mdm/verify` — so a browser-side app holding
|
|
430
|
+
only a publishable key can call them.
|
|
431
|
+
|
|
432
|
+
**This is the engagement seam.** `updateMessageTracking` writes
|
|
433
|
+
`opened_at` / `form_submitted_at` onto the message row — the
|
|
434
|
+
open/click/conversion half of campaign analytics — and it NEVER
|
|
435
|
+
changes `status` (delivery truth belongs to the action status
|
|
436
|
+
updater; see `messages.md` §1 for who writes which field).
|
|
347
437
|
|
|
348
438
|
### Workflow-side wiring
|
|
349
439
|
|
|
@@ -362,7 +452,7 @@ connected_app_metadata_template string — Liquid template rendering a JSON
|
|
|
362
452
|
|
|
363
453
|
See `workflows.md` §7 for the action-level field detail.
|
|
364
454
|
|
|
365
|
-
##
|
|
455
|
+
## 8. Gotchas
|
|
366
456
|
|
|
367
457
|
1. **Exactly one URL must be `is_primary: true`.** Zero or
|
|
368
458
|
multiple primaries returns 422. The primary is what the
|
|
@@ -406,3 +496,25 @@ See `workflows.md` §7 for the action-level field detail.
|
|
|
406
496
|
8. **Route manifests must be JSON arrays, not objects.** A
|
|
407
497
|
common mistake is wrapping the routes in `{ "routes": [...] }`
|
|
408
498
|
— the manifest's top-level must be the array itself.
|
|
499
|
+
|
|
500
|
+
9. **No MDM subject ⇒ no link (but the message still sends).** Minting
|
|
501
|
+
a page token needs the resolved MDM subject; if the workflow row
|
|
502
|
+
didn't resolve one, the mint is skipped with a logged warning and
|
|
503
|
+
`{{ connected_app_form_url }}` renders empty — the recipient gets a
|
|
504
|
+
linkless message, not an error. If an action mints links, the
|
|
505
|
+
workflow must resolve a subject (`skip_mdm_resolution: false` + an
|
|
506
|
+
entity FK on the row; see `workflows.md` §3). See §6.
|
|
507
|
+
|
|
508
|
+
10. **Tracked links expire (7-day default TTL).** A page token is valid
|
|
509
|
+
for 604,800 s from mint; `resolvePage` returns 410 for an expired
|
|
510
|
+
token. Account for it on long-lead campaigns.
|
|
511
|
+
|
|
512
|
+
11. **`urls` are environments, not tracking domains.** The `is_primary`
|
|
513
|
+
URL becomes the domain of every minted link; the others are
|
|
514
|
+
reference-only. Multiple tracking domains = multiple connected apps
|
|
515
|
+
(see §2).
|
|
516
|
+
|
|
517
|
+
12. **No DNS verification exists.** `status: 'synced'` means the
|
|
518
|
+
manifest was fetched and parsed, not that the domain is verified as
|
|
519
|
+
yours. Route-sync is the only reachability check the platform runs
|
|
520
|
+
(see §5).
|
|
@@ -88,19 +88,19 @@ The `contact-us-triage-with-llm` cookbook ingests into a generic
|
|
|
88
88
|
table via the auto-provisioned identity contract, so it loads no
|
|
89
89
|
vendored template.
|
|
90
90
|
|
|
91
|
-
### `
|
|
91
|
+
### `subscription/`
|
|
92
92
|
|
|
93
93
|
| File | Consumed by cookbook(s) | Source-of-truth |
|
|
94
94
|
|---|---|---|
|
|
95
|
-
| `
|
|
96
|
-
| `
|
|
97
|
-
| `stripe_customers_batch1.csv` (data) | `bulk-ingest` | `integration-tests/tests/
|
|
95
|
+
| `_customers_subscription_customer.liquid` | `welcome-sms-for-customers`, `dunning-sms-for-delinquent`, `bulk-ingest`, `rest-fetch` | `priv/liquid_templates/data_activation/interoperability/subscription/stripe/_customers_subscription_customer.liquid` |
|
|
96
|
+
| `_customers_subscription_mdm.liquid` | `welcome-sms-for-customers`, `dunning-sms-for-delinquent`, `bulk-ingest`, `rest-fetch` | `priv/liquid_templates/data_activation/interoperability/subscription/stripe/_customers_subscription_mdm.liquid` |
|
|
97
|
+
| `stripe_customers_batch1.csv` (data) | `bulk-ingest` | `integration-tests/tests/subscription/fixtures/csv/stripe_customers_batch1.csv` |
|
|
98
98
|
|
|
99
|
-
### `
|
|
99
|
+
### `payments/`
|
|
100
100
|
|
|
101
101
|
| File | Consumed by cookbook(s) | Source-of-truth |
|
|
102
102
|
|---|---|---|
|
|
103
|
-
| `
|
|
104
|
-
| `
|
|
105
|
-
| `
|
|
106
|
-
| `
|
|
103
|
+
| `_payment_accounts_payments_payment_account.liquid` | `kyc-notification-on-account-activation` | `priv/liquid_templates/data_activation/interoperability/payments/atomic_fi/_payment_accounts_payments_payment_account.liquid` |
|
|
104
|
+
| `_payment_accounts_payments_mdm.liquid` | `kyc-notification-on-account-activation` | `priv/liquid_templates/data_activation/interoperability/payments/atomic_fi/_payment_accounts_payments_mdm.liquid` |
|
|
105
|
+
| `_compliance_screenings_payments_compliance_screening.liquid` | `sanctions-screening-with-agent-review` | `priv/liquid_templates/data_activation/interoperability/payments/atomic_fi/_compliance_screenings_payments_compliance_screening.liquid` |
|
|
106
|
+
| `_compliance_screenings_payments_mdm.liquid` | `sanctions-screening-with-agent-review` | `priv/liquid_templates/data_activation/interoperability/payments/atomic_fi/_compliance_screenings_payments_mdm.liquid` |
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
{% comment %}Atomic FI ComplianceScreenings →
|
|
1
|
+
{% comment %}Atomic FI ComplianceScreenings → Payments ComplianceScreening attrs
|
|
2
2
|
|
|
3
3
|
Source: GET /api/compliance-screenings on Atomic FI (paginated; the
|
|
4
4
|
fetch pipeline extracts the `data` array via the response_extractor_key
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
{% comment %}Atomic FI PaymentAccount →
|
|
1
|
+
{% comment %}Atomic FI PaymentAccount → Payments PaymentAccount attrs
|
|
2
2
|
|
|
3
3
|
Source: GET /api/payment-accounts on Atomic FI (paginated; the fetch
|
|
4
4
|
pipeline extracts the `data` array via the response_extractor_key and
|
|
@@ -35,13 +35,15 @@ having to copy-paste this prelude.
|
|
|
35
35
|
## 001 — root admin signs in and provisions a fresh industry-admin user
|
|
36
36
|
|
|
37
37
|
Authenticate as the platform's root admin (`admin@dev.local` /
|
|
38
|
-
`devpassword` in local dev)
|
|
38
|
+
`devpassword` in local dev) via the tenantless bootstrap login —
|
|
39
|
+
keyless by structural necessity (no tenant exists yet to scope a
|
|
40
|
+
key to), and a dev/test-only surface. Then sign up a per-run industry-admin
|
|
39
41
|
user (`sarah`) under a unique email derived from `runSuffix`, and
|
|
40
42
|
confirm them so they can sign in. Both the signup and the confirm
|
|
41
43
|
endpoints require root scope.
|
|
42
44
|
|
|
43
45
|
```typescript
|
|
44
|
-
ctx.rootSession = await
|
|
46
|
+
ctx.rootSession = await createBootstrapSession({
|
|
45
47
|
baseUrl: process.env.ALVERA_BASE_URL!,
|
|
46
48
|
email: process.env.ALVERA_ROOT_EMAIL!,
|
|
47
49
|
password: process.env.ALVERA_ROOT_PASSWORD!,
|
|
@@ -49,12 +51,13 @@ ctx.rootSession = await createSession({
|
|
|
49
51
|
ctx.rootApi = createIsolatedPlatformApi({
|
|
50
52
|
baseUrl: process.env.ALVERA_BASE_URL!,
|
|
51
53
|
sessionToken: ctx.rootSession.sessionToken,
|
|
54
|
+
apiKey: '',
|
|
52
55
|
})
|
|
53
56
|
|
|
54
57
|
ctx.sarahEmail = `cookbook-sarah-${runSuffix}@dev.local`
|
|
55
58
|
ctx.sarahPassword = 'CookbookPass1!'
|
|
56
59
|
|
|
57
|
-
const signUpResp = await ctx.rootApi.
|
|
60
|
+
const signUpResp = await ctx.rootApi.admin.signUp({
|
|
58
61
|
email: ctx.sarahEmail,
|
|
59
62
|
password: ctx.sarahPassword,
|
|
60
63
|
first_name: 'Cookbook',
|
|
@@ -72,7 +75,7 @@ foundation tenant. The tenant's server-derived `slug` is captured
|
|
|
72
75
|
into the closure-scoped `tenantSlug` slot for downstream steps.
|
|
73
76
|
|
|
74
77
|
```typescript
|
|
75
|
-
ctx.sarahTenantlessSession = await
|
|
78
|
+
ctx.sarahTenantlessSession = await createBootstrapSession({
|
|
76
79
|
baseUrl: process.env.ALVERA_BASE_URL!,
|
|
77
80
|
email: ctx.sarahEmail,
|
|
78
81
|
password: ctx.sarahPassword,
|
|
@@ -80,6 +83,7 @@ ctx.sarahTenantlessSession = await createSession({
|
|
|
80
83
|
ctx.sarahTenantlessApi = createIsolatedPlatformApi({
|
|
81
84
|
baseUrl: process.env.ALVERA_BASE_URL!,
|
|
82
85
|
sessionToken: ctx.sarahTenantlessSession.sessionToken,
|
|
86
|
+
apiKey: '',
|
|
83
87
|
})
|
|
84
88
|
|
|
85
89
|
const tenantResp = await ctx.sarahTenantlessApi.tenants.create({
|
|
@@ -96,15 +100,28 @@ is assigned to the closure-scoped `api` slot so the scenario
|
|
|
96
100
|
cookbook's steps inherit it via inlining.
|
|
97
101
|
|
|
98
102
|
```typescript
|
|
103
|
+
// A tenant-scoped login requires X-API-Key — mint a public_api key for
|
|
104
|
+
// the fresh tenant via the platform-admin side door (root Bearer). In the
|
|
105
|
+
// web console this is Settings -> API Keys; any of the tenant's keys
|
|
106
|
+
// satisfies the login gate, and the minted Bearer inherits this key's
|
|
107
|
+
// origin policy.
|
|
108
|
+
const { data: mintedKey } = await ctx.rootApi.admin.createTenantApiKey(tenantSlug, {
|
|
109
|
+
name: `Cookbook Bootstrap Key ${runSuffix}`,
|
|
110
|
+
data_access_mode: 'unregulated',
|
|
111
|
+
})
|
|
112
|
+
ctx.tenantApiKey = mintedKey.api_key
|
|
113
|
+
|
|
99
114
|
const sarahTenantSession = await createSession({
|
|
100
115
|
baseUrl: process.env.ALVERA_BASE_URL!,
|
|
101
116
|
email: ctx.sarahEmail,
|
|
102
117
|
password: ctx.sarahPassword,
|
|
103
118
|
tenantSlug,
|
|
119
|
+
apiKey: ctx.tenantApiKey,
|
|
104
120
|
})
|
|
105
121
|
api = createIsolatedPlatformApi({
|
|
106
122
|
baseUrl: process.env.ALVERA_BASE_URL!,
|
|
107
123
|
sessionToken: sarahTenantSession.sessionToken,
|
|
124
|
+
apiKey: ctx.tenantApiKey,
|
|
108
125
|
})
|
|
109
126
|
```
|
|
110
127
|
|
|
@@ -262,6 +279,9 @@ After this setup runs, the closure-scoped slots are populated as:
|
|
|
262
279
|
- `ctx.sarahEmail` / `ctx.sarahPassword` — the per-run admin
|
|
263
280
|
credentials (kept on `ctx` because no scenario cookbook needs
|
|
264
281
|
to re-authenticate by default)
|
|
282
|
+
- `ctx.tenantApiKey` — the tenant's publishable API key (minted in
|
|
283
|
+
setup via `admin.createTenantApiKey`); thread it into any
|
|
284
|
+
additional tenant-scoped `createSession` a scenario performs
|
|
265
285
|
|
|
266
286
|
Scenario cookbooks under `industry: foundation` start at their own
|
|
267
287
|
`§001` with these slots already populated.
|
|
@@ -35,13 +35,15 @@ having to copy-paste this prelude.
|
|
|
35
35
|
## 001 — root admin signs in and provisions a fresh industry-admin user
|
|
36
36
|
|
|
37
37
|
Authenticate as the platform's root admin (`admin@dev.local` /
|
|
38
|
-
`devpassword` in local dev)
|
|
38
|
+
`devpassword` in local dev) via the tenantless bootstrap login —
|
|
39
|
+
keyless by structural necessity (no tenant exists yet to scope a
|
|
40
|
+
key to), and a dev/test-only surface. Then sign up a per-run industry-admin
|
|
39
41
|
user (`sarah`) under a unique email derived from `runSuffix`, and
|
|
40
42
|
confirm them so they can sign in. Both the signup and the confirm
|
|
41
43
|
endpoints require root scope.
|
|
42
44
|
|
|
43
45
|
```typescript
|
|
44
|
-
ctx.rootSession = await
|
|
46
|
+
ctx.rootSession = await createBootstrapSession({
|
|
45
47
|
baseUrl: process.env.ALVERA_BASE_URL!,
|
|
46
48
|
email: process.env.ALVERA_ROOT_EMAIL!,
|
|
47
49
|
password: process.env.ALVERA_ROOT_PASSWORD!,
|
|
@@ -49,12 +51,13 @@ ctx.rootSession = await createSession({
|
|
|
49
51
|
ctx.rootApi = createIsolatedPlatformApi({
|
|
50
52
|
baseUrl: process.env.ALVERA_BASE_URL!,
|
|
51
53
|
sessionToken: ctx.rootSession.sessionToken,
|
|
54
|
+
apiKey: '',
|
|
52
55
|
})
|
|
53
56
|
|
|
54
57
|
ctx.sarahEmail = `cookbook-sarah-${runSuffix}@dev.local`
|
|
55
58
|
ctx.sarahPassword = 'CookbookPass1!'
|
|
56
59
|
|
|
57
|
-
const signUpResp = await ctx.rootApi.
|
|
60
|
+
const signUpResp = await ctx.rootApi.admin.signUp({
|
|
58
61
|
email: ctx.sarahEmail,
|
|
59
62
|
password: ctx.sarahPassword,
|
|
60
63
|
first_name: 'Cookbook',
|
|
@@ -72,7 +75,7 @@ healthcare tenant. The tenant's server-derived `slug` is captured
|
|
|
72
75
|
into the closure-scoped `tenantSlug` slot for downstream steps.
|
|
73
76
|
|
|
74
77
|
```typescript
|
|
75
|
-
ctx.sarahTenantlessSession = await
|
|
78
|
+
ctx.sarahTenantlessSession = await createBootstrapSession({
|
|
76
79
|
baseUrl: process.env.ALVERA_BASE_URL!,
|
|
77
80
|
email: ctx.sarahEmail,
|
|
78
81
|
password: ctx.sarahPassword,
|
|
@@ -80,6 +83,7 @@ ctx.sarahTenantlessSession = await createSession({
|
|
|
80
83
|
ctx.sarahTenantlessApi = createIsolatedPlatformApi({
|
|
81
84
|
baseUrl: process.env.ALVERA_BASE_URL!,
|
|
82
85
|
sessionToken: ctx.sarahTenantlessSession.sessionToken,
|
|
86
|
+
apiKey: '',
|
|
83
87
|
})
|
|
84
88
|
|
|
85
89
|
const tenantResp = await ctx.sarahTenantlessApi.tenants.create({
|
|
@@ -96,15 +100,28 @@ is assigned to the closure-scoped `api` slot so the scenario
|
|
|
96
100
|
cookbook's steps inherit it via inlining.
|
|
97
101
|
|
|
98
102
|
```typescript
|
|
103
|
+
// A tenant-scoped login requires X-API-Key — mint a public_api key for
|
|
104
|
+
// the fresh tenant via the platform-admin side door (root Bearer). In the
|
|
105
|
+
// web console this is Settings -> API Keys; any of the tenant's keys
|
|
106
|
+
// satisfies the login gate, and the minted Bearer inherits this key's
|
|
107
|
+
// origin policy.
|
|
108
|
+
const { data: mintedKey } = await ctx.rootApi.admin.createTenantApiKey(tenantSlug, {
|
|
109
|
+
name: `Cookbook Bootstrap Key ${runSuffix}`,
|
|
110
|
+
data_access_mode: 'unregulated',
|
|
111
|
+
})
|
|
112
|
+
ctx.tenantApiKey = mintedKey.api_key
|
|
113
|
+
|
|
99
114
|
const sarahTenantSession = await createSession({
|
|
100
115
|
baseUrl: process.env.ALVERA_BASE_URL!,
|
|
101
116
|
email: ctx.sarahEmail,
|
|
102
117
|
password: ctx.sarahPassword,
|
|
103
118
|
tenantSlug,
|
|
119
|
+
apiKey: ctx.tenantApiKey,
|
|
104
120
|
})
|
|
105
121
|
api = createIsolatedPlatformApi({
|
|
106
122
|
baseUrl: process.env.ALVERA_BASE_URL!,
|
|
107
123
|
sessionToken: sarahTenantSession.sessionToken,
|
|
124
|
+
apiKey: ctx.tenantApiKey,
|
|
108
125
|
})
|
|
109
126
|
```
|
|
110
127
|
|
|
@@ -263,6 +280,9 @@ After this setup runs, the closure-scoped slots are populated as:
|
|
|
263
280
|
- `ctx.sarahEmail` / `ctx.sarahPassword` — the per-run admin
|
|
264
281
|
credentials (kept on `ctx` because no scenario cookbook needs
|
|
265
282
|
to re-authenticate by default)
|
|
283
|
+
- `ctx.tenantApiKey` — the tenant's publishable API key (minted in
|
|
284
|
+
setup via `admin.createTenantApiKey`); thread it into any
|
|
285
|
+
additional tenant-scoped `createSession` a scenario performs
|
|
266
286
|
|
|
267
287
|
Scenario cookbooks under `industry: healthcare` start at their own
|
|
268
288
|
`§001` with these slots already populated.
|