@alvera-ai/platform-sdk 0.17.0 → 0.18.0
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 +82 -144
- 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 +36 -34
- package/.agent/mock-services.md +1 -1
- package/.agent/mutations.md +2 -2
- package/.agent/templates.md +14 -13
- package/.agent/tools.md +63 -21
- package/.agent/type_naming.md +13 -13
- package/.agent/workflows.md +99 -53
- package/README.md +2 -2
- package/dist/bin/platform-sdk.mjs +33 -47
- package/dist/bin/platform-sdk.mjs.map +1 -1
- package/dist/index.d.mts +565 -379
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +494 -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
|
@@ -1,165 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: "Capability: discover the platform's built-in row-mapping templates"
|
|
3
|
-
summary: A capability walk for system-template discovery. List the Liquid templates the platform ships for your datalake's data domain (`api.templates.systemTemplates`), read the human-readable catalog (`api.templates.metadata`), and pull one template's detail (`api.templates.metadataDetails`) — so you can start an interoperability contract from a platform template instead of hand-writing Liquid. Read-only; creates nothing.
|
|
4
|
-
industry: subscription
|
|
5
|
-
slug: system-templates
|
|
6
|
-
vitest_source:
|
|
7
|
-
- integration-tests/tests/subscription/system-templates.test.ts
|
|
8
|
-
- integration-tests/tests/subscription/bootstrap.test.ts
|
|
9
|
-
status: green
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
# Capability
|
|
13
|
-
|
|
14
|
-
**What you get:** the list of row-mapping Liquid templates the platform already
|
|
15
|
-
ships for your datalake's data domain, so an interoperability contract can start
|
|
16
|
-
from a vetted template instead of hand-written Liquid.
|
|
17
|
-
|
|
18
|
-
`api.templates.*` is read-only discovery. Three calls:
|
|
19
|
-
|
|
20
|
-
- `systemTemplates(tenantSlug, datalakeSlug)` — the machine-readable list:
|
|
21
|
-
`{ data: [{ path, content, output_schema }] }`. The list is **filtered to your
|
|
22
|
-
datalake's data domain** plus globals — a payments or healthcare template
|
|
23
|
-
never appears in an subscription datalake.
|
|
24
|
-
- `metadata(tenantSlug, datalakeSlug)` — the same catalog as a markdown summary.
|
|
25
|
-
- `metadataDetails(tenantSlug, datalakeSlug, basename, intent)` — one template's
|
|
26
|
-
detail by basename + intent.
|
|
27
|
-
|
|
28
|
-
This is **global** to every data domain — only the anchor template paths below
|
|
29
|
-
differ by domain. See `templates.md` for the reference, and
|
|
30
|
-
`interoperability_contracts.md` for how a template becomes a contract.
|
|
31
|
-
|
|
32
|
-
# Walkthrough
|
|
33
|
-
|
|
34
|
-
The `_setup/subscription.md` bootstrap already provisioned the tenant +
|
|
35
|
-
datalake and left `api`, `tenantSlug`, and `datalakeSlug` populated. This walk
|
|
36
|
-
starts from there.
|
|
37
|
-
|
|
38
|
-
## 001 — list the system templates in scope for this datalake
|
|
39
|
-
|
|
40
|
-
`systemTemplates` returns every platform-shipped Liquid template visible to this
|
|
41
|
-
datalake: the templates for its data domain (here, `subscription`) plus
|
|
42
|
-
domain-agnostic globals (e.g. the SNS delivery-status poller). Each entry has a
|
|
43
|
-
`path`, the Liquid `content`, and an `output_schema` (`null` when the template
|
|
44
|
-
has no companion schema). The list is domain-scoped — templates for other data
|
|
45
|
-
domains (`healthcare`, `payments`, `foundation`, …) are filtered out, so you
|
|
46
|
-
only see what is usable here.
|
|
47
|
-
|
|
48
|
-
```typescript
|
|
49
|
-
const { data: templates } = await api.templates.systemTemplates(tenantSlug, datalakeSlug)
|
|
50
|
-
|
|
51
|
-
const paths = (templates.data ?? []).map((t) => t.path)
|
|
52
|
-
if (paths.length === 0) {
|
|
53
|
-
throw new Error('expected the platform to ship at least one system template')
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// The subscription anchor template is present...
|
|
57
|
-
const arAnchor = 'data_activation/interoperability/subscription/stripe/customers_subscription_customer'
|
|
58
|
-
if (!paths.some((p) => p.includes(arAnchor))) {
|
|
59
|
-
throw new Error(`expected the AR customer template (${arAnchor}) in the list`)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// ...and templates for other data domains are filtered out of an AR datalake.
|
|
63
|
-
const otherDomains = ['healthcare', 'payments', 'foundation', 'core_banking', 'service_commerce', 'trading']
|
|
64
|
-
const leaked = paths.filter((p) => otherDomains.some((d) => p.split('/').includes(d)))
|
|
65
|
-
if (leaked.length > 0) {
|
|
66
|
-
throw new Error(`cross-domain templates leaked into an AR datalake: ${leaked.join(', ')}`)
|
|
67
|
-
}
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
## 002 — read the catalog as markdown
|
|
71
|
-
|
|
72
|
-
`metadata` returns the same catalog as a markdown document — the form an agent
|
|
73
|
-
reads to decide which template to start from. It names the in-scope templates,
|
|
74
|
-
including the AR customer template by basename.
|
|
75
|
-
|
|
76
|
-
```typescript
|
|
77
|
-
const { data: catalog } = await api.templates.metadata(tenantSlug, datalakeSlug)
|
|
78
|
-
if (typeof catalog !== 'string' || catalog.length === 0) {
|
|
79
|
-
throw new Error('expected a non-empty markdown template catalog')
|
|
80
|
-
}
|
|
81
|
-
if (!catalog.includes('customers_subscription_customer')) {
|
|
82
|
-
throw new Error('expected the AR customer template named in the catalog markdown')
|
|
83
|
-
}
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
## 003 — read one template's detail by basename + intent
|
|
87
|
-
|
|
88
|
-
`metadataDetails` pulls a single template's detail. Address it by its
|
|
89
|
-
**basename** (the template name without the path) and its **intent** (the path
|
|
90
|
-
prefix joined with underscores — here `data_activation_interoperability`). The
|
|
91
|
-
result is a markdown body you can show an operator or feed an agent before it
|
|
92
|
-
copies the template into a contract's `template_config`.
|
|
93
|
-
|
|
94
|
-
```typescript
|
|
95
|
-
const { data: detail } = await api.templates.metadataDetails(
|
|
96
|
-
tenantSlug,
|
|
97
|
-
datalakeSlug,
|
|
98
|
-
'customers_subscription_customer',
|
|
99
|
-
'data_activation_interoperability',
|
|
100
|
-
)
|
|
101
|
-
if (typeof detail !== 'string' || detail.length === 0) {
|
|
102
|
-
throw new Error('expected a non-empty markdown detail for the AR customer template')
|
|
103
|
-
}
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
## 004 — write the integration test
|
|
107
|
-
|
|
108
|
-
End the walk with a test you keep: anything that references a system
|
|
109
|
-
template by path (`template_config: { type: 'system', path: … }`) depends
|
|
110
|
-
on that path staying in this datalake's scope — so the durable test
|
|
111
|
-
re-asserts the anchor is listed, the domain filter holds, and the detail
|
|
112
|
-
surface renders. This block runs live under `make validate-cookbook`.
|
|
113
|
-
|
|
114
|
-
```typescript
|
|
115
|
-
const { data: tplList } = await api.templates.systemTemplates(tenantSlug, datalakeSlug)
|
|
116
|
-
const tplPaths = (tplList.data ?? []).map((t) => t.path)
|
|
117
|
-
const anchor = 'data_activation/interoperability/subscription/stripe/customers_subscription_customer'
|
|
118
|
-
if (!tplPaths.some((p) => p.includes(anchor))) {
|
|
119
|
-
throw new Error(`anchor template ${anchor} vanished from the domain-scoped list`)
|
|
120
|
-
}
|
|
121
|
-
const foreign = ['healthcare', 'payments', 'foundation', 'core_banking', 'service_commerce', 'trading']
|
|
122
|
-
const leakedPaths = tplPaths.filter((p) => foreign.some((d) => p.split('/').includes(d)))
|
|
123
|
-
if (leakedPaths.length > 0) {
|
|
124
|
-
throw new Error(`cross-domain templates leaked: ${leakedPaths.join(', ')}`)
|
|
125
|
-
}
|
|
126
|
-
// Behavioural probe — the detail surface must render for the anchor.
|
|
127
|
-
const { data: anchorDetail } = await api.templates.metadataDetails(
|
|
128
|
-
tenantSlug,
|
|
129
|
-
datalakeSlug,
|
|
130
|
-
'customers_subscription_customer',
|
|
131
|
-
'data_activation_interoperability',
|
|
132
|
-
)
|
|
133
|
-
if (typeof anchorDetail !== 'string' || anchorDetail.length === 0) {
|
|
134
|
-
throw new Error('anchor template metadataDetails came back empty')
|
|
135
|
-
}
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
If the anchor disappears in production, escalate with the listing
|
|
139
|
-
evidence — a `type: system` reference that stops resolving is a platform
|
|
140
|
-
regression, not something to paper over by inlining the template body.
|
|
141
|
-
|
|
142
|
-
# Gotchas
|
|
143
|
-
|
|
144
|
-
- **The list is domain-scoped, not global.** `systemTemplates` only returns
|
|
145
|
-
templates for this datalake's data domain plus globals. Calling it on a
|
|
146
|
-
healthcare datalake returns the healthcare templates, never the AR ones —
|
|
147
|
-
there is no "all templates" view.
|
|
148
|
-
- **`output_schema` is often `null`.** A template carries a non-null
|
|
149
|
-
`output_schema` only when it ships a companion schema; most interop templates
|
|
150
|
-
return `null`. Don't treat `null` as an error.
|
|
151
|
-
- **`metadataDetails` keys on basename + intent, not the full path.** Pass the
|
|
152
|
-
bare template name and the underscore-joined path prefix (the "intent"), not
|
|
153
|
-
the slash path from `systemTemplates`.
|
|
154
|
-
- **Discovery only.** None of these calls create or mutate anything — they read
|
|
155
|
-
what the platform ships. To turn a template into a live mapping, copy its
|
|
156
|
-
`content` into an `interoperabilityContracts.create(...)` `template_config`
|
|
157
|
-
(see `interoperability_contracts.md`).
|
|
158
|
-
|
|
159
|
-
# See also
|
|
160
|
-
|
|
161
|
-
- `templates.md` — the `api.templates.*` reference (wire shapes, intents)
|
|
162
|
-
- `interoperability_contracts.md` — how a template becomes a live row mapping
|
|
163
|
-
- `_setup/subscription.md` — the bootstrap this walk starts from
|
|
164
|
-
- `integration-tests/tests/subscription/system-templates.test.ts` — the
|
|
165
|
-
green test these calls are lifted from
|
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: "Capability: talk to your datalake — natural language → SQL → rows"
|
|
3
|
-
summary: A capability walk for datalake text-to-SQL. Generate a read-only SQL statement from a natural-language prompt (`api.datalakes.textToSql`) — only the prompt + schema cross the LLM boundary, never rows — then run it read-only and read back the page (`api.datalakes.executeSql`), with both the JSON `{ data, meta }` envelope and a CSV export. The BI surface on a datalake.
|
|
4
|
-
industry: foundation
|
|
5
|
-
slug: talk-to-data
|
|
6
|
-
vitest_source:
|
|
7
|
-
- integration-tests/tests/foundation/text-to-sql.test.ts
|
|
8
|
-
status: green
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
# Capability
|
|
12
|
-
|
|
13
|
-
**What you get:** a two-call BI surface on a datalake — describe what you want in
|
|
14
|
-
plain language, get back reviewable SQL, then run it read-only and read the rows.
|
|
15
|
-
|
|
16
|
-
The split is deliberate: `textToSql` *generates*, `executeSql` *runs*. A UI or
|
|
17
|
-
agent can show the SQL, let a human edit it, and re-run — and the generate step
|
|
18
|
-
is **data-free by construction** (only the prompt + the datalake schema reach the
|
|
19
|
-
LLM, never rows), so it is safe in both `regulated` and `unregulated` mode.
|
|
20
|
-
|
|
21
|
-
1. `datalakes.textToSql(...)` — natural language → `{ sql, model, provider, explanation }`.
|
|
22
|
-
2. `datalakes.executeSql(...)` — read-only execution → `{ data, meta }` (data is an
|
|
23
|
-
array-of-arrays row page aligned to `meta.columns`), or a CSV string with
|
|
24
|
-
`{ format: 'csv' }`.
|
|
25
|
-
|
|
26
|
-
See `datalakes.md` "Talk to data" for the wire reference and `ai_sandbox.md`
|
|
27
|
-
Layer 3 for why the boundary holds.
|
|
28
|
-
|
|
29
|
-
# Walkthrough
|
|
30
|
-
|
|
31
|
-
The `_setup/foundation.md` bootstrap left `api`, `tenantSlug`, and `datalakeSlug`
|
|
32
|
-
populated, pointing at a freshly-migrated foundation datalake.
|
|
33
|
-
|
|
34
|
-
## 001 — generate SQL from a natural-language prompt
|
|
35
|
-
|
|
36
|
-
`textToSql` runs ordered multi-provider LLM failover and returns the generated
|
|
37
|
-
`sql` plus the winning `model` / `provider` and a best-effort `explanation`
|
|
38
|
-
(`null` when the explainer is unavailable). It does **not** execute the SQL — it
|
|
39
|
-
hands it back for review. Nothing but the prompt and the datalake schema is sent
|
|
40
|
-
to the model, so no datalake rows leave the trust boundary.
|
|
41
|
-
|
|
42
|
-
```typescript
|
|
43
|
-
const { data: gen } = await api.datalakes.textToSql(tenantSlug, datalakeSlug, {
|
|
44
|
-
prompt: 'how many legal entities are there?',
|
|
45
|
-
mode: 'unregulated',
|
|
46
|
-
})
|
|
47
|
-
if (typeof gen.sql !== 'string' || gen.sql.trim() === '') {
|
|
48
|
-
throw new Error(`textToSql returned no SQL (got: ${JSON.stringify(gen.sql)})`)
|
|
49
|
-
}
|
|
50
|
-
if (typeof gen.model !== 'string' || typeof gen.provider !== 'string') {
|
|
51
|
-
throw new Error('textToSql response missing model/provider attribution')
|
|
52
|
-
}
|
|
53
|
-
// gen.explanation is best-effort plain-language prose, or null.
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
## 002 — run a read-only query and read back the page
|
|
57
|
-
|
|
58
|
-
`executeSql` runs the SQL **read-only** (INSERT/UPDATE/DELETE/DDL are rejected)
|
|
59
|
-
and returns `{ data, meta }`. `data` is an **array-of-arrays** row page — positional,
|
|
60
|
-
aligned to `meta.columns`, because arbitrary SQL can have duplicate / expression
|
|
61
|
-
column names. `meta` carries the structural + pagination fields. A deterministic
|
|
62
|
-
`SELECT 1 AS n` keeps this step independent of the generated SQL and seeded data.
|
|
63
|
-
|
|
64
|
-
The curated return is `ExecuteSqlResponse | string` (the CSV branch is a string),
|
|
65
|
-
so narrow with a `typeof` guard before reading the JSON envelope.
|
|
66
|
-
|
|
67
|
-
```typescript
|
|
68
|
-
const jsonResult = await api.datalakes.executeSql(tenantSlug, datalakeSlug, {
|
|
69
|
-
sql: 'SELECT 1 AS n',
|
|
70
|
-
mode: 'unregulated',
|
|
71
|
-
})
|
|
72
|
-
if (typeof jsonResult.data === 'string') {
|
|
73
|
-
throw new Error('expected the JSON envelope, got a CSV string')
|
|
74
|
-
}
|
|
75
|
-
const page = jsonResult.data
|
|
76
|
-
if (!Array.isArray(page.data) || !Array.isArray(page.data[0])) {
|
|
77
|
-
throw new Error('executeSql data is not an array-of-arrays')
|
|
78
|
-
}
|
|
79
|
-
if (page.meta.columns[0] !== 'n' || page.data[0][0] !== 1 || page.meta.num_rows !== 1) {
|
|
80
|
-
throw new Error(`unexpected SELECT 1 result: ${JSON.stringify(page)}`)
|
|
81
|
-
}
|
|
82
|
-
// page.meta also carries total_count, page, page_size, total_pages, duration_ms, command.
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
## 003 — export the same page as CSV
|
|
86
|
-
|
|
87
|
-
Pass `{ format: 'csv' }` to get the page as a CSV **string** instead of the JSON
|
|
88
|
-
envelope — the same read, content-negotiated for download.
|
|
89
|
-
|
|
90
|
-
```typescript
|
|
91
|
-
const csvResult = await api.datalakes.executeSql(
|
|
92
|
-
tenantSlug,
|
|
93
|
-
datalakeSlug,
|
|
94
|
-
{ sql: 'SELECT 1 AS n', mode: 'unregulated' },
|
|
95
|
-
{ format: 'csv' },
|
|
96
|
-
)
|
|
97
|
-
if (typeof csvResult.data !== 'string') {
|
|
98
|
-
throw new Error('expected a CSV string for { format: "csv" }')
|
|
99
|
-
}
|
|
100
|
-
const csv = csvResult.data
|
|
101
|
-
if (!/\bn\b/.test(csv) || !csv.includes('1')) {
|
|
102
|
-
throw new Error(`unexpected CSV body: ${JSON.stringify(csv)}`)
|
|
103
|
-
}
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
## 004 — write the integration test
|
|
107
|
-
|
|
108
|
-
End the walk with a test you keep: both surfaces are read-only, so the
|
|
109
|
-
durable test is the pair of deterministic boundary probes — the
|
|
110
|
-
round-trip works, and the read-only wall holds. Neither depends on an
|
|
111
|
-
LLM provider or seeded data, which is what makes the test durable. This
|
|
112
|
-
block runs live under `make validate-cookbook`.
|
|
113
|
-
|
|
114
|
-
```typescript
|
|
115
|
-
// Round-trip probe — deterministic, data-independent.
|
|
116
|
-
const probe = await api.datalakes.executeSql(tenantSlug, datalakeSlug, {
|
|
117
|
-
sql: 'SELECT 1 AS ok',
|
|
118
|
-
mode: 'unregulated',
|
|
119
|
-
})
|
|
120
|
-
if (typeof probe.data === 'string' || probe.data.data[0]?.[0] !== 1) {
|
|
121
|
-
throw new Error('executeSql round-trip probe failed')
|
|
122
|
-
}
|
|
123
|
-
// Boundary probe — write SQL must be rejected with a 422; the
|
|
124
|
-
// read-only wall is the security property this capability rests on.
|
|
125
|
-
let walled = false
|
|
126
|
-
try {
|
|
127
|
-
await api.datalakes.executeSql(tenantSlug, datalakeSlug, {
|
|
128
|
-
sql: "INSERT INTO legal_entities (legal_name) VALUES ('test-should-never-land')",
|
|
129
|
-
mode: 'unregulated',
|
|
130
|
-
})
|
|
131
|
-
} catch (err) {
|
|
132
|
-
const status = (err as { _httpStatus?: number })._httpStatus
|
|
133
|
-
if (status !== 422) throw err
|
|
134
|
-
walled = true
|
|
135
|
-
}
|
|
136
|
-
if (!walled) {
|
|
137
|
-
throw new Error('executeSql accepted a write statement — the read-only wall is down')
|
|
138
|
-
}
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
If the write probe ever lands in production, stop and escalate
|
|
142
|
-
immediately with the accepted statement as evidence — a read-only
|
|
143
|
-
boundary failure is a security regression, never a config problem.
|
|
144
|
-
|
|
145
|
-
# Branches
|
|
146
|
-
|
|
147
|
-
- **Generation failure.** If every configured LLM provider fails, `textToSql`
|
|
148
|
-
returns `422` (`AlveraApiError`) — `Text-to-SQL generation failed for all
|
|
149
|
-
providers (...)`. Surface it; do not retry blindly.
|
|
150
|
-
- **Write SQL rejected.** `executeSql` with a non-read-only statement
|
|
151
|
-
(`INSERT`/`UPDATE`/`DELETE`/DDL) is rejected `422` — the read-only boundary is
|
|
152
|
-
enforced at the app `@deny` regex, a read-only DB transaction, and an EXPLAIN
|
|
153
|
-
preflight.
|
|
154
|
-
- **Pagination.** Pass `page` / `page_size` to window large result sets;
|
|
155
|
-
`meta.total_count` / `meta.total_pages` describe the full set (`page_size` is
|
|
156
|
-
capped server-side).
|
|
157
|
-
|
|
158
|
-
# Rollback
|
|
159
|
-
|
|
160
|
-
Nothing to tear down — both calls are read-only (or generate-only). The
|
|
161
|
-
`_setup/foundation.md` tenant + datalake are reset with `mix ecto.reset` on the
|
|
162
|
-
platform; per-run `runSuffix` names avoid collisions across reruns.
|
|
163
|
-
|
|
164
|
-
# Outcome
|
|
165
|
-
|
|
166
|
-
A datalake becomes conversational: a prompt yields reviewable SQL with provider
|
|
167
|
-
attribution and a plain-language explanation, that SQL runs read-only, and the
|
|
168
|
-
page comes back either as a structured `{ data, meta }` envelope or a CSV export
|
|
169
|
-
— all without any datalake row ever reaching the LLM.
|
|
170
|
-
|
|
171
|
-
# See also
|
|
172
|
-
|
|
173
|
-
- `.agent/datalakes.md` — "Talk to data" (`textToSql` / `executeSql` wire shape)
|
|
174
|
-
and §6 gotcha on the array-of-arrays `data`.
|
|
175
|
-
- `.agent/ai_sandbox.md` — Layer 3 SQL boundary: how generation stays data-free
|
|
176
|
-
and execution stays read-only + mode-routed.
|
|
177
|
-
- `integration-tests/tests/foundation/text-to-sql.test.ts` — the green vitest
|
|
178
|
-
these snippets are lifted from.
|