@alvera-ai/platform-sdk 0.16.2 → 0.17.0-next.g00fdd4c
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 +88 -143
- package/.agent/account_management.md +2 -2
- package/.agent/action_logs.md +4 -4
- package/.agent/ai_agents.md +28 -21
- package/.agent/ai_sandbox.md +49 -39
- package/.agent/connected_apps.md +3 -3
- package/.agent/cookbook/_fixtures/README.md +1 -1
- package/.agent/cookbook/_fixtures/{foundation → organic-marketing}/_lead_submissions_foundation_generic_table.liquid +1 -1
- package/.agent/cookbook/_fixtures/organic-marketing/_lead_submissions_foundation_legal_entity.liquid +80 -0
- package/.agent/cookbook/_fixtures/{foundation → organic-marketing}/_lead_submissions_foundation_mdm.liquid +2 -1
- package/.agent/cookbook/_fixtures/payments-compliance/_compliance_screenings_generic_table.liquid +57 -0
- package/.agent/cookbook/_fixtures/payments-compliance/_compliance_screenings_legal_entity.liquid +30 -0
- package/.agent/cookbook/_fixtures/payments-compliance/_compliance_screenings_mdm.liquid +44 -0
- package/.agent/cookbook/_fixtures/payments-compliance/_payment_accounts_generic_table.liquid +57 -0
- package/.agent/cookbook/_fixtures/payments-compliance/_payment_accounts_legal_entity.liquid +36 -0
- package/.agent/cookbook/_fixtures/payments-compliance/_payment_accounts_mdm.liquid +41 -0
- package/.agent/cookbook/_fixtures/primary-care-feedback/_cahps_appointments_generic_table.liquid +70 -0
- package/.agent/cookbook/_fixtures/primary-care-feedback/_cahps_appointments_legal_entity.liquid +52 -0
- package/.agent/cookbook/_fixtures/primary-care-feedback/_cahps_appointments_mdm.liquid +42 -0
- package/.agent/cookbook/_fixtures/subscription-saas/_customers_subscription_generic_table.liquid +38 -0
- package/.agent/cookbook/_fixtures/subscription-saas/_customers_subscription_legal_entity.liquid +48 -0
- package/.agent/cookbook/_fixtures/subscription-saas/_customers_subscription_mdm.liquid +49 -0
- package/.agent/cookbook/organic-marketing.md +2801 -0
- package/.agent/cookbook/payments-compliance.md +2180 -0
- package/.agent/cookbook/primary-care.md +2175 -0
- package/.agent/cookbook/subscription-saas.md +2403 -0
- package/.agent/data_activation_clients.md +65 -52
- package/.agent/datalakes.md +338 -171
- package/.agent/errors.md +3 -3
- package/.agent/generic_tables.md +151 -62
- package/.agent/interoperability_contracts.md +57 -22
- package/.agent/mdm.md +136 -153
- package/.agent/messages.md +81 -40
- package/.agent/mock-services.md +192 -0
- package/.agent/mutations.md +2 -2
- package/.agent/templates.md +14 -13
- package/.agent/tools.md +108 -26
- package/.agent/type_naming.md +13 -13
- package/.agent/workflows.md +99 -53
- package/dist/bin/platform-sdk.mjs +33 -47
- package/dist/bin/platform-sdk.mjs.map +1 -1
- package/dist/index.d.mts +521 -385
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +493 -59
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -3
- package/.agent/cookbook/_fixtures/foundation/_lead_submissions_foundation_legal_entity.liquid +0 -88
- package/.agent/cookbook/_fixtures/healthcare/_cahps_appointments_healthcare_appointment.liquid +0 -47
- package/.agent/cookbook/_fixtures/healthcare/_cahps_appointments_healthcare_mdm.liquid +0 -24
- package/.agent/cookbook/_fixtures/healthcare/_cahps_appointments_healthcare_patient.liquid +0 -38
- package/.agent/cookbook/_fixtures/payments/_compliance_screenings_payments_compliance_screening.liquid +0 -59
- package/.agent/cookbook/_fixtures/payments/_compliance_screenings_payments_mdm.liquid +0 -36
- package/.agent/cookbook/_fixtures/payments/_payment_accounts_payments_mdm.liquid +0 -30
- package/.agent/cookbook/_fixtures/payments/_payment_accounts_payments_payment_account.liquid +0 -55
- package/.agent/cookbook/_fixtures/subscription/_customers_subscription_mdm.liquid +0 -20
- package/.agent/cookbook/_setup/foundation.md +0 -359
- package/.agent/cookbook/_setup/healthcare.md +0 -361
- package/.agent/cookbook/_setup/payments.md +0 -365
- package/.agent/cookbook/_setup/subscription.md +0 -364
- package/.agent/cookbook/action-status-updaters.md +0 -278
- package/.agent/cookbook/ai-agent-invoke.md +0 -279
- package/.agent/cookbook/appointment-review-sms-workflow.md +0 -801
- package/.agent/cookbook/birthday-greeting-sms-trigger.md +0 -696
- package/.agent/cookbook/bulk-ingest.md +0 -302
- package/.agent/cookbook/contact-us-triage-with-llm.md +0 -663
- package/.agent/cookbook/dunning-sms-for-delinquent.md +0 -659
- package/.agent/cookbook/generic-tables.md +0 -244
- package/.agent/cookbook/invite-team.md +0 -200
- package/.agent/cookbook/kyc-notification-on-account-activation.md +0 -661
- package/.agent/cookbook/marketing-campaign-send.md +0 -1044
- package/.agent/cookbook/paginated-restapi-poller.md +0 -383
- package/.agent/cookbook/rest-fetch.md +0 -273
- package/.agent/cookbook/sanctions-screening-with-agent-review.md +0 -773
- package/.agent/cookbook/score-leads-with-llm-categorization.md +0 -665
- package/.agent/cookbook/system-templates.md +0 -165
- package/.agent/cookbook/talk-to-data.md +0 -178
- package/.agent/cookbook/triage-prospects-by-priority.md +0 -571
- package/.agent/cookbook/welcome-sms-for-customers.md +0 -647
- /package/.agent/cookbook/_fixtures/{healthcare → primary-care-feedback}/memorandum-of-association-01.png +0 -0
- /package/.agent/cookbook/_fixtures/{healthcare → primary-care-feedback}/sample_two_page.pdf +0 -0
- /package/.agent/cookbook/_fixtures/{subscription → subscription-saas}/_customers_subscription_customer.liquid +0 -0
- /package/.agent/cookbook/_fixtures/{subscription → subscription-saas}/stripe_customers_batch1.csv +0 -0
package/.agent/AGENTS.md
CHANGED
|
@@ -231,17 +231,16 @@ What changes between them is only *where you put the calls* (a component
|
|
|
231
231
|
handler vs. a test body), never the calls themselves. The cookbooks under
|
|
232
232
|
`cookbook/` read as test-style sequences because that is how they are
|
|
233
233
|
validated, but every step is a plain `api.*` call you can lift into an app
|
|
234
|
-
unchanged.
|
|
235
|
-
|
|
236
|
-
business outcome.
|
|
234
|
+
unchanged. Capabilities are not documented separately — each lives as
|
|
235
|
+
numbered steps inside the cookbook whose use case motivates it.
|
|
237
236
|
|
|
238
237
|
## Resources
|
|
239
238
|
|
|
240
239
|
```
|
|
241
240
|
INFRASTRUCTURE (stood up before data flows)
|
|
242
241
|
──────────────
|
|
243
|
-
datalakes.md Storage layer;
|
|
244
|
-
|
|
242
|
+
datalakes.md Storage layer; the raw lake and
|
|
243
|
+
its tokenized + redacted copies.
|
|
245
244
|
data_sources.md External ingestion endpoints.
|
|
246
245
|
tools.md Authenticated connections for
|
|
247
246
|
action execution.
|
|
@@ -323,12 +322,13 @@ at runtime.
|
|
|
323
322
|
|
|
324
323
|
Each cookbook structure:
|
|
325
324
|
|
|
326
|
-
- **Front matter** — `title`, `summary`, `
|
|
325
|
+
- **Front matter** — `title`, `summary`, `use_case`, `slug`,
|
|
327
326
|
`vitest_source` (list of integration-test files the snippets
|
|
328
|
-
are lifted from, anchor file first), `status`.
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
327
|
+
are lifted from, anchor file first), `status`. `use_case` is one
|
|
328
|
+
of the four surfaces — `organic-marketing`,
|
|
329
|
+
`subscription-saas`, `payments-compliance`,
|
|
330
|
+
`primary-care-feedback` — and there is exactly one cookbook per
|
|
331
|
+
use case, at `cookbook/<use-case>.md`.
|
|
332
332
|
- **Problem** — the business-outcome statement in domain terms,
|
|
333
333
|
sourced from the anchor vitest's behaviour (not from
|
|
334
334
|
customer-narrative documentation).
|
|
@@ -349,44 +349,33 @@ Each cookbook structure:
|
|
|
349
349
|
- **See also** — links to relevant per-resource reference MDs
|
|
350
350
|
and to the anchor + ancillary vitest files.
|
|
351
351
|
|
|
352
|
-
###
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
prefixed with its source slug so failure output
|
|
380
|
-
unambiguously points at the file to open (e.g.
|
|
381
|
-
`_setup/foundation §001 — auth` versus
|
|
382
|
-
`birthday-greeting-sms-trigger §001 — create workflow`).
|
|
383
|
-
Cookbook authors keep clean local §001-§00N numbering
|
|
384
|
-
inside their own markdown.
|
|
385
|
-
- An agent reading a scenario cookbook discovers the matching
|
|
386
|
-
setup file at the predictable conventional path
|
|
387
|
-
`_setup/<industry>.md` — one extra file open at a
|
|
388
|
-
fixed-by-convention location, not via cookbook-specific
|
|
389
|
-
metadata the agent has to learn.
|
|
352
|
+
### Every cookbook is self-reliant, and repeats what it needs
|
|
353
|
+
|
|
354
|
+
A cookbook stands up **its own** tenant, datalake and resources in
|
|
355
|
+
its own numbered steps. There is no shared setup file, no setup
|
|
356
|
+
group and no bootstrap phase: everything the walk depends on is
|
|
357
|
+
in the walk.
|
|
358
|
+
|
|
359
|
+
That means the auth + tenant + datalake preamble is **duplicated
|
|
360
|
+
across all four cookbooks, deliberately.** This corpus is read by
|
|
361
|
+
someone — or something — that opens one file and works through it,
|
|
362
|
+
and for that reader a shared setup is not a saving. It is a second
|
|
363
|
+
file to find, a second convention to learn, and a hidden dependency
|
|
364
|
+
on steps they never see. Four documents repeating a preamble is the
|
|
365
|
+
cheaper trade, and it is the trade this corpus makes. Do not
|
|
366
|
+
re-factor it out.
|
|
367
|
+
|
|
368
|
+
Two consequences worth stating plainly:
|
|
369
|
+
|
|
370
|
+
- **Write for idempotence, not just for a clean run.** Use stable
|
|
371
|
+
names and create-or-reuse: look the resource up, create it only
|
|
372
|
+
if it is absent, and assert the end state either way. A step that
|
|
373
|
+
mints a fresh random name works once and litters forever —
|
|
374
|
+
nothing in the product reclaims an abandoned datalake. Ask of
|
|
375
|
+
every creating step: what does this do on run two?
|
|
376
|
+
- **Label numbering is local and clean.** Each cookbook numbers its
|
|
377
|
+
own steps `§001…§00N` from the top, and a failure names the file
|
|
378
|
+
and the step directly.
|
|
390
379
|
|
|
391
380
|
### Vendored fixtures (Liquid templates, CSV bodies)
|
|
392
381
|
|
|
@@ -417,7 +406,7 @@ repo's own integration tests use.
|
|
|
417
406
|
const LE_TEMPLATE = fs.readFileSync(
|
|
418
407
|
path.join(
|
|
419
408
|
process.env.COOKBOOK_FIXTURES_DIR!,
|
|
420
|
-
'
|
|
409
|
+
'organic-marketing/_lead_submissions_foundation_legal_entity.liquid',
|
|
421
410
|
),
|
|
422
411
|
'utf8',
|
|
423
412
|
)
|
|
@@ -439,109 +428,58 @@ repo's own integration tests use.
|
|
|
439
428
|
template plus the cookbooks that depend on it. No need to
|
|
440
429
|
read cookbook code first to know what fixtures exist.
|
|
441
430
|
|
|
442
|
-
###
|
|
431
|
+
### The four cookbooks
|
|
443
432
|
|
|
444
|
-
|
|
445
|
-
|
|
433
|
+
`cookbook/` holds **four files, one per use case**, and nothing else that
|
|
434
|
+
runs. Each is self-reliant: it stands up its own tenant, datalake and
|
|
435
|
+
resources in its own numbered steps and shares nothing with its siblings.
|
|
436
|
+
A reader reads one file end to end and needs no second one. The
|
|
437
|
+
duplicated bootstrap across them is deliberate — four repeated preambles
|
|
438
|
+
are cheaper than one shared setup every reader must go and understand
|
|
439
|
+
first.
|
|
446
440
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
shown as the minimal call sequence that proves it (e.g. `bulk-ingest`,
|
|
453
|
-
`ai-agent-invoke`, `generic-tables`, `invite-team`). Read these to learn one
|
|
454
|
-
capability in isolation; cookbooks weave them into outcomes.
|
|
441
|
+
There is no separate per-capability file. Bulk upload, REST fetch, team
|
|
442
|
+
invitations, action status updaters, paginated pollers, text-to-SQL and
|
|
443
|
+
agent file-invocation each live as numbered steps inside the cookbook
|
|
444
|
+
whose use case motivates them, because a capability shown without a
|
|
445
|
+
reason to use it is the thing readers skip.
|
|
455
446
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
The ten business-cookbook scenarios. Each is anchored to a green
|
|
459
|
-
end-to-end vitest scenario in the platform's integration-tests suite and is
|
|
460
|
-
verified at dev time by `make validate-cookbook` at the platform-sdk repo root.
|
|
461
|
-
This index is mirrored into the managed block `alvera llm-export` writes to
|
|
462
|
-
consumer `AGENTS.md` files (`buildManagedBlock` in both the SDK and CLI
|
|
463
|
-
packages — kept in sync by hand; the llm-export test suites pin the shipped
|
|
464
|
-
copy, so a dropped entry breaks a test).
|
|
447
|
+
All four are verified at dev time by `make validate-cookbook` at the
|
|
448
|
+
repo root; every numbered step is one test.
|
|
465
449
|
|
|
466
450
|
<!-- BEGIN:cookbook-index -->
|
|
467
451
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
(high / medium / low) via an LLM agent, route each band to a
|
|
490
|
-
tailored SMS action.
|
|
491
|
-
|
|
492
|
-
**Payments**
|
|
493
|
-
|
|
494
|
-
- [kyc-notification-on-account-activation](./cookbook/kyc-notification-on-account-activation.md)
|
|
495
|
-
— Send a KYC-notification SMS when a payment account
|
|
496
|
-
transitions to active status.
|
|
497
|
-
- [sanctions-screening-with-agent-review](./cookbook/sanctions-screening-with-agent-review.md)
|
|
498
|
-
— Disambiguate gray-zone sanctions screenings via an LLM
|
|
499
|
-
agent, route confirmed-clean and confirmed-block outcomes to
|
|
500
|
-
distinct SMS actions.
|
|
501
|
-
|
|
502
|
-
**Foundation**
|
|
503
|
-
|
|
504
|
-
- [birthday-greeting-sms-trigger](./cookbook/birthday-greeting-sms-trigger.md)
|
|
505
|
-
— Send a happy-birthday SMS on each contact's next birthday
|
|
506
|
-
using a pure-Liquid trigger (year-roll math).
|
|
507
|
-
- [score-leads-with-llm-categorization](./cookbook/score-leads-with-llm-categorization.md)
|
|
508
|
-
— Score inbound leads into four bands
|
|
509
|
-
(hot / warm / cold / spam) via an LLM agent, route each band
|
|
510
|
-
to a tailored SMS action.
|
|
511
|
-
- [marketing-campaign-send](./cookbook/marketing-campaign-send.md)
|
|
512
|
-
— Send an A/B marketing campaign across SMS and email: the
|
|
513
|
-
suppression and reachability gates and the A/B split all live
|
|
514
|
-
in the workflow, each send carries a per-recipient short link,
|
|
515
|
-
and the reply re-attaches to the customer who sent it.
|
|
452
|
+
- [organic-marketing](./cookbook/organic-marketing.md) — score inbound
|
|
453
|
+
leads into four bands with an LLM agent, schedule a birthday greeting a
|
|
454
|
+
year out with a pure-Liquid trigger, and run an A/B campaign whose
|
|
455
|
+
suppression gates and split live in the workflow. Ends with delivery
|
|
456
|
+
reconciliation and a natural-language read of the lake. One raw
|
|
457
|
+
datalake.
|
|
458
|
+
- [payments-compliance](./cookbook/payments-compliance.md) —
|
|
459
|
+
disambiguate gray-zone sanctions screenings with an LLM agent that
|
|
460
|
+
never sees who was screened, and fire a KYC notification on account
|
|
461
|
+
activation. Ends by reading one row through raw, tokenized and redacted
|
|
462
|
+
to show what each reader gets. Three datalakes.
|
|
463
|
+
- [subscription-saas](./cookbook/subscription-saas.md) — one customers
|
|
464
|
+
table and three workflows over it: a welcome SMS gated on
|
|
465
|
+
reachability, a dunning reminder gated on reachability AND KYC, and an
|
|
466
|
+
LLM agent banding accounts by priority. Then the same table filled
|
|
467
|
+
three more ways — inline, bulk CSV, REST fetch — and a teammate invited
|
|
468
|
+
onto the tenant. One raw datalake.
|
|
469
|
+
- [primary-care](./cookbook/primary-care.md) — a review request that
|
|
470
|
+
only reaches patients whose visit actually happened, an LLM agent
|
|
471
|
+
triaging inbound messages off the tokenized projection, and a vision
|
|
472
|
+
agent reading a scanned document. Three datalakes.
|
|
516
473
|
|
|
517
474
|
<!-- END:cookbook-index -->
|
|
518
475
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
one upload (presigned link → PUT → `ingestFile` → search by batch).
|
|
527
|
-
- [rest-fetch](./cookbook/rest-fetch.md) — pull records from a third-party REST
|
|
528
|
-
API on demand (Bearer + OAuth2), no file upload.
|
|
529
|
-
- [ai-agent-invoke](./cookbook/ai-agent-invoke.md) — read an uploaded
|
|
530
|
-
document/image and pull structured JSON out of it (`aiAgents.invoke` with
|
|
531
|
-
files). The correct file-vision path — the agent reads the file; the DAC
|
|
532
|
-
ingests it.
|
|
533
|
-
- [generic-tables](./cookbook/generic-tables.md) — stand up a generic table the
|
|
534
|
-
built-in datasets don't model; deploy → ingest → read back via executeSql.
|
|
535
|
-
- [action-status-updaters](./cookbook/action-status-updaters.md) — reconcile
|
|
536
|
-
the delivery status of messages you send, on a schedule.
|
|
537
|
-
- [system-templates](./cookbook/system-templates.md) — discover the platform's
|
|
538
|
-
built-in row-mapping Liquid templates.
|
|
539
|
-
- [invite-team](./cookbook/invite-team.md) — invite a teammate into your tenant
|
|
540
|
-
(root / tenantless / tenant-scoped sessions in one flow).
|
|
541
|
-
- [talk-to-data](./cookbook/talk-to-data.md) — turn a datalake conversational:
|
|
542
|
-
natural language → reviewable SQL (`datalakes.textToSql`, data-free by
|
|
543
|
-
construction) → read-only execution returning a `{ data, meta }` row page or a
|
|
544
|
-
CSV export (`datalakes.executeSql`).
|
|
476
|
+
This index is mirrored into the managed block `alvera llm-export` writes
|
|
477
|
+
to consumer `AGENTS.md` files — `buildManagedBlock`, duplicated in both
|
|
478
|
+
the SDK and CLI packages and kept in sync by hand. **Both suites now
|
|
479
|
+
assert every cookbook named in the block exists on disk, and that every
|
|
480
|
+
cookbook on disk is named.** They previously pinned names against the
|
|
481
|
+
block string alone, which is how the index went on advertising four
|
|
482
|
+
deleted files without a single test failing.
|
|
545
483
|
|
|
546
484
|
## Utility namespaces
|
|
547
485
|
|
|
@@ -576,6 +514,13 @@ of restating:
|
|
|
576
514
|
| Async + readiness | `async.md` |
|
|
577
515
|
| Debugging (HTTP interceptor + safe redaction) | `debugging.md` |
|
|
578
516
|
| Type naming + server-derived fields | `type_naming.md` |
|
|
517
|
+
| Local mock surfaces (WireMock / LocalStack) | `mock-services.md` |
|
|
518
|
+
|
|
519
|
+
`mock-services.md` answers the local-development questions the other
|
|
520
|
+
pages assume away: how the platform reaches mocked third-party APIs on
|
|
521
|
+
a laptop, the `endpoint_url` override on the sender tools, and why End
|
|
522
|
+
User Messaging points at WireMock rather than LocalStack. It is fetched
|
|
523
|
+
from the platform-metadata channel, not authored here.
|
|
579
524
|
|
|
580
525
|
## Just getting started?
|
|
581
526
|
|
|
@@ -86,7 +86,7 @@ platform-admin side door (a root Bearer):
|
|
|
86
86
|
```typescript
|
|
87
87
|
const { data: minted } = await rootApi.admin.createTenantApiKey(tenantSlug, {
|
|
88
88
|
name: 'Bootstrap Key',
|
|
89
|
-
data_access_mode: '
|
|
89
|
+
data_access_mode: 'raw',
|
|
90
90
|
})
|
|
91
91
|
// minted.api_key — the publishable key value (thread into createSession)
|
|
92
92
|
```
|
|
@@ -329,7 +329,7 @@ What you SUBMIT when creating an invitation. The
|
|
|
329
329
|
| Invitation `role` | Result on accept |
|
|
330
330
|
|-------------------|----------------------------------------|
|
|
331
331
|
| `'member'` | Standard tenant user |
|
|
332
|
-
| `'researcher'` |
|
|
332
|
+
| `'researcher'` | Ceiling of `tokenized`; cannot read the raw lake |
|
|
333
333
|
| `'admin'` | Full tenant management |
|
|
334
334
|
|
|
335
335
|
```typescript
|
package/.agent/action_logs.md
CHANGED
|
@@ -39,7 +39,7 @@ batch_id — the run this outcome belongs to; THE
|
|
|
39
39
|
decision_key — which action fired
|
|
40
40
|
context_key — companion string key; like decision_key, it is
|
|
41
41
|
SQL-lane only (neither is Flop-filterable, §5)
|
|
42
|
-
|
|
42
|
+
legal_entity_id, subject_name, session_id
|
|
43
43
|
action_type ('sms'|'email'|'voice'|'data_exchange'), channel,
|
|
44
44
|
classification
|
|
45
45
|
status — see §3
|
|
@@ -92,17 +92,17 @@ message.
|
|
|
92
92
|
// dataset type is SINGULAR 'action_log' (the table is plural)
|
|
93
93
|
const { data: search } = await api.datasets.createUserSearch(
|
|
94
94
|
tenantSlug, datalakeSlug, 'action_log',
|
|
95
|
-
{ search_query: `
|
|
95
|
+
{ search_query: `al.batch_id = '${batchId}'` },
|
|
96
96
|
)
|
|
97
97
|
const { data: page } = await api.datasets.search(
|
|
98
98
|
tenantSlug, datalakeSlug, 'action_log',
|
|
99
|
-
{ userSearchId: search.id, dataAccessMode: '
|
|
99
|
+
{ userSearchId: search.id, dataAccessMode: 'tokenized' },
|
|
100
100
|
)
|
|
101
101
|
```
|
|
102
102
|
|
|
103
103
|
Filterable columns (Flop, the full set): `id`, `inserted_at`,
|
|
104
104
|
`action_type`, `channel`, `status`, `batch_id`, `classification`,
|
|
105
|
-
`workflow_id`, `sent_at`, `external_id`, `
|
|
105
|
+
`workflow_id`, `sent_at`, `external_id`, `legal_entity_id`. Note
|
|
106
106
|
there is **no `global_search`** here (unlike the message dataset) —
|
|
107
107
|
`decision_key`/`context_key` are not in this set either; scope them
|
|
108
108
|
via the `search_query` SQL fragment above.
|
package/.agent/ai_agents.md
CHANGED
|
@@ -31,7 +31,7 @@ const { data: created } = await api.aiAgents.create(
|
|
|
31
31
|
tool_id: chatCompletionToolId, // see §2 — must be a tool with intent: 'llm_enrichment'
|
|
32
32
|
|
|
33
33
|
model: 'qwen3-vl:8b-instruct', // provider-specific model identifier
|
|
34
|
-
data_access: '
|
|
34
|
+
data_access: 'tokenized', // 'raw' | 'tokenized' (compliance gate)
|
|
35
35
|
temperature: 0.0,
|
|
36
36
|
max_tokens: 2048,
|
|
37
37
|
enabled: true,
|
|
@@ -125,25 +125,30 @@ referenced by the prompt body resolves to a property declared in
|
|
|
125
125
|
### `data_access` is the compliance gate
|
|
126
126
|
|
|
127
127
|
```
|
|
128
|
-
data_access: '
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
128
|
+
data_access: 'tokenized' — the LLM call reads the tokenized lake:
|
|
129
|
+
each masked column arrives as a stable
|
|
130
|
+
tok_<hash>. Safe for any LLM, including
|
|
131
|
+
third-party. The right choice for triage,
|
|
132
|
+
categorization and lead scoring, where the
|
|
133
|
+
model reads free text and has no business
|
|
134
|
+
knowing whose it is.
|
|
135
|
+
data_access: 'raw' — the LLM call receives real values. MUST be
|
|
136
|
+
paired with an LLM the operator trusts
|
|
137
|
+
with them (typically on-prem). For work
|
|
138
|
+
that genuinely needs the value — sanctions
|
|
139
|
+
screening, manual review, KYC — and for
|
|
140
|
+
prompts that carry no identity at all, so
|
|
141
|
+
there is nothing to hold back.
|
|
142
|
+
|
|
143
|
+
An agent is never pointed at the redacted lake: its placeholders are
|
|
144
|
+
unjoinable by design, which makes them useless to a model.
|
|
140
145
|
```
|
|
141
146
|
|
|
142
147
|
**The platform does NOT enforce `data_access` as immutable — a PUT
|
|
143
148
|
`update()` can change it today.** The field is cast by the same
|
|
144
149
|
changeset that `update()` uses and carries no immutability guard (and
|
|
145
|
-
isn't `readOnly` on the wire), so an update can flip `
|
|
146
|
-
|
|
150
|
+
isn't `readOnly` on the wire), so an update can flip `raw ↔
|
|
151
|
+
tokenized`, which changes whether the agent sees real or masked
|
|
147
152
|
values. **Treat it as immutable by discipline** — a data source
|
|
148
153
|
built expecting one tier shouldn't be silently re-tiered under it —
|
|
149
154
|
but know the platform won't stop a flip, so guard it in your own
|
|
@@ -209,9 +214,10 @@ const { data: workflow } = await api.workflows.create(
|
|
|
209
214
|
tenantSlug, datalakeSlug,
|
|
210
215
|
{
|
|
211
216
|
name: 'Contact-Us Triage',
|
|
212
|
-
|
|
217
|
+
description: 'Triages inbound contact-us messages.',
|
|
218
|
+
dataset_type: 'generic_table' as const,
|
|
219
|
+
status: 'live' as const,
|
|
213
220
|
tags: [],
|
|
214
|
-
// …other workflow fields…
|
|
215
221
|
workflow_ai_agents: [
|
|
216
222
|
{
|
|
217
223
|
ai_agent_id: createdAgentId,
|
|
@@ -305,7 +311,7 @@ name required string
|
|
|
305
311
|
description optional string
|
|
306
312
|
tool_id required UUID — must be intent: llm_enrichment
|
|
307
313
|
model required string — provider-specific identifier
|
|
308
|
-
data_access required enum — '
|
|
314
|
+
data_access required enum — 'raw' | 'tokenized' (no default)
|
|
309
315
|
temperature required number
|
|
310
316
|
max_tokens required number
|
|
311
317
|
enabled required bool
|
|
@@ -417,8 +423,9 @@ usage object — { model, input_tokens, output_tokens, total_tokens,
|
|
|
417
423
|
|
|
418
424
|
Invoke passes NO `context_mapping_config` — `input` is validated
|
|
419
425
|
directly against `input_schema` (unlike the nested workflow path,
|
|
420
|
-
which remaps host context first). See
|
|
421
|
-
|
|
426
|
+
which remaps host context first). See `cookbook/primary-care.md`
|
|
427
|
+
§025–028 for the full upload → tool → agent → invoke walk, including
|
|
428
|
+
the input-gate refusal.
|
|
422
429
|
|
|
423
430
|
### Drift checksum
|
|
424
431
|
|
|
@@ -544,7 +551,7 @@ with `{ status: 'skipped' }` and no per-agent entries.
|
|
|
544
551
|
6. **`data_access` is immutable by convention only — the platform
|
|
545
552
|
won't stop a PUT from flipping it.** There's no immutability
|
|
546
553
|
guard in the changeset, so an `update()` can change the
|
|
547
|
-
compliance tier (`
|
|
554
|
+
compliance tier (`raw ↔ tokenized`) and re-tier what the
|
|
548
555
|
agent sees. Enforce it in your own change-control (or delete +
|
|
549
556
|
recreate); don't rely on the platform to reject the flip (§2).
|
|
550
557
|
|
package/.agent/ai_sandbox.md
CHANGED
|
@@ -17,16 +17,16 @@ the worst case — and they compose into defense-in-depth.
|
|
|
17
17
|
|
|
18
18
|
## Layer 1 — Compliance gates (who sees what)
|
|
19
19
|
|
|
20
|
-
Two consumer-set fields decide whether a request reaches raw
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
Two consumer-set fields decide whether a request reaches the raw
|
|
21
|
+
lake or only the tokenized projection. Both must authorize raw reach
|
|
22
|
+
for real values to flow:
|
|
23
23
|
|
|
24
24
|
```
|
|
25
25
|
session role AI agent data_access → outcome
|
|
26
26
|
────────────── ─────────────────── ───────
|
|
27
|
-
member or admin
|
|
28
|
-
member or admin
|
|
29
|
-
member or admin (no agent on path)
|
|
27
|
+
member or admin raw raw reachable
|
|
28
|
+
member or admin tokenized tokenized only
|
|
29
|
+
member or admin (no agent on path) raw reachable
|
|
30
30
|
researcher any value tokenized only
|
|
31
31
|
```
|
|
32
32
|
|
|
@@ -34,21 +34,28 @@ authorize regulated reach for raw data to flow:
|
|
|
34
34
|
time (vocabulary A — `'member' | 'researcher' | 'admin'`; the
|
|
35
35
|
resulting session carries the corresponding role-record name
|
|
36
36
|
— see `account_management.md` §3).
|
|
37
|
-
- **AI agent `data_access`** is `'
|
|
38
|
-
|
|
39
|
-
`
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
the
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
37
|
+
- **AI agent `data_access`** is `'raw' | 'tokenized'`, set at agent
|
|
38
|
+
creation. **The platform does not enforce it as immutable** — one
|
|
39
|
+
`changeset/2` serves create and update, and nothing refuses a PUT
|
|
40
|
+
that flips the tier, so treat it as immutable by your own
|
|
41
|
+
change-control rather than by the platform's (see `ai_agents.md`
|
|
42
|
+
§2). An
|
|
43
|
+
agent is **never pointed at the redacted lake**: that copy is for a
|
|
44
|
+
person to glance at, and its values are unjoinable by design.
|
|
45
|
+
|
|
46
|
+
**There is no default — the field is required.** Omitting it is a
|
|
47
|
+
422 at create, not a quiet fall-back to the safer tier. That is a
|
|
48
|
+
deliberate change from an older design that defaulted to the
|
|
49
|
+
tokenized side: a default meant an author could get a compliance
|
|
50
|
+
tier without ever deciding on one, and the reviewer reading the
|
|
51
|
+
spec could not tell an intentional choice from an omission. Being
|
|
52
|
+
made to say it is the point.
|
|
53
|
+
|
|
54
|
+
A `researcher` session can never reach real values regardless of how
|
|
55
|
+
the agent is configured. A `'tokenized'` agent can never reach them
|
|
56
|
+
regardless of session role. Tenant isolation (every row carries a
|
|
57
|
+
tenant id; queries filter on the session's tenant) AND-composes with
|
|
58
|
+
both, so a request only sees the slice that *all three* allow.
|
|
52
59
|
|
|
53
60
|
## Layer 2 — Liquid sandbox (what can execute)
|
|
54
61
|
|
|
@@ -156,10 +163,9 @@ fragments. Either path lands at the same boundary.
|
|
|
156
163
|
|
|
157
164
|
### What SQL fragments CAN do
|
|
158
165
|
|
|
159
|
-
- SELECT against the
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
§6.5 SQL aliases).
|
|
166
|
+
- SELECT against the tables exposed by the dataset search aliases
|
|
167
|
+
(`le` for legal entities, `m` for messages, `al` for action logs
|
|
168
|
+
— see `data_activation_clients.md` §6.5 SQL aliases).
|
|
163
169
|
- Filter results into the platform's `search_results`
|
|
164
170
|
accumulator scoped to the materialised search id. This is the
|
|
165
171
|
one write the query path performs, and the platform builds it
|
|
@@ -199,15 +205,15 @@ it sits inside the same Layer-3 boundary:
|
|
|
199
205
|
only the natural-language `prompt` plus the datalake **schema**
|
|
200
206
|
to the LLM — never rows. The model returns SQL for review; no
|
|
201
207
|
datalake data ever crosses the LLM boundary, so generation is
|
|
202
|
-
safe in
|
|
208
|
+
safe in every mode. The two-call
|
|
203
209
|
split (generate, then execute) exists so a human or agent can
|
|
204
210
|
inspect and edit the SQL before any data is read.
|
|
205
211
|
- **Execution is read-only and mode-routed.** `executeSql` runs the
|
|
206
212
|
statement against the mode-appropriate schema with a read-only
|
|
207
213
|
connection: INSERT/UPDATE/DELETE/DDL are rejected (app-layer
|
|
208
214
|
`@deny` + a read-only DB transaction + an EXPLAIN preflight), and
|
|
209
|
-
`mode` (`
|
|
210
|
-
(RLS) decide which
|
|
215
|
+
`mode` (`raw`, `tokenized` or `redacted`) plus the session's tenant
|
|
216
|
+
(RLS) decide which lake and which rows are reachable — the same
|
|
211
217
|
Layer-1 compliance gates that govern every other read.
|
|
212
218
|
- **Pagination caps blast radius.** `page` / `page_size` map to a
|
|
213
219
|
server-side `LIMIT/OFFSET` window capped at the platform default,
|
|
@@ -220,9 +226,9 @@ it sits inside the same Layer-3 boundary:
|
|
|
220
226
|
│ Layer 1 — compliance gates │
|
|
221
227
|
│ session role AND agent data_access │
|
|
222
228
|
│ AND tenant id (RLS) │
|
|
223
|
-
│ Decides: which
|
|
224
|
-
│
|
|
225
|
-
│
|
|
229
|
+
│ Decides: which lake (raw / tokenized / │
|
|
230
|
+
│ redacted) and which tenant the query hits. │
|
|
231
|
+
│ │
|
|
226
232
|
└────────────────────┬────────────────────────┘
|
|
227
233
|
│
|
|
228
234
|
┌────────────────────▼────────────────────────┐
|
|
@@ -248,9 +254,9 @@ it sits inside the same Layer-3 boundary:
|
|
|
248
254
|
|
|
249
255
|
Each layer is independently sufficient: bypassing any single
|
|
250
256
|
layer still leaves the other two enforcing the contract. The
|
|
251
|
-
compound rule is "all three must permit" —
|
|
252
|
-
authorising session role AND
|
|
253
|
-
agent is on the path) AND tenant id match.
|
|
257
|
+
compound rule is "all three must permit" — reading real values
|
|
258
|
+
requires an authorising session role AND a `raw` agent (when an
|
|
259
|
+
agent is on the path) AND a tenant id match.
|
|
254
260
|
|
|
255
261
|
## Auditability
|
|
256
262
|
|
|
@@ -259,15 +265,19 @@ recorded with cloud-storage pointers to the exact input payload
|
|
|
259
265
|
the agent received and the exact response the provider
|
|
260
266
|
returned. The audit record outlives the host run that produced
|
|
261
267
|
it — a compliance auditor can later replay any agent's input
|
|
262
|
-
and verify
|
|
263
|
-
reached the model. The audit surface is admin-facing today; the
|
|
268
|
+
and verify which lake (raw vs tokenized) actually fed the model. The audit surface is admin-facing today; the
|
|
264
269
|
guarantees it captures back-stop Layer 1.
|
|
265
270
|
|
|
266
271
|
## Consumer takeaways
|
|
267
272
|
|
|
268
|
-
1. **
|
|
269
|
-
|
|
270
|
-
|
|
273
|
+
1. **Reach for `'tokenized'` agent `data_access` first.** The field
|
|
274
|
+
is required, so this is a decision you must make rather than
|
|
275
|
+
inherit; choose `'raw'` only when the prompt genuinely needs real
|
|
276
|
+
values, and pair it with a session role that is authorised. The
|
|
277
|
+
tier should follow what the prompt can actually reach — an agent
|
|
278
|
+
whose prompt carries only an account number and a type has no
|
|
279
|
+
identity to protect, and one reading free-text patient messages
|
|
280
|
+
has plenty.
|
|
271
281
|
|
|
272
282
|
2. **Use only the 13 custom filters** named in Layer 2. Bodies
|
|
273
283
|
that reference other filter names will silently fall through
|
package/.agent/connected_apps.md
CHANGED
|
@@ -398,14 +398,14 @@ resolves the page:
|
|
|
398
398
|
|
|
399
399
|
```typescript
|
|
400
400
|
const { data: page } = await api.connectedApps.resolvePage(
|
|
401
|
-
tenantSlug, slug,
|
|
401
|
+
tenantSlug, datalakeSlug, slug, // tenant + datalake + app slug
|
|
402
402
|
{
|
|
403
403
|
short_path: '<the-short-path-from-the-URL>',
|
|
404
404
|
user_agent: req.headers.get('user-agent') ?? '',
|
|
405
405
|
},
|
|
406
406
|
)
|
|
407
407
|
// page.message — the rendered message row
|
|
408
|
-
// page.message.body — the
|
|
408
|
+
// page.message.body — the rendered body, real values (raw lake)
|
|
409
409
|
// page.route_path — the route the link was minted against
|
|
410
410
|
// (e.g. '/forms/review')
|
|
411
411
|
// page.metadata — parsed `connected_app_metadata_template`
|
|
@@ -418,7 +418,7 @@ the customer opens or submits the form:
|
|
|
418
418
|
|
|
419
419
|
```typescript
|
|
420
420
|
await api.connectedApps.updateMessageTracking(
|
|
421
|
-
tenantSlug, slug,
|
|
421
|
+
tenantSlug, datalakeSlug, slug,
|
|
422
422
|
{
|
|
423
423
|
short_path: '<same-short-path>',
|
|
424
424
|
opened_at: new Date().toISOString(),
|
|
@@ -27,7 +27,7 @@ import path from 'node:path'
|
|
|
27
27
|
const LE_TEMPLATE = fs.readFileSync(
|
|
28
28
|
path.join(
|
|
29
29
|
process.env.COOKBOOK_FIXTURES_DIR!,
|
|
30
|
-
'
|
|
30
|
+
'organic-marketing/_lead_submissions_foundation_legal_entity.liquid',
|
|
31
31
|
),
|
|
32
32
|
'utf8',
|
|
33
33
|
)
|