@alvera-ai/platform-sdk 0.18.0 → 0.18.1
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 +11 -2
- package/.agent/advanced_migrations.md +202 -0
- package/.agent/datalakes.md +69 -0
- package/.agent/generic_tables.md +32 -0
- package/.agent/mock-services.md +12 -11
- package/dist/index.d.mts +71 -23
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +49 -8
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/.agent/AGENTS.md
CHANGED
|
@@ -259,6 +259,10 @@ numbered steps inside the cookbook whose use case motivates it.
|
|
|
259
259
|
DATA ACTIVATION (how data flows in)
|
|
260
260
|
───────────────
|
|
261
261
|
generic_tables.md Schema-on-write tables.
|
|
262
|
+
advanced_migrations.md Hand-written DDL for one lake —
|
|
263
|
+
indexes, foreign keys, anything
|
|
264
|
+
past the CREATE TABLE / ADD COLUMN
|
|
265
|
+
the platform writes for itself.
|
|
262
266
|
interoperability_contracts.md Liquid template mappings.
|
|
263
267
|
data_activation_clients.md Ingestion pipelines — both the
|
|
264
268
|
binding row (CRUD) and the
|
|
@@ -298,6 +302,7 @@ wire name (snake_case):
|
|
|
298
302
|
api.connectedApps connected_apps.md
|
|
299
303
|
api.mdm mdm.md
|
|
300
304
|
api.genericTables generic_tables.md
|
|
305
|
+
api.advancedMigrations advanced_migrations.md
|
|
301
306
|
api.interoperabilityContracts interoperability_contracts.md
|
|
302
307
|
api.dataActivationClients data_activation_clients.md
|
|
303
308
|
api.workflows workflows.md
|
|
@@ -327,8 +332,12 @@ Each cookbook structure:
|
|
|
327
332
|
are lifted from, anchor file first), `status`. `use_case` is one
|
|
328
333
|
of the four surfaces — `organic-marketing`,
|
|
329
334
|
`subscription-saas`, `payments-compliance`,
|
|
330
|
-
`primary-care-feedback` — and there is exactly one cookbook per
|
|
331
|
-
|
|
335
|
+
`primary-care-feedback` — and there is exactly one cookbook per use
|
|
336
|
+
case. Three are at `cookbook/<use-case>.md`; the fourth is at
|
|
337
|
+
`cookbook/primary-care.md`, because `use_case` tracks the integration
|
|
338
|
+
suite it is lifted from (`integration-tests/tests/primary-care-feedback/`)
|
|
339
|
+
and the filename does not. Read `use_case` from the front matter rather
|
|
340
|
+
than deriving it from the filename.
|
|
332
341
|
- **Problem** — the business-outcome statement in domain terms,
|
|
333
342
|
sourced from the anchor vitest's behaviour (not from
|
|
334
343
|
customer-narrative documentation).
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
# Advanced migrations
|
|
2
|
+
|
|
3
|
+
SQL a person wrote by hand for one datalake.
|
|
4
|
+
|
|
5
|
+
The platform writes its own DDL when a generic table is created or gains a
|
|
6
|
+
column — `CREATE TABLE` and `ALTER TABLE … ADD COLUMN`, and nothing else.
|
|
7
|
+
Everything past that is yours to write here: an index, a foreign key, a column
|
|
8
|
+
rename, a type change, a drop. The same migrator runs both.
|
|
9
|
+
|
|
10
|
+
**One name, two spellings.** Outwardly these are *advanced migrations*, which is
|
|
11
|
+
what the API path and this guide call them. The platform stores them in
|
|
12
|
+
`datalake_migrations`, and its own logs and internals use that name. Both are
|
|
13
|
+
correct; do not coin a third.
|
|
14
|
+
|
|
15
|
+
api.advancedMigrations → this guide
|
|
16
|
+
/datalakes/{slug}/advanced-migrations
|
|
17
|
+
|
|
18
|
+
## 1. Wire shape
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
const { data: created } = await api.advancedMigrations.create(
|
|
22
|
+
tenantSlug,
|
|
23
|
+
datalakeSlug, // scope comes from the PATH — see below
|
|
24
|
+
{
|
|
25
|
+
name: 'AddEmailIndexToContactSubmissions',
|
|
26
|
+
up_statement:
|
|
27
|
+
'CREATE INDEX IF NOT EXISTS idx_contact_submissions_email ' +
|
|
28
|
+
'ON alvera_custom_contact_submissions (email)',
|
|
29
|
+
down_statement:
|
|
30
|
+
'DROP INDEX IF EXISTS idx_contact_submissions_email',
|
|
31
|
+
},
|
|
32
|
+
)
|
|
33
|
+
// created.id — server-derived UUID
|
|
34
|
+
// created.version — server-assigned from a sequence; orders execution
|
|
35
|
+
// created.datalake_id — server-derived from the path
|
|
36
|
+
// created.checksum — server-computed; the drift fingerprint
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Three fields, all required, all strings:
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
name required string — capitalized identifier, unique per datalake
|
|
43
|
+
up_statement required string — the DDL to run
|
|
44
|
+
down_statement required string — the DDL that reverses it
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**`datalake_id` is NOT a body field.** This is the one place the shape departs
|
|
48
|
+
from every other datalake-scoped kind. Elsewhere — tools, workflows, generic
|
|
49
|
+
tables — you send `datalake_id` in the body. Here it is `readOnly`: the scope
|
|
50
|
+
comes from `datalakeSlug` in the path, and the server fills the field in. Sending
|
|
51
|
+
it is not how you say which lake this belongs to.
|
|
52
|
+
|
|
53
|
+
**`version` is server-assigned and orders execution.** It comes from a sequence,
|
|
54
|
+
not from you, and it is the thing that decides what runs first — not the name,
|
|
55
|
+
and not the order you declared them in. Generated migrations carry a year-style
|
|
56
|
+
prefix in their names to read well beside yours; that prefix is cosmetic.
|
|
57
|
+
|
|
58
|
+
## 2. Rules the type cannot encode
|
|
59
|
+
|
|
60
|
+
### An applied migration is frozen — update and delete both refuse
|
|
61
|
+
|
|
62
|
+
Once a migration has run successfully, `update()` and `delete()` each answer
|
|
63
|
+
**409 Conflict**. The reasons differ and both are worth knowing.
|
|
64
|
+
|
|
65
|
+
**Update** is refused because a statement that has already executed cannot be
|
|
66
|
+
rewritten after the fact: the new text would no longer describe what happened.
|
|
67
|
+
|
|
68
|
+
**Delete** is refused because the run log cascades on delete, so removing an
|
|
69
|
+
applied migration would also erase the record of it having run.
|
|
70
|
+
|
|
71
|
+
So a migration has exactly two lives. Before it runs it is ordinary and
|
|
72
|
+
editable. After it runs it is history. **Changing applied DDL means writing a
|
|
73
|
+
NEW migration that alters what the old one built** — the same shape as changing
|
|
74
|
+
a generic table column's `privacy_requirement`, which is delete-and-recreate
|
|
75
|
+
rather than an edit.
|
|
76
|
+
|
|
77
|
+
"Applied" means precisely: a run log row exists for this migration with
|
|
78
|
+
direction `up` and status `success`. A failed run does not freeze it.
|
|
79
|
+
|
|
80
|
+
### Write the statement to be safe on a second run
|
|
81
|
+
|
|
82
|
+
The platform tracks what it has applied and will not run an applied migration
|
|
83
|
+
twice, so you do not need `IF NOT EXISTS` for that. You need it for the other
|
|
84
|
+
case: a run that failed **partway**. The applied-migrations record stops a second
|
|
85
|
+
run; it does not undo a first one that got halfway.
|
|
86
|
+
|
|
87
|
+
```sql
|
|
88
|
+
-- safe to meet again
|
|
89
|
+
CREATE INDEX IF NOT EXISTS idx_submissions_email ON … (email)
|
|
90
|
+
|
|
91
|
+
-- Postgres has no ADD CONSTRAINT IF NOT EXISTS, so guard it yourself
|
|
92
|
+
DO $$
|
|
93
|
+
BEGIN
|
|
94
|
+
IF NOT EXISTS (
|
|
95
|
+
SELECT 1 FROM pg_constraint WHERE conname = 'fk_submissions_lead'
|
|
96
|
+
) THEN
|
|
97
|
+
ALTER TABLE alvera_custom_contact_submissions
|
|
98
|
+
ADD CONSTRAINT fk_submissions_lead
|
|
99
|
+
FOREIGN KEY (lead_id) REFERENCES alvera_custom_leads (id);
|
|
100
|
+
END IF;
|
|
101
|
+
END $$;
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
The same applies to `down_statement`, which is likelier to be run against a
|
|
105
|
+
database that is not in the state you assumed.
|
|
106
|
+
|
|
107
|
+
### The table names are the platform's, not yours
|
|
108
|
+
|
|
109
|
+
A generic table you declared as `contact_submissions` is physically
|
|
110
|
+
`alvera_custom_contact_submissions`. Read the name back from the created table
|
|
111
|
+
rather than composing it — see `generic_tables.md` — because your migration
|
|
112
|
+
statement has to name the physical table, and getting it wrong is a runtime
|
|
113
|
+
failure inside DDL rather than a validation error.
|
|
114
|
+
|
|
115
|
+
### Ordering is real, and it is the version's job
|
|
116
|
+
|
|
117
|
+
Adding an index and then a foreign key that depends on it are two migrations,
|
|
118
|
+
and the sequence decides which runs first. Two migrations created in one session
|
|
119
|
+
run in creation order because the sequence is monotonic — but if you need B
|
|
120
|
+
after A, that is a fact about your two `version` values, not about how you
|
|
121
|
+
listed them.
|
|
122
|
+
|
|
123
|
+
## 3. Field ownership
|
|
124
|
+
|
|
125
|
+
**Caller-authored (Request).** `name`, `up_statement`, `down_statement`. That is
|
|
126
|
+
the whole writable surface.
|
|
127
|
+
|
|
128
|
+
**Server-derived (Response-only).** `id`, `datalake_id`, `version`, `checksum`,
|
|
129
|
+
`inserted_at`, `updated_at`. None may be sent; see `type_naming.md` for why the
|
|
130
|
+
Writable types exclude them.
|
|
131
|
+
|
|
132
|
+
`name` is unique per datalake, so two lakes may each carry a
|
|
133
|
+
`AddEmailIndex` and neither collides.
|
|
134
|
+
|
|
135
|
+
## 4. Error envelopes
|
|
136
|
+
|
|
137
|
+
Standard JSON:API envelope throughout — see `errors.md`.
|
|
138
|
+
|
|
139
|
+
| Status | When |
|
|
140
|
+
|---|---|
|
|
141
|
+
| 422 | a required field is missing, or `name` collides within this datalake |
|
|
142
|
+
| 409 | the migration has already been applied — `update` and `delete` both |
|
|
143
|
+
| 404 | the id belongs to a different datalake (deliberately not "403": its existence is not leaked) |
|
|
144
|
+
|
|
145
|
+
A 409 here is not a retryable condition. It is telling you the migration is
|
|
146
|
+
history and you want a new one.
|
|
147
|
+
|
|
148
|
+
## 5. Lifecycle
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
create → the row exists; nothing has run yet
|
|
152
|
+
migrate → the datalake's migrator runs it, in version order
|
|
153
|
+
(POST /datalakes/{slug}/migrate — asynchronous, 202)
|
|
154
|
+
applied → a run log row: direction up, status success
|
|
155
|
+
from here: update 409, delete 409
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Creating a migration does **not** run it. The datalake's migrate call does, and
|
|
159
|
+
it is asynchronous — it answers 202 as soon as the job is enqueued. Poll the
|
|
160
|
+
datalake for `status: ready` the way you would after creating one; see
|
|
161
|
+
`datalakes.md`.
|
|
162
|
+
|
|
163
|
+
## 6. Gotchas
|
|
164
|
+
|
|
165
|
+
**A created migration that never ran is invisible in the database.** The row
|
|
166
|
+
exists, `list()` returns it, and nothing has happened to any table. Reading the
|
|
167
|
+
catalog is the only way to know whether it took effect — the migration record
|
|
168
|
+
says what you asked for, not what is true.
|
|
169
|
+
|
|
170
|
+
**Proving one worked means asking the database, not the API.** The response
|
|
171
|
+
carries `checksum`, `datalake_id`, `down_statement`, `id`, `inserted_at`,
|
|
172
|
+
`name`, `up_statement`, `updated_at` and `version` — no status, no applied
|
|
173
|
+
flag, and `version` is execution ORDER rather than proof of execution. So the
|
|
174
|
+
honest check is a query against the catalog for the object the statement was
|
|
175
|
+
supposed to create:
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
// an index
|
|
179
|
+
const { data: indexRows } = await api.datalakes.executeSql(tenantSlug, datalakeSlug, {
|
|
180
|
+
sql: `select 1 from pg_indexes
|
|
181
|
+
where tablename = 'alvera_custom_contact_submissions'
|
|
182
|
+
and indexname = 'idx_contact_submissions_email'`,
|
|
183
|
+
mode: 'raw',
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
// a foreign key
|
|
187
|
+
const { data: fkRows } = await api.datalakes.executeSql(tenantSlug, datalakeSlug, {
|
|
188
|
+
sql: `select 1 from information_schema.table_constraints
|
|
189
|
+
where constraint_type = 'FOREIGN KEY'
|
|
190
|
+
and table_name = 'alvera_custom_contact_submissions'
|
|
191
|
+
and constraint_name = 'fk_submissions_lead'`,
|
|
192
|
+
mode: 'raw',
|
|
193
|
+
})
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
**`down_statement` is required and is rarely exercised.** Nothing makes you
|
|
197
|
+
prove it works, and it is the statement you will want on your worst day. Write
|
|
198
|
+
it as carefully as the `up`.
|
|
199
|
+
|
|
200
|
+
**A migration belongs to exactly one datalake.** There is no shared or
|
|
201
|
+
tenant-level migration. Three lakes needing the same index need three
|
|
202
|
+
migrations — which is also why `name` only has to be unique per lake.
|
package/.agent/datalakes.md
CHANGED
|
@@ -261,6 +261,75 @@ The tier is chosen per call, not per datalake — `mode` on
|
|
|
261
261
|
`executeSql`, `dataAccessMode` on a dataset search. Nothing about
|
|
262
262
|
the datalake decides it.
|
|
263
263
|
|
|
264
|
+
### How the copies get turned on
|
|
265
|
+
|
|
266
|
+
Nothing in this SDK turns them on directly, and that is worth stating
|
|
267
|
+
before the mechanism, because the field involved is one you cannot send.
|
|
268
|
+
|
|
269
|
+
**Through the `alvera` CLI it is one boolean on the datalake block:**
|
|
270
|
+
|
|
271
|
+
```toml
|
|
272
|
+
[datalakes.my-lake]
|
|
273
|
+
enable_tokenization = true
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
`enable_tokenization` is a **CLI-only manifest key**. The CLI strips it
|
|
277
|
+
before the request reaches the API — there is no such field on
|
|
278
|
+
`DatalakeRequest`, and adding one to an SDK call does nothing. What it
|
|
279
|
+
drives is a separate endpoint, `POST /datalakes/<slug>/tokenization`,
|
|
280
|
+
which the CLI calls on your behalf after the raw lake reaches `ready`.
|
|
281
|
+
Calling that endpoint yourself is the SDK-level equivalent.
|
|
282
|
+
|
|
283
|
+
Four things about it that are not guessable from the field:
|
|
284
|
+
|
|
285
|
+
**Both copies or neither.** They are created, destroyed and billed
|
|
286
|
+
together. There is no way to ask for a tokenized lake without a redacted
|
|
287
|
+
one.
|
|
288
|
+
|
|
289
|
+
**Order is handled for you, and it matters.** A derived lake's own
|
|
290
|
+
migration creates its tables and starts the copy, so the raw lake must be
|
|
291
|
+
`ready` first. `apply` turns the copies on after that, then waits for
|
|
292
|
+
both to reach `ready` themselves.
|
|
293
|
+
|
|
294
|
+
**It only goes one way.** Nothing turns tokenization off — no endpoint
|
|
295
|
+
deletes a derived lake — which is why the platform declined to make it an
|
|
296
|
+
authored field in the first place: `true → false` would let a caller
|
|
297
|
+
declare a state the platform cannot honour.
|
|
298
|
+
|
|
299
|
+
**`[tokenization.<slug>]` is retired.** If you meet a manifest carrying
|
|
300
|
+
that block, it is pre-0.18 and the CLI now refuses it by name.
|
|
301
|
+
|
|
302
|
+
### What turning them on actually requires
|
|
303
|
+
|
|
304
|
+
One boolean, three prerequisites — and the platform **probes** each and
|
|
305
|
+
**refuses** when it is missing. It never issues `CREATE DATABASE` or
|
|
306
|
+
`CreateBucket`, deliberately: a platform that can create one can create
|
|
307
|
+
it in the wrong place, and a datalake's storage belongs to the operator.
|
|
308
|
+
|
|
309
|
+
**1 · Two more databases.** Named from the raw lake's own database name
|
|
310
|
+
with the mode appended — `<raw>_tokenized` and `<raw>_redacted`. Postgres
|
|
311
|
+
truncates an over-long identifier *silently*, which would land two
|
|
312
|
+
derived lakes on one physical database, so a name that would exceed the
|
|
313
|
+
63-byte identifier limit is refused rather than truncated. Long raw
|
|
314
|
+
database names are where that bites.
|
|
315
|
+
|
|
316
|
+
**2 · On two more Postgres servers.** A derived lake does not live beside
|
|
317
|
+
the raw one. Locally the tokenized copy is on `:5433` and the redacted on
|
|
318
|
+
`:5434`, beside the raw lake on `:5432`. Separate servers on purpose: a
|
|
319
|
+
derived lake runs `postgresql_anonymizer` and PL/Python, which the raw
|
|
320
|
+
lake — holding the real values — must not have.
|
|
321
|
+
|
|
322
|
+
**3 · Three buckets, not one.** The raw lake's bucket plus
|
|
323
|
+
`<bucket>-tokenized` and `<bucket>-redacted`.
|
|
324
|
+
|
|
325
|
+
**The failure mode is worth knowing in advance, because it is
|
|
326
|
+
mis-attributed by default.** A missing copy bucket is reported against
|
|
327
|
+
the **primary** lake, naming the lake rather than the bucket that is
|
|
328
|
+
actually absent — and it surfaces minutes into a create, as a 422 that
|
|
329
|
+
reads like a platform defect. It has been diagnosed as one. If a create
|
|
330
|
+
fails on cloud storage for a lake declaring the copies, check that all
|
|
331
|
+
three buckets exist before looking anywhere else.
|
|
332
|
+
|
|
264
333
|
### What decides what a reader sees
|
|
265
334
|
|
|
266
335
|
**`privacy_requirement` on the column.** That single declaration is
|
package/.agent/generic_tables.md
CHANGED
|
@@ -57,6 +57,17 @@ const { data: created } = await api.genericTables.create(
|
|
|
57
57
|
is_unique: true,
|
|
58
58
|
privacy_requirement: 'none',
|
|
59
59
|
},
|
|
60
|
+
{
|
|
61
|
+
name: 'email',
|
|
62
|
+
title: 'Email',
|
|
63
|
+
type: 'string',
|
|
64
|
+
description: 'Submitter email',
|
|
65
|
+
is_unique: false,
|
|
66
|
+
// The masking pair: WHETHER, and WHAT it is so the right
|
|
67
|
+
// function masks it. See "a pair" below.
|
|
68
|
+
privacy_requirement: 'tokenize',
|
|
69
|
+
semantic_kind: 'email',
|
|
70
|
+
},
|
|
60
71
|
// ... more columns
|
|
61
72
|
],
|
|
62
73
|
},
|
|
@@ -122,8 +133,29 @@ is_array optional bool — column holds an array of `type` values
|
|
|
122
133
|
description required string — narrative; surfaces in catalogs
|
|
123
134
|
is_unique required bool — Postgres UNIQUE constraint at migration time
|
|
124
135
|
privacy_requirement required enum — 'none' | 'tokenize' | 'redact'
|
|
136
|
+
semantic_kind optional enum — 'string' | 'text' | 'email' | 'phone'
|
|
137
|
+
| 'date' | 'json' | 'image' | 'pdf'
|
|
138
|
+
| 'attachment'; nullable
|
|
125
139
|
```
|
|
126
140
|
|
|
141
|
+
#### `privacy_requirement` and `semantic_kind` are a pair
|
|
142
|
+
|
|
143
|
+
They answer two different questions and a masked column needs both.
|
|
144
|
+
|
|
145
|
+
**`privacy_requirement` says WHETHER the column is masked** in the derived
|
|
146
|
+
lakes — see the table in §2 below. Immutable once the column exists.
|
|
147
|
+
|
|
148
|
+
**`semantic_kind` says WHAT the value is, which decides WHICH function
|
|
149
|
+
masks it.** A tokenized email should come back looking like an email; a
|
|
150
|
+
tokenized phone like a phone. Without one, the platform can only treat
|
|
151
|
+
the value as free text, so **a masked column with no `semantic_kind` gets
|
|
152
|
+
a worse mask than it should** — still masked, but less useful to
|
|
153
|
+
everything downstream that expected the shape to survive.
|
|
154
|
+
|
|
155
|
+
It is optional, and nothing fails without it, which is exactly why it is
|
|
156
|
+
easy to miss. **Set it on every column you mark `tokenize` or `redact`.**
|
|
157
|
+
On a `none` column it is documentation and costs nothing.
|
|
158
|
+
|
|
127
159
|
#### Column types
|
|
128
160
|
|
|
129
161
|
```
|
package/.agent/mock-services.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
How the platform reaches mocked third-party APIs, on a laptop and from deployed
|
|
6
6
|
environments. Two mock surfaces exist and they answer different questions:
|
|
7
|
-
**WireMock** stands in for third-party SaaS APIs, **
|
|
7
|
+
**WireMock** stands in for third-party SaaS APIs, **MiniStack** stands in for AWS.
|
|
8
8
|
|
|
9
9
|
## The two surfaces
|
|
10
10
|
|
|
@@ -12,14 +12,15 @@ environments. Two mock surfaces exist and they answer different questions:
|
|
|
12
12
|
`wm_mappings/`, with response bodies in `wm_response_files/`. It covers the
|
|
13
13
|
third-party APIs the platform integrates with — Athenahealth, Twilio, Mailgun,
|
|
14
14
|
Stripe, SendGrid, HubSpot, QuickBooks, Airtable, Atomic FI, the LLM providers,
|
|
15
|
-
and a small set of AWS endpoints
|
|
15
|
+
and a small set of AWS endpoints MiniStack cannot serve.
|
|
16
16
|
|
|
17
|
-
**
|
|
17
|
+
**MiniStack** serves AWS itself: `s3,lambda,sns,ssm,sqs,sts,logs,secretsmanager,cloudformation`
|
|
18
18
|
(`local-dependencies.yml`). See [Datalakes](datalakes.md) for how datalake
|
|
19
19
|
provisioning uses it.
|
|
20
20
|
|
|
21
|
-
The division is not arbitrary.
|
|
22
|
-
`pinpoint-sms-voice-v2`
|
|
21
|
+
The division is not arbitrary. No AWS emulator here serves
|
|
22
|
+
`pinpoint-sms-voice-v2` — LocalStack Community answered 501 (Ultimate tier
|
|
23
|
+
only) and MiniStack has no such service at all — so End User Messaging points
|
|
23
24
|
at WireMock for its send, its S3 media staging, and its STS credential probe. A
|
|
24
25
|
tool has one `endpoint_url`, so all three land on the same host.
|
|
25
26
|
|
|
@@ -62,11 +63,11 @@ the hosted instance — see below.
|
|
|
62
63
|
|
|
63
64
|
## Running locally
|
|
64
65
|
|
|
65
|
-
`make run-backing-services` starts both. WireMock listens on `:8080`,
|
|
66
|
+
`make run-backing-services` starts both. WireMock listens on `:8080`, MiniStack
|
|
66
67
|
on `:4566`. Tool bodies reach them by setting `endpoint_url` — blank means real
|
|
67
68
|
AWS, any value replaces host, port and scheme.
|
|
68
69
|
|
|
69
|
-
|
|
70
|
+
MiniStack's S3 state does **not** survive a container restart. Recreate a bucket
|
|
70
71
|
after one:
|
|
71
72
|
|
|
72
73
|
```bash
|
|
@@ -176,10 +177,10 @@ identical POSTs return three *different* bodies in a fixed order. Matching hashe
|
|
|
176
177
|
across three successive calls proves both environments walked the same state
|
|
177
178
|
machine — something no stateless matcher could fake.
|
|
178
179
|
|
|
179
|
-
##
|
|
180
|
+
## MiniStack at the edge
|
|
180
181
|
|
|
181
|
-
Not done, and deliberately scoped apart.
|
|
182
|
-
rides the same mechanism, but Containers **sleep by design** and
|
|
182
|
+
Not done, and deliberately scoped apart. MiniStack is also just an image and
|
|
183
|
+
rides the same mechanism, but Containers **sleep by design** and MiniStack's S3
|
|
183
184
|
state already does not survive a restart. What is an occasional annoyance locally
|
|
184
185
|
becomes routine when instances sleep between runs. Anything depending on objects
|
|
185
186
|
persisting must move to R2, which speaks S3 natively — migration work, not a flag
|
|
@@ -188,5 +189,5 @@ flip.
|
|
|
188
189
|
## Related
|
|
189
190
|
|
|
190
191
|
- [Tools](tools.md) — tool bodies, `endpoint_url`, and REST API auth methods
|
|
191
|
-
- [Datalakes](datalakes.md) — how datalake provisioning uses
|
|
192
|
+
- [Datalakes](datalakes.md) — how datalake provisioning uses MiniStack
|
|
192
193
|
- [Connected Apps](connected_apps.md) — the third-party integrations the corpus stands in for
|
package/dist/index.d.mts
CHANGED
|
@@ -127,6 +127,18 @@ type ActionExecutionLogResponse = {
|
|
|
127
127
|
* Creation timestamp
|
|
128
128
|
*/
|
|
129
129
|
inserted_at?: string;
|
|
130
|
+
/**
|
|
131
|
+
* How many times the job has been attempted. `0` means it has not run yet.
|
|
132
|
+
*/
|
|
133
|
+
readonly job_attempt?: number | null;
|
|
134
|
+
/**
|
|
135
|
+
* When the job will next be attempted. Equal to `scheduled_at` until something moves it; a snoozing job carries the instant it wakes.
|
|
136
|
+
*/
|
|
137
|
+
readonly job_next_attempt_at?: string | null;
|
|
138
|
+
/**
|
|
139
|
+
* State of the job carrying this action — one of `available`, `scheduled`, `executing`, `retryable`, `completed`, `discarded`, `cancelled`. Null when no job carries it.
|
|
140
|
+
*/
|
|
141
|
+
readonly job_state?: string | null;
|
|
130
142
|
/**
|
|
131
143
|
* Rendered message body for this action (mode-driven; null when no message was sent)
|
|
132
144
|
*/
|
|
@@ -441,7 +453,7 @@ type CloudWatchLogGroupResponse = {
|
|
|
441
453
|
auth_method: 'access_key' | 'iam_role' | 'assume_role';
|
|
442
454
|
base_filter_pattern?: ComplexTemplateConfigResponse | null;
|
|
443
455
|
/**
|
|
444
|
-
* Custom CloudWatch Logs endpoint URL (e.g., http://localhost:4566 for
|
|
456
|
+
* Custom CloudWatch Logs endpoint URL (e.g., http://localhost:4566 for MiniStack)
|
|
445
457
|
*/
|
|
446
458
|
endpoint_url?: string | null;
|
|
447
459
|
/**
|
|
@@ -878,7 +890,7 @@ type ExecuteActionRequest = {
|
|
|
878
890
|
*/
|
|
879
891
|
mode?: 'live' | 'dry_run';
|
|
880
892
|
/**
|
|
881
|
-
* When true, the action's `trigger_template` schedule is bypassed and the queued job is dispatched immediately. The ActionExecutionLog still records the trigger-rendered `scheduled_at` — only the Oban job is fast-forwarded. Use this to force a future
|
|
893
|
+
* When true, the action's `trigger_template` schedule is bypassed and the queued job is dispatched immediately. The ActionExecutionLog still records the trigger-rendered `scheduled_at` — only the Oban job is fast-forwarded. Use this to force an action whose trigger renders a future instant (e.g. a year-roll birthday SMS) to fire now, which is the only way to drive such a workflow to completion inside a live test or cookbook run. An action whose trigger renders a past instant does not need it: a past `scheduled_at` is staged on the next pass and runs on its own.
|
|
882
894
|
*/
|
|
883
895
|
trigger_override?: boolean;
|
|
884
896
|
};
|
|
@@ -2568,17 +2580,17 @@ type DatalakeResponse = {
|
|
|
2568
2580
|
*/
|
|
2569
2581
|
db_reader_enable_ssl: boolean;
|
|
2570
2582
|
/**
|
|
2571
|
-
* Reader DB host
|
|
2583
|
+
* Reader DB host. Null while a derived lake's database is still being provisioned
|
|
2572
2584
|
*/
|
|
2573
|
-
db_reader_host: string;
|
|
2585
|
+
db_reader_host: string | null;
|
|
2574
2586
|
/**
|
|
2575
2587
|
* Reader DB name
|
|
2576
2588
|
*/
|
|
2577
2589
|
db_reader_name: string;
|
|
2578
2590
|
/**
|
|
2579
|
-
* Reader DB port
|
|
2591
|
+
* Reader DB port. Null while a derived lake's database is still being provisioned
|
|
2580
2592
|
*/
|
|
2581
|
-
db_reader_port: number;
|
|
2593
|
+
db_reader_port: number | null;
|
|
2582
2594
|
/**
|
|
2583
2595
|
* Reader DB schema name
|
|
2584
2596
|
*/
|
|
@@ -2592,17 +2604,17 @@ type DatalakeResponse = {
|
|
|
2592
2604
|
*/
|
|
2593
2605
|
db_writer_enable_ssl: boolean;
|
|
2594
2606
|
/**
|
|
2595
|
-
* Writer DB host
|
|
2607
|
+
* Writer DB host. Null while a derived lake's database is still being provisioned
|
|
2596
2608
|
*/
|
|
2597
|
-
db_writer_host: string;
|
|
2609
|
+
db_writer_host: string | null;
|
|
2598
2610
|
/**
|
|
2599
2611
|
* Writer DB name
|
|
2600
2612
|
*/
|
|
2601
2613
|
db_writer_name: string;
|
|
2602
2614
|
/**
|
|
2603
|
-
* Writer DB port
|
|
2615
|
+
* Writer DB port. Null while a derived lake's database is still being provisioned
|
|
2604
2616
|
*/
|
|
2605
|
-
db_writer_port: number;
|
|
2617
|
+
db_writer_port: number | null;
|
|
2606
2618
|
/**
|
|
2607
2619
|
* Writer DB schema name
|
|
2608
2620
|
*/
|
|
@@ -2642,7 +2654,7 @@ type DatalakeResponse = {
|
|
|
2642
2654
|
/**
|
|
2643
2655
|
* Datalake setup status
|
|
2644
2656
|
*/
|
|
2645
|
-
readonly status?: 'new' | 'processing' | 'ready';
|
|
2657
|
+
readonly status?: 'provisioning' | 'new' | 'processing' | 'ready';
|
|
2646
2658
|
tenant?: TenantResponse;
|
|
2647
2659
|
/**
|
|
2648
2660
|
* Datalake reporting timezone. Closed whitelist of 8 US timezones — general IANA values (including `UTC`) are rejected.
|
|
@@ -2685,6 +2697,10 @@ type AlveraApiError = {
|
|
|
2685
2697
|
*/
|
|
2686
2698
|
title: string;
|
|
2687
2699
|
}>;
|
|
2700
|
+
/**
|
|
2701
|
+
* Which derived copy failed to provision — `tokenized` or `redacted`. `null` on every other structured error, which is why it is not required. Without it a client is told a copy of the pair failed but not which one, and a copy is repaired one mode at a time.
|
|
2702
|
+
*/
|
|
2703
|
+
mode?: string | null;
|
|
2688
2704
|
};
|
|
2689
2705
|
/**
|
|
2690
2706
|
* CloudStorageAwsResponse
|
|
@@ -3216,7 +3232,7 @@ type SnsResponse = {
|
|
|
3216
3232
|
auth_method: 'access_key' | 'iam_role' | 'assume_role';
|
|
3217
3233
|
base_message?: ComplexTemplateConfigResponse | null;
|
|
3218
3234
|
/**
|
|
3219
|
-
* Custom SNS endpoint URL (e.g., http://localhost:4566 for
|
|
3235
|
+
* Custom SNS endpoint URL (e.g., http://localhost:4566 for MiniStack); leave blank for real AWS
|
|
3220
3236
|
*/
|
|
3221
3237
|
endpoint_url?: string | null;
|
|
3222
3238
|
/**
|
|
@@ -3561,12 +3577,16 @@ type WorkflowLogResponse = {
|
|
|
3561
3577
|
* Completed action count
|
|
3562
3578
|
*/
|
|
3563
3579
|
actions_completed?: number;
|
|
3580
|
+
/**
|
|
3581
|
+
* Actions that have started and not finished. A run showing `actions_pending: 1, actions_executing: 0` is waiting to be picked up; one showing `actions_pending: 1, actions_executing: 1` is mid-flight and may be stuck.
|
|
3582
|
+
*/
|
|
3583
|
+
actions_executing?: number;
|
|
3564
3584
|
/**
|
|
3565
3585
|
* Failed action count
|
|
3566
3586
|
*/
|
|
3567
3587
|
actions_failed?: number;
|
|
3568
3588
|
/**
|
|
3569
|
-
*
|
|
3589
|
+
* Unfinished action count — actions still queued plus actions already running. `actions_executing` is the running share of it.
|
|
3570
3590
|
*/
|
|
3571
3591
|
actions_pending?: number;
|
|
3572
3592
|
/**
|
|
@@ -3930,6 +3950,14 @@ type TwilioRequest = {
|
|
|
3930
3950
|
* Twilio Account SID (required on a primary; supplied by the primary on a variant)
|
|
3931
3951
|
*/
|
|
3932
3952
|
account_sid?: string | null;
|
|
3953
|
+
/**
|
|
3954
|
+
* Twilio API Key SID (required when auth_method is api_key)
|
|
3955
|
+
*/
|
|
3956
|
+
api_key_sid?: string | null;
|
|
3957
|
+
/**
|
|
3958
|
+
* How this primary authenticates to Twilio. Defaults to auth_token.
|
|
3959
|
+
*/
|
|
3960
|
+
auth_method?: 'auth_token' | 'api_key';
|
|
3933
3961
|
base_message?: ComplexTemplateConfigRequest;
|
|
3934
3962
|
/**
|
|
3935
3963
|
* Custom Twilio API base URL (e.g., http://localhost:8080 for WireMock); leave blank for https://api.twilio.com
|
|
@@ -4589,6 +4617,14 @@ type TwilioResponse = {
|
|
|
4589
4617
|
* Twilio Account SID (required on a primary; supplied by the primary on a variant)
|
|
4590
4618
|
*/
|
|
4591
4619
|
account_sid?: string | null;
|
|
4620
|
+
/**
|
|
4621
|
+
* Twilio API Key SID (required when auth_method is api_key)
|
|
4622
|
+
*/
|
|
4623
|
+
api_key_sid?: string | null;
|
|
4624
|
+
/**
|
|
4625
|
+
* How this primary authenticates to Twilio. Defaults to auth_token.
|
|
4626
|
+
*/
|
|
4627
|
+
auth_method?: 'auth_token' | 'api_key';
|
|
4592
4628
|
base_message?: ComplexTemplateConfigResponse | null;
|
|
4593
4629
|
/**
|
|
4594
4630
|
* Custom Twilio API base URL (e.g., http://localhost:8080 for WireMock); leave blank for https://api.twilio.com
|
|
@@ -4643,9 +4679,9 @@ type DatalakeRequestWritable = {
|
|
|
4643
4679
|
*/
|
|
4644
4680
|
db_reader_enable_ssl: boolean;
|
|
4645
4681
|
/**
|
|
4646
|
-
* Reader DB host
|
|
4682
|
+
* Reader DB host. Null while a derived lake's database is still being provisioned
|
|
4647
4683
|
*/
|
|
4648
|
-
db_reader_host: string;
|
|
4684
|
+
db_reader_host: string | null;
|
|
4649
4685
|
/**
|
|
4650
4686
|
* Reader DB name
|
|
4651
4687
|
*/
|
|
@@ -4655,9 +4691,9 @@ type DatalakeRequestWritable = {
|
|
|
4655
4691
|
*/
|
|
4656
4692
|
db_reader_pass: string;
|
|
4657
4693
|
/**
|
|
4658
|
-
* Reader DB port
|
|
4694
|
+
* Reader DB port. Null while a derived lake's database is still being provisioned
|
|
4659
4695
|
*/
|
|
4660
|
-
db_reader_port: number;
|
|
4696
|
+
db_reader_port: number | null;
|
|
4661
4697
|
/**
|
|
4662
4698
|
* Reader DB schema name
|
|
4663
4699
|
*/
|
|
@@ -4675,9 +4711,9 @@ type DatalakeRequestWritable = {
|
|
|
4675
4711
|
*/
|
|
4676
4712
|
db_writer_enable_ssl: boolean;
|
|
4677
4713
|
/**
|
|
4678
|
-
* Writer DB host
|
|
4714
|
+
* Writer DB host. Null while a derived lake's database is still being provisioned
|
|
4679
4715
|
*/
|
|
4680
|
-
db_writer_host: string;
|
|
4716
|
+
db_writer_host: string | null;
|
|
4681
4717
|
/**
|
|
4682
4718
|
* Writer DB name
|
|
4683
4719
|
*/
|
|
@@ -4687,9 +4723,9 @@ type DatalakeRequestWritable = {
|
|
|
4687
4723
|
*/
|
|
4688
4724
|
db_writer_pass: string;
|
|
4689
4725
|
/**
|
|
4690
|
-
* Writer DB port
|
|
4726
|
+
* Writer DB port. Null while a derived lake's database is still being provisioned
|
|
4691
4727
|
*/
|
|
4692
|
-
db_writer_port: number;
|
|
4728
|
+
db_writer_port: number | null;
|
|
4693
4729
|
/**
|
|
4694
4730
|
* Writer DB schema name
|
|
4695
4731
|
*/
|
|
@@ -5485,7 +5521,7 @@ type CloudWatchLogGroupRequestWritable = {
|
|
|
5485
5521
|
auth_method: 'access_key' | 'iam_role' | 'assume_role';
|
|
5486
5522
|
base_filter_pattern?: ComplexTemplateConfigRequest;
|
|
5487
5523
|
/**
|
|
5488
|
-
* Custom CloudWatch Logs endpoint URL (e.g., http://localhost:4566 for
|
|
5524
|
+
* Custom CloudWatch Logs endpoint URL (e.g., http://localhost:4566 for MiniStack)
|
|
5489
5525
|
*/
|
|
5490
5526
|
endpoint_url?: string | null;
|
|
5491
5527
|
/**
|
|
@@ -5521,7 +5557,7 @@ type SnsRequestWritable = {
|
|
|
5521
5557
|
auth_method: 'access_key' | 'iam_role' | 'assume_role';
|
|
5522
5558
|
base_message?: ComplexTemplateConfigRequest;
|
|
5523
5559
|
/**
|
|
5524
|
-
* Custom SNS endpoint URL (e.g., http://localhost:4566 for
|
|
5560
|
+
* Custom SNS endpoint URL (e.g., http://localhost:4566 for MiniStack); leave blank for real AWS
|
|
5525
5561
|
*/
|
|
5526
5562
|
endpoint_url?: string | null;
|
|
5527
5563
|
/**
|
|
@@ -6000,6 +6036,18 @@ type TwilioRequestWritable = {
|
|
|
6000
6036
|
* Twilio Account SID (required on a primary; supplied by the primary on a variant)
|
|
6001
6037
|
*/
|
|
6002
6038
|
account_sid?: string | null;
|
|
6039
|
+
/**
|
|
6040
|
+
* Twilio API Key Secret (required when auth_method is api_key)
|
|
6041
|
+
*/
|
|
6042
|
+
api_key_secret?: string | null;
|
|
6043
|
+
/**
|
|
6044
|
+
* Twilio API Key SID (required when auth_method is api_key)
|
|
6045
|
+
*/
|
|
6046
|
+
api_key_sid?: string | null;
|
|
6047
|
+
/**
|
|
6048
|
+
* How this primary authenticates to Twilio. Defaults to auth_token.
|
|
6049
|
+
*/
|
|
6050
|
+
auth_method?: 'auth_token' | 'api_key';
|
|
6003
6051
|
/**
|
|
6004
6052
|
* Twilio Auth Token (required on a primary; supplied by the primary on a variant)
|
|
6005
6053
|
*/
|