@alvera-ai/platform-sdk 0.11.0 → 0.12.1-next.f7f8a3c

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 CHANGED
@@ -357,7 +357,7 @@ The `cookbook/` directory holds two kinds of recipe, both validated by
357
357
  an outcome.
358
358
  - **Capability docs** (`<capability>.md`) — one platform capability each,
359
359
  shown as the minimal call sequence that proves it (e.g. `bulk-ingest`,
360
- `ai-agent-invoke`, `custom-tables`, `invite-team`). Read these to learn one
360
+ `ai-agent-invoke`, `generic-tables`, `invite-team`). Read these to learn one
361
361
  capability in isolation; cookbooks weave them into outcomes.
362
362
 
363
363
  ### Available cookbooks
@@ -430,14 +430,18 @@ full wire shape.
430
430
  document/image and pull structured JSON out of it (`aiAgents.invoke` with
431
431
  files). The correct file-vision path — the agent reads the file; the DAC
432
432
  ingests it.
433
- - [custom-tables](./cookbook/custom-tables.md) — stand up a custom table the
434
- built-in datasets don't model; deploy → ingest → search.
433
+ - [generic-tables](./cookbook/generic-tables.md) — stand up a generic table the
434
+ built-in datasets don't model; deploy → ingest → read back via executeSql.
435
435
  - [action-status-updaters](./cookbook/action-status-updaters.md) — reconcile
436
436
  the delivery status of messages you send, on a schedule.
437
437
  - [system-templates](./cookbook/system-templates.md) — discover the platform's
438
438
  built-in row-mapping Liquid templates.
439
439
  - [invite-team](./cookbook/invite-team.md) — invite a teammate into your tenant
440
440
  (root / tenantless / tenant-scoped sessions in one flow).
441
+ - [talk-to-data](./cookbook/talk-to-data.md) — turn a datalake conversational:
442
+ natural language → reviewable SQL (`datalakes.textToSql`, data-free by
443
+ construction) → read-only execution returning a `{ data, meta }` row page or a
444
+ CSV export (`datalakes.executeSql`).
441
445
 
442
446
  ## Utility namespaces
443
447
 
@@ -179,6 +179,31 @@ via consumer-authored fragments is structurally impossible: the
179
179
  fragment is the WHERE *expression*, with values supplied via
180
180
  positional bindings the platform builds.
181
181
 
182
+ ### Datalake "talk to data" — `textToSql` + `executeSql`
183
+
184
+ The datalake BI surface (`api.datalakes.textToSql` /
185
+ `api.datalakes.executeSql`, see `datalakes.md` "Talk to data") is
186
+ the **full-statement** sibling of the WHERE-clause lane above, and
187
+ it sits inside the same Layer-3 boundary:
188
+
189
+ - **Generation is data-free by construction.** `textToSql` sends
190
+ only the natural-language `prompt` plus the datalake **schema**
191
+ to the LLM — never rows. The model returns SQL for review; no
192
+ datalake data ever crosses the LLM boundary, so generation is
193
+ safe in both `regulated` and `unregulated` mode. The two-call
194
+ split (generate, then execute) exists so a human or agent can
195
+ inspect and edit the SQL before any data is read.
196
+ - **Execution is read-only and mode-routed.** `executeSql` runs the
197
+ statement against the mode-appropriate schema with a read-only
198
+ connection: INSERT/UPDATE/DELETE/DDL are rejected (app-layer
199
+ `@deny` + a read-only DB transaction + an EXPLAIN preflight), and
200
+ `mode` (`regulated` vs `unregulated`) plus the session's tenant
201
+ (RLS) decide which schema and which rows are reachable — the same
202
+ Layer-1 compliance gates that govern every other read.
203
+ - **Pagination caps blast radius.** `page` / `page_size` map to a
204
+ server-side `LIMIT/OFFSET` window capped at the platform default,
205
+ so a single call cannot exfiltrate an unbounded result set.
206
+
182
207
  ## How the three layers compose
183
208
 
184
209
  ```
@@ -1,8 +1,8 @@
1
1
  ---
2
- title: "Capability: stand up a custom table the platform doesn't model"
3
- summary: A capability walk for generic tables. Create a custom table with typed, privacy-tagged columns (`api.genericTables.create`), wait for it to deploy, use the default ingestion client the platform auto-provisions for it, ingest a row (`api.dataActivationClients.ingest`), and read it back by its unique key (`api.datasets.search`). For data the built-in datasets don't cover.
2
+ title: "Capability: stand up a generic table the platform doesn't model"
3
+ summary: A capability walk for generic tables. Create a generic table with typed, privacy-tagged columns (`api.genericTables.create`), wait for it to deploy, use the default ingestion client the platform auto-provisions for it, ingest a row (`api.dataActivationClients.ingest`), and read it back with read-only SQL (`api.datalakes.executeSql`). For data the built-in datasets don't cover.
4
4
  industry: accounts_receivable
5
- slug: custom-tables
5
+ slug: generic-tables
6
6
  vitest_source:
7
7
  - integration-tests/tests/accounts_receivable/generic-tables.test.ts
8
8
  - integration-tests/tests/accounts_receivable/bootstrap.test.ts
@@ -23,7 +23,7 @@ The lifecycle is:
23
23
  **auto-provisions a default Data Activation Client** bound to an identity
24
24
  contract — no tool, contract, or DAC to wire by hand.
25
25
  3. `dataActivationClients.ingest(...)` a row through that default client.
26
- 4. `datasets.search(...)` it back by its unique column.
26
+ 4. `datalakes.executeSql(...)` reads it back by its unique column with read-only SQL.
27
27
 
28
28
  This is **global** to every datalake — only the table's domain meaning differs.
29
29
  See `generic_tables.md` for the column reference.
@@ -33,7 +33,7 @@ See `generic_tables.md` for the column reference.
33
33
  The `_setup/accounts_receivable.md` bootstrap left `api`, `tenantSlug`, and
34
34
  `datalakeSlug` populated.
35
35
 
36
- ## 001 — list existing custom tables (sanity)
36
+ ## 001 — list existing generic tables (sanity)
37
37
 
38
38
  `genericTables.list` returns the paginated `{ data, meta }` envelope. A fresh
39
39
  datalake has none; the call just confirms the surface is reachable.
@@ -45,7 +45,7 @@ if (!Array.isArray(existing.data)) {
45
45
  }
46
46
  ```
47
47
 
48
- ## 002 — create the custom table
48
+ ## 002 — create the generic table
49
49
 
50
50
  Declare the table with a `title` and `columns`. Each column has a `type`, an
51
51
  `is_unique` flag, and — load-bearing — a `privacy_requirement` that decides how
@@ -115,7 +115,7 @@ while (Date.now() < deadline && !defaultDacSlug) {
115
115
  if (!defaultDacSlug) await new Promise((r) => setTimeout(r, 1_000))
116
116
  }
117
117
  if (!defaultDacSlug) {
118
- throw new Error('default DAC for the custom table was not provisioned within 30s')
118
+ throw new Error('default DAC for the generic table was not provisioned within 30s')
119
119
  }
120
120
  ctx.defaultDacSlug = defaultDacSlug
121
121
  ```
@@ -144,34 +144,34 @@ if (!ingest.batch_id) {
144
144
  }
145
145
  ```
146
146
 
147
- ## 006 — read the row back by its unique key
147
+ ## 006 — read the row back with read-only SQL
148
148
 
149
- Search the `generic_table` dataset, scoped to this table via `generic_table_id`,
150
- filtering on the regulated table name (`regulated_<table_name>`) and the unique
151
- column. `createUserSearch` compiles the query; `search` returns rows once
152
- ingestion has landed poll until the row appears.
149
+ `executeSql` runs a read-only statement against the datalake and returns a page
150
+ as `{ data, meta }` `data` is an array-of-arrays aligned positionally to
151
+ `meta.columns`. `mode: 'unregulated'` runs it against the unregulated schema, so
152
+ you reference the table by its server-derived `name` (the `alvera_custom_…`
153
+ value from §002 — `mode` selects the schema, so no `regulated_`/`unregulated_`
154
+ prefix). Ingestion is async, so poll until the row lands.
153
155
 
154
156
  ```typescript
155
- const { data: userSearch } = await api.datasets.createUserSearch(tenantSlug, datalakeSlug, 'generic_table', {
156
- search_query: `regulated_${ctx.tableName}.submission_id = '${ctx.submissionId}'`,
157
- generic_table_id: genericTableId,
158
- })
159
- if (userSearch.status !== 'completed') {
160
- throw new Error(`user-search did not compile: ${userSearch.status}`)
161
- }
162
-
163
157
  const deadline = Date.now() + 45_000
164
- let rows: Array<Record<string, unknown>> = []
165
- while (Date.now() < deadline && rows.length === 0) {
166
- const { data: page } = await api.datasets.search(tenantSlug, datalakeSlug, 'generic_table', {
167
- userSearchId: userSearch.id!,
168
- dataAccessMode: 'unregulated',
158
+ let row: Record<string, unknown> | undefined
159
+ while (Date.now() < deadline && !row) {
160
+ const { data: result } = await api.datalakes.executeSql(tenantSlug, datalakeSlug, {
161
+ sql: `SELECT submission_id, customer_name, source_channel
162
+ FROM ${ctx.tableName}
163
+ WHERE submission_id = '${ctx.submissionId}'`,
164
+ mode: 'unregulated',
169
165
  })
170
- rows = (page.data ?? []) as Array<Record<string, unknown>>
171
- if (rows.length === 0) await new Promise((r) => setTimeout(r, 1_000))
166
+ // JSON envelope (a `string` only for `{ format: 'csv' }`); zip the positional
167
+ // row back into an object keyed by meta.columns.
168
+ if (typeof result !== 'string' && result.data.length > 0) {
169
+ row = Object.fromEntries(result.meta.columns.map((c, i) => [c, result.data[0][i]]))
170
+ }
171
+ if (!row) await new Promise((r) => setTimeout(r, 1_000))
172
172
  }
173
- if (!rows.some((r) => r.submission_id === ctx.submissionId)) {
174
- throw new Error('ingested row not found in the custom table within 45s')
173
+ if (!row || row.submission_id !== ctx.submissionId) {
174
+ throw new Error('ingested row not found in the generic table within 45s')
175
175
  }
176
176
  ```
177
177
 
@@ -182,15 +182,18 @@ if (!rows.some((r) => r.submission_id === ctx.submissionId)) {
182
182
  free text is scrubbed, `none` passes through. Dropping it silently changes who
183
183
  can see what.
184
184
  - **The table name is server-derived, never set by you.** The platform slugifies
185
- the title and prefixes `alvera_custom_`. Read `name` off the create response;
186
- the search query uses `regulated_<name>`.
185
+ the title and prefixes `alvera_custom_`. Read `name` off the create response and
186
+ reference it directly in `executeSql`; `mode` selects the schema, so you don't
187
+ add a `regulated_`/`unregulated_` prefix yourself.
187
188
  - **You don't create the ingestion client.** Deploying the table auto-provisions
188
189
  a default DAC + identity contract. Find it by name; don't build one.
189
- - **Two async waits.** Wait for `:deployed` before ingesting, then poll the
190
- search until the row materializes — ingestion is async like every other
190
+ - **Two async waits.** Wait for `:deployed` before ingesting, then poll
191
+ `executeSql` until the row materializes — ingestion is async like every other
191
192
  ingest path.
192
- - **`generic_table_id` on the search is required.** It routes the query to your
193
- table's dataset; omitting it searches the wrong scope.
193
+ - **`mode` picks the schema, and `tokenize` columns come back masked.** In
194
+ `mode: 'unregulated'` the read runs against the tokenized view, so filter and
195
+ assert on a `none` column (here `submission_id`); a `tokenize` column like
196
+ `customer_name` returns its token, not the raw value.
194
197
 
195
198
  # See also
196
199
 
@@ -0,0 +1,139 @@
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
+ # Branches
107
+
108
+ - **Generation failure.** If every configured LLM provider fails, `textToSql`
109
+ returns `422` (`AlveraApiError`) — `Text-to-SQL generation failed for all
110
+ providers (...)`. Surface it; do not retry blindly.
111
+ - **Write SQL rejected.** `executeSql` with a non-read-only statement
112
+ (`INSERT`/`UPDATE`/`DELETE`/DDL) is rejected `422` — the read-only boundary is
113
+ enforced at the app `@deny` regex, a read-only DB transaction, and an EXPLAIN
114
+ preflight.
115
+ - **Pagination.** Pass `page` / `page_size` to window large result sets;
116
+ `meta.total_count` / `meta.total_pages` describe the full set (`page_size` is
117
+ capped server-side).
118
+
119
+ # Rollback
120
+
121
+ Nothing to tear down — both calls are read-only (or generate-only). The
122
+ `_setup/foundation.md` tenant + datalake are reset with `mix ecto.reset` on the
123
+ platform; per-run `runSuffix` names avoid collisions across reruns.
124
+
125
+ # Outcome
126
+
127
+ A datalake becomes conversational: a prompt yields reviewable SQL with provider
128
+ attribution and a plain-language explanation, that SQL runs read-only, and the
129
+ page comes back either as a structured `{ data, meta }` envelope or a CSV export
130
+ — all without any datalake row ever reaching the LLM.
131
+
132
+ # See also
133
+
134
+ - `.agent/datalakes.md` — "Talk to data" (`textToSql` / `executeSql` wire shape)
135
+ and §6 gotcha on the array-of-arrays `data`.
136
+ - `.agent/ai_sandbox.md` — Layer 3 SQL boundary: how generation stays data-free
137
+ and execution stays read-only + mode-routed.
138
+ - `integration-tests/tests/foundation/text-to-sql.test.ts` — the green vitest
139
+ these snippets are lifted from.
@@ -586,6 +586,52 @@ for (const name of catalog.datasets) {
586
586
  }
587
587
  ```
588
588
 
589
+ ### Talk to data — `textToSql` + `executeSql`
590
+
591
+ Two query methods turn the datalake into a BI surface: natural language → SQL →
592
+ rows. They are **split on purpose** so an agent or UI can show, edit, and re-run
593
+ the SQL between the two calls.
594
+
595
+ | Method | Returns |
596
+ |------------------------------------------------------------|-----------------------------------------------------------|
597
+ | `.textToSql(tenantSlug, datalakeSlug, body)` | `{ sql, model, provider, explanation }` |
598
+ | `.executeSql(tenantSlug, datalakeSlug, body, options?)` | `{ data, meta }` — or a CSV **string** with `{ format: 'csv' }` |
599
+
600
+ - **`textToSql`** body is `{ prompt, mode }` (`mode` ∈ `'regulated' | 'unregulated'`,
601
+ selecting which schema to target). It runs ordered multi-provider LLM failover and
602
+ returns the generated `sql`, the winning `model` + `provider`, and a best-effort
603
+ `explanation` (`null` when the explainer is unavailable). **Only the prompt + the
604
+ schema cross the LLM boundary — never datalake rows**, so it is safe in both modes.
605
+ It returns SQL for review; it does **not** execute it.
606
+ - **`executeSql`** body is `{ sql, mode, page?, page_size? }`. It runs the SQL
607
+ **read-only** (INSERT/UPDATE/DELETE/DDL are rejected) on the mode-appropriate
608
+ schema, with Flop-inspired pagination (`page_size` capped server-side). The JSON
609
+ shape is `{ data, meta }`; pass `options = { format: 'csv' }` to get the page as a
610
+ CSV string attachment instead.
611
+
612
+ ```typescript
613
+ const { data: gen } = await api.datalakes.textToSql(tenantSlug, datalakeSlug, {
614
+ prompt: 'count contacts created this month',
615
+ mode: 'unregulated',
616
+ })
617
+ // gen.sql — review / edit before running
618
+
619
+ const { data: page } = await api.datalakes.executeSql(tenantSlug, datalakeSlug, {
620
+ sql: gen.sql,
621
+ mode: 'unregulated',
622
+ page: 1,
623
+ page_size: 100,
624
+ })
625
+ // page.data — array-of-arrays rows, aligned to page.meta.columns
626
+ // page.meta — { columns, num_rows, total_count, page, page_size, total_pages, duration_ms, command }
627
+
628
+ const { data: csv } = await api.datalakes.executeSql(
629
+ tenantSlug, datalakeSlug,
630
+ { sql: gen.sql, mode: 'unregulated' },
631
+ { format: 'csv' },
632
+ ) // csv is a string, not the JSON envelope
633
+ ```
634
+
589
635
  ## 6. Gotchas
590
636
 
591
637
  1. **`create()` does NOT auto-enqueue migration.** The create response
@@ -712,3 +758,12 @@ for (const name of catalog.datasets) {
712
758
  `"password"` — and consequently requires both `*_user` and
713
759
  `*_pass` to be supplied. The IAM-role relaxation in §2 applies
714
760
  only when `auth_method` is explicitly `"iam_role"`.
761
+
762
+ 13. **`executeSql` rows are array-of-arrays, not objects.** `data` is
763
+ `Array<Array<unknown>>` — a positional row page with **no column
764
+ keys**, because arbitrary SQL can yield duplicate or expression
765
+ column names that have no safe object key. Read column names from
766
+ `meta.columns` and index rows positionally (`row[i]` ↔
767
+ `meta.columns[i]`); never assume `row.someColumn`. The `?format=csv`
768
+ branch returns a CSV **string** with no `meta` envelope — for the
769
+ structural metadata, use the JSON call.
package/LICENSE ADDED
@@ -0,0 +1,93 @@
1
+ Elastic License 2.0
2
+
3
+ URL: https://www.elastic.co/licensing/elastic-license
4
+
5
+ ## Acceptance
6
+
7
+ By using the software, you agree to all of the terms and conditions below.
8
+
9
+ ## Copyright License
10
+
11
+ The licensor grants you a non-exclusive, royalty-free, worldwide,
12
+ non-sublicensable, non-transferable license to use, copy, distribute, make
13
+ available, and prepare derivative works of the software, in each case subject to
14
+ the limitations and conditions below.
15
+
16
+ ## Limitations
17
+
18
+ You may not provide the software to third parties as a hosted or managed
19
+ service, where the service provides users with access to any substantial set of
20
+ the features or functionality of the software.
21
+
22
+ You may not move, change, disable, or circumvent the license key functionality
23
+ in the software, and you may not remove or obscure any functionality in the
24
+ software that is protected by the license key.
25
+
26
+ You may not alter, remove, or obscure any licensing, copyright, or other notices
27
+ of the licensor in the software. Any use of the licensor’s trademarks is subject
28
+ to applicable law.
29
+
30
+ ## Patents
31
+
32
+ The licensor grants you a license, under any patent claims the licensor can
33
+ license, or becomes able to license, to make, have made, use, sell, offer for
34
+ sale, import and have imported the software, in each case subject to the
35
+ limitations and conditions in this license. This license does not cover any
36
+ patent claims that you cause to be infringed by modifications or additions to
37
+ the software. If you or your company make any written claim that the software
38
+ infringes or contributes to infringement of any patent, your patent license for
39
+ the software granted under these terms ends immediately. If your company makes
40
+ such a claim, your patent license ends immediately for work on behalf of your
41
+ company.
42
+
43
+ ## Notices
44
+
45
+ You must ensure that anyone who gets a copy of any part of the software from you
46
+ also gets a copy of these terms.
47
+
48
+ If you modify the software, you must include in any modified copies of the
49
+ software prominent notices stating that you have modified the software.
50
+
51
+ ## No Other Rights
52
+
53
+ These terms do not imply any licenses other than those expressly granted in
54
+ these terms.
55
+
56
+ ## Termination
57
+
58
+ If you use the software in violation of these terms, such use is not licensed,
59
+ and your licenses will automatically terminate. If the licensor provides you
60
+ with a notice of your violation, and you cease all violation of this license no
61
+ later than 30 days after you receive that notice, your licenses will be
62
+ reinstated retroactively. However, if you violate these terms after such
63
+ reinstatement, any additional violation of these terms will cause your licenses
64
+ to terminate automatically and permanently.
65
+
66
+ ## No Liability
67
+
68
+ *As far as the law allows, the software comes as is, without any warranty or
69
+ condition, and the licensor will not be liable to you for any damages arising
70
+ out of these terms or the use or nature of the software, under any kind of
71
+ legal claim.*
72
+
73
+ ## Definitions
74
+
75
+ The **licensor** is the entity offering these terms, and the **software** is the
76
+ software the licensor makes available under these terms, including any portion
77
+ of it.
78
+
79
+ **you** refers to the individual or entity agreeing to these terms.
80
+
81
+ **your company** is any legal entity, sole proprietorship, or other kind of
82
+ organization that you work for, plus all organizations that have control over,
83
+ are under the control of, or are under common control with that
84
+ organization. **control** means ownership of substantially all the assets of an
85
+ entity, or the power to direct its management and policies by vote, contract, or
86
+ otherwise. Control can be direct or indirect.
87
+
88
+ **your licenses** are all the licenses granted to you for the software under
89
+ these terms.
90
+
91
+ **use** means anything you do with the software requiring one of your licenses.
92
+
93
+ **trademark** means trademarks, service marks, and similar rights.
package/README.md CHANGED
@@ -132,7 +132,7 @@ Every resource is a typed namespace on the client (`api.<resource>.<verb>`):
132
132
  | `tenants` | `list`, `create` |
133
133
  | `invitations` | `list`, `create`, `accept` |
134
134
  | `datasets` | `search`, `metadata`, `createUserSearch` |
135
- | `datalakes` | `list`, `get`, `create`, `metadata`, `migrate`, `createUploadLink`, `createDownloadLink` |
135
+ | `datalakes` | `list`, `get`, `create`, `metadata`, `migrate`, `createUploadLink`, `createDownloadLink`, `textToSql`, `executeSql` |
136
136
  | `dataSources` | `list`, `create`, `update` |
137
137
  | `tools` | `list`, `get`, `create`, `update`, `delete`, `testInvocation` |
138
138
  | `genericTables` | `list`, `create` |