@alvera-ai/platform-sdk 0.10.0-rc.2 → 0.10.0-rc.21

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.
Files changed (55) hide show
  1. package/.agent/AGENTS.md +440 -0
  2. package/.agent/account_management.md +455 -0
  3. package/.agent/action_status_updaters.md +262 -0
  4. package/.agent/ai_agents.md +423 -0
  5. package/.agent/ai_sandbox.md +265 -0
  6. package/.agent/async.md +111 -0
  7. package/.agent/connected_apps.md +407 -0
  8. package/.agent/cookbook/_fixtures/README.md +99 -0
  9. package/.agent/cookbook/_fixtures/accounts_receivable/_customers_accounts_receivable_customer.liquid +32 -0
  10. package/.agent/cookbook/_fixtures/accounts_receivable/_customers_accounts_receivable_mdm.liquid +20 -0
  11. package/.agent/cookbook/_fixtures/foundation/_lead_submissions_foundation_generic_table.liquid +33 -0
  12. package/.agent/cookbook/_fixtures/foundation/_lead_submissions_foundation_legal_entity.liquid +88 -0
  13. package/.agent/cookbook/_fixtures/foundation/_lead_submissions_foundation_mdm.liquid +48 -0
  14. package/.agent/cookbook/_fixtures/healthcare/_cahps_appointments_healthcare_appointment.liquid +47 -0
  15. package/.agent/cookbook/_fixtures/healthcare/_cahps_appointments_healthcare_mdm.liquid +24 -0
  16. package/.agent/cookbook/_fixtures/healthcare/_cahps_appointments_healthcare_patient.liquid +38 -0
  17. package/.agent/cookbook/_fixtures/payment_risk/_compliance_screenings_payment_risk_compliance_screening.liquid +59 -0
  18. package/.agent/cookbook/_fixtures/payment_risk/_compliance_screenings_payment_risk_mdm.liquid +36 -0
  19. package/.agent/cookbook/_fixtures/payment_risk/_payment_accounts_payment_risk_mdm.liquid +30 -0
  20. package/.agent/cookbook/_fixtures/payment_risk/_payment_accounts_payment_risk_payment_account.liquid +55 -0
  21. package/.agent/cookbook/_setup/accounts_receivable.md +282 -0
  22. package/.agent/cookbook/_setup/foundation.md +277 -0
  23. package/.agent/cookbook/_setup/healthcare.md +279 -0
  24. package/.agent/cookbook/_setup/payment_risk.md +283 -0
  25. package/.agent/cookbook/appointment-review-sms-workflow.md +761 -0
  26. package/.agent/cookbook/birthday-greeting-sms-trigger.md +656 -0
  27. package/.agent/cookbook/contact-us-triage-with-llm.md +603 -0
  28. package/.agent/cookbook/dunning-sms-for-delinquent.md +619 -0
  29. package/.agent/cookbook/kyc-notification-on-account-activation.md +619 -0
  30. package/.agent/cookbook/sanctions-screening-with-agent-review.md +711 -0
  31. package/.agent/cookbook/score-leads-with-llm-categorization.md +602 -0
  32. package/.agent/cookbook/welcome-sms-for-customers.md +607 -0
  33. package/.agent/data_activation_clients.md +557 -0
  34. package/.agent/data_sources.md +234 -0
  35. package/.agent/datalakes.md +712 -0
  36. package/.agent/debugging.md +137 -0
  37. package/.agent/errors.md +196 -0
  38. package/.agent/generic_tables.md +351 -0
  39. package/.agent/interoperability_contracts.md +351 -0
  40. package/.agent/mdm.md +293 -0
  41. package/.agent/mutations.md +152 -0
  42. package/.agent/templates.md +98 -0
  43. package/.agent/tool-call-configs.md +90 -0
  44. package/.agent/tools.md +546 -0
  45. package/.agent/type_naming.md +131 -0
  46. package/.agent/workflows.md +601 -0
  47. package/README.md +46 -0
  48. package/dist/bin/platform-sdk.d.mts +1 -0
  49. package/dist/bin/platform-sdk.mjs +106 -0
  50. package/dist/bin/platform-sdk.mjs.map +1 -0
  51. package/dist/index.d.mts +1200 -43201
  52. package/dist/index.d.mts.map +1 -1
  53. package/dist/index.mjs +1859 -7319
  54. package/dist/index.mjs.map +1 -1
  55. package/package.json +19 -10
@@ -0,0 +1,546 @@
1
+ # `tools` — block authoring reference
2
+
3
+ Family: **INFRA** (anchor for `intent = data_exchange`) +
4
+ **WORKFLOWS** (tagged for `intent ∈ {sms, email, voice, export}`).
5
+
6
+ A tool is an authenticated connection to an external system that
7
+ the platform invokes to **execute actions**. Two orthogonal
8
+ dimensions:
9
+
10
+ - **intent** classifies what the tool DOES (move data, send a
11
+ message, render a report)
12
+ - **body type** declares HOW the tool connects (S3, SFTP, SQL,
13
+ REST, AWS Lambda, …) via a polymorphic discriminator
14
+
15
+ A datalake can own many tools. Some tools nest under a data source
16
+ (`data_source_id` populated — typical for ingestion-receiving
17
+ tools like `manual_upload`); others are standalone and reachable
18
+ to any workflow on the datalake.
19
+
20
+ ## 1. Wire shape
21
+
22
+ SDK TypeScript type (request shape; server-derived fields excluded):
23
+
24
+ ```
25
+ import type { ToolRequestWritable }
26
+ from '@alvera-ai/platform-sdk'
27
+ ```
28
+
29
+ No runtime validator is exported — validation is server-authoritative
30
+ (a bad body returns a 422 `AlveraApiError`; see `errors.md`).
31
+
32
+ Intent enum (typed):
33
+
34
+ ```
35
+ import { ToolIntent } from '@alvera-ai/platform-sdk'
36
+
37
+ ToolIntent.DATA_EXCHANGE // fetch / push payloads
38
+ ToolIntent.SMS // outbound text messages
39
+ ToolIntent.EMAIL // outbound email
40
+ ToolIntent.VOICE // outbound voice / voicemail
41
+ ToolIntent.EXPORT // report / extract generation
42
+ ToolIntent.STATUS_POLLER // polls external systems for
43
+ // delivery/action status; paired
44
+ // with action status updaters
45
+ // (see action_status_updaters.md)
46
+ ToolIntent.CHAT_COMPLETION // OpenAI-compatible chat-completion
47
+ // endpoint; paired with AI agents
48
+ // (see ai_agents.md)
49
+ ToolIntent.CONTEXT_EXTRACTION
50
+ // LLM-backed enrichment that extracts
51
+ // structured context from raw rows;
52
+ // paired with AI agents (see
53
+ // ai_agents.md). Like CHAT_COMPLETION,
54
+ // pairs with rest_api body against an
55
+ // OpenAI-compatible endpoint.
56
+ ```
57
+
58
+ Polymorphic body dispatch — the `body.tool_body_type` field is
59
+ the discriminator. Each branch is its own TypeScript type:
60
+
61
+ ```
62
+ tool_body_type = 'manual_upload' → manual-upload receiver
63
+ (presigned-URL upload path)
64
+ tool_body_type = 's3' → AWS S3 / R2 / custom-endpoint
65
+ object storage
66
+ tool_body_type = 'sftp' → SFTP file transfer
67
+ tool_body_type = 'sql_database' → SQL (Postgres, MySQL, …)
68
+ tool_body_type = 'rest_api' → generic REST endpoint
69
+ tool_body_type = 'sns' → AWS SNS topic (used for
70
+ SMS / push notifications)
71
+ tool_body_type = 'sharepoint' → Microsoft Graph SharePoint
72
+ tool_body_type = 'aws_lambda' → invoke a Lambda function
73
+ tool_body_type = 'sqs' → AWS SQS queue
74
+ tool_body_type = 'cloud_watch_log_group'
75
+ → AWS CloudWatch Logs poller
76
+ (paired with
77
+ ToolIntent.STATUS_POLLER)
78
+ tool_body_type = 'email' → dedicated email-provider body
79
+ (SendGrid, SES, SMTP-bridge)
80
+ — alternative to using
81
+ rest_api for email intents
82
+ ```
83
+
84
+ The authoritative enum lives in the generated `ToolBodyTypeEnum`
85
+ export from `@alvera-ai/platform-sdk` — consult the type for the
86
+ complete set, since new body types land independently of doc
87
+ revisions.
88
+
89
+ ## 2. Rules the type cannot encode
90
+
91
+ ### `intent` and `tool_body_type` are loosely coupled
92
+
93
+ The TypeScript type lets you combine any intent with any body
94
+ type, but the platform validates the pairing semantically at
95
+ create time:
96
+
97
+ ```
98
+ data_exchange → s3, sftp, sql_database, rest_api,
99
+ manual_upload, aws_lambda, sharepoint, sqs
100
+ sms → sns, rest_api (provider-specific)
101
+ email → email (dedicated body), rest_api,
102
+ aws_lambda (custom dispatchers)
103
+ voice → rest_api (Twilio Voice, etc.)
104
+ export → s3, aws_lambda
105
+ status_poller → cloud_watch_log_group, rest_api
106
+ llm_enrichment → rest_api (against an OpenAI-compatible LLM
107
+ endpoint; the platform appends
108
+ /chat/completions to base_url; the
109
+ tool is the complete LLM provider
110
+ adapter and AI agents bind it for
111
+ structured row enrichment)
112
+ ```
113
+
114
+ A misaligned pair (e.g. `intent: sms` + `tool_body_type: sftp`)
115
+ returns a `422` with `body.tool_body_type` in `errors`.
116
+
117
+ ### `data_source_id` is optional but discriminator-aware
118
+
119
+ When set, the tool is **embedded under a data source** — its
120
+ lifecycle is tied to the parent data source, and ingestion paths
121
+ expect it at that scope. When omitted, the tool is **standalone**
122
+ on the datalake — available to any workflow that names it.
123
+
124
+ The body type itself signals the typical pattern: `manual_upload`
125
+ is almost always embedded (the data source IS the upload sink);
126
+ `sns`, `rest_api`, `sql_database` are typically standalone.
127
+
128
+ ### Body-type sub-fields are validated against the matching discriminator branch (server-side)
129
+
130
+ Each branch has its own required-list. Common patterns:
131
+
132
+ ```
133
+ sns body region, auth_method, plus EITHER
134
+ (access_key_id + secret_access_key
135
+ + optional endpoint_url)
136
+ OR (iam_role)
137
+
138
+ sql_database body db_type, db_host, db_port, db_name,
139
+ auth_method, db_username,
140
+ db_password (or iam_role)
141
+
142
+ s3 body region, bucket, auth_method,
143
+ access_key_id, secret_access_key
144
+ (or iam_role), optional base_path
145
+
146
+ sftp body hostname, port, username, auth_method,
147
+ password (or private_key)
148
+
149
+ rest_api body base_url, auth_method (oauth2 /
150
+ oidc / api_key / basic), plus the
151
+ corresponding credential block
152
+ ```
153
+
154
+ The full per-branch shape lives in
155
+ `packages/sdk/src/generated/types.gen.ts` under the matching
156
+ `Tool<Type>BodyWritable` types.
157
+
158
+ ### `status` field gates whether the tool can be invoked
159
+
160
+ ```
161
+ active tool is invocable; test-invocation + workflow steps work
162
+ inactive tool exists but invocations short-circuit with
163
+ an explanatory error envelope
164
+ ```
165
+
166
+ Most callers set `status: 'active'` on create; flipping to
167
+ `inactive` is the soft-delete / pause pattern.
168
+
169
+ ### `aws_lambda` body has a second discriminator: `type`
170
+
171
+ Inside the `aws_lambda` body (selected by the outer
172
+ `tool_body_type: "aws_lambda"`), the wire carries a second
173
+ discriminator field literally named `type`:
174
+
175
+ ```
176
+ body.type = "managed" → the platform owns the function;
177
+ create + deploy + delete go through
178
+ the platform's CloudFormation stack
179
+ body.type = "external" → the function is operator-managed in
180
+ an AWS account; the platform only
181
+ invokes it
182
+ ```
183
+
184
+ The two paths require disjoint field sets:
185
+
186
+ ```
187
+ type: "managed"
188
+ required: ssm_config_key (string — names the config
189
+ partition in SSM Parameter Store
190
+ the function reads at runtime)
191
+ accepted: secrets[] (array of { key, value } — material
192
+ the function reads as runtime
193
+ secrets; values are sensitive,
194
+ treat as write-only on the manifest
195
+ side via `<%= name %>` per §6 gotcha 10)
196
+ env_vars[] (array of { key, value } — passed
197
+ to the function as plain environment
198
+ variables; non-sensitive)
199
+ server-set: function_arn (populated after deploy succeeds)
200
+ stack_id, stack_name, stack_status
201
+ (CloudFormation tracking; see
202
+ §2 "aws_lambda tools have an
203
+ async deployment lifecycle")
204
+
205
+ type: "external"
206
+ required: function_arn (the existing AWS function to invoke)
207
+ auth_method ("access_key" or "iam_role")
208
+ access_key: access_key_id required;
209
+ secret_access_key write-only at create
210
+ iam_role: no AWS credentials required; the platform
211
+ assumes the role at invocation time
212
+ ```
213
+
214
+ ### `aws_lambda` tools have an async deployment lifecycle
215
+
216
+ For `type: "managed"`, creating the tool registers the row but
217
+ DOES NOT deploy. An out-of-band deployment step packages and
218
+ uploads the function; poll the tool's `deployment_status` field:
219
+
220
+ ```
221
+ pending POST returned; deployment job enqueued
222
+ deploying code being uploaded to AWS
223
+ deployed function is invocable; testInvocation will route to it
224
+ failed deployment error; check `deployment_error` field
225
+ ```
226
+
227
+ Two consumer-relevant consequences:
228
+
229
+ - **Managed tools cannot be created with `status: "active"`.**
230
+ The platform rejects with `/status: "cannot be set to active
231
+ until Lambda deployment is complete"`. Create with
232
+ `status: "draft"` (the typical pattern for managed lambdas),
233
+ trigger deployment, poll until `deployment_status: "deployed"`,
234
+ then PUT to flip `status` to `"active"`.
235
+ - **`function_arn` is server-populated for managed tools.** Do
236
+ not supply it on POST/PUT for `type: "managed"` — the platform
237
+ writes it after the CloudFormation stack reaches a
238
+ create-complete state.
239
+
240
+ For `type: "external"`, there is no deployment phase — the row
241
+ is invocable as soon as the create returns, and `function_arn`
242
+ is caller-supplied. Other body types (`s3`, `sns`, `rest_api`,
243
+ …) likewise have no deployment phase.
244
+
245
+ ### `rest_api` body with `auth_method: "oidc"` probes the token endpoint at create
246
+
247
+ For `auth_method: "oidc"`, the platform fetches an access token
248
+ from the configured `oidc_issuer_url` using
249
+ `oidc_client_id` + `oidc_client_secret` **before** the row is
250
+ written. The probe surfaces in field-level rejections:
251
+
252
+ ```
253
+ 401 from token endpoint → /oidc_client_id:
254
+ "invalid credentials — token
255
+ endpoint returned 401"
256
+ other non-200 → /oidc_issuer_url:
257
+ "token endpoint returned HTTP {status}"
258
+ network unreachable → /oidc_issuer_url:
259
+ "cannot reach token endpoint: {reason}"
260
+ ```
261
+
262
+ The probe is skipped when any of the required OIDC fields
263
+ (`oidc_issuer_url`, `oidc_client_id`, `oidc_client_secret`) are
264
+ missing — in that case the request fails on the missing-field
265
+ check before any HTTP call. Other `auth_method` values
266
+ (`oauth2`, `api_key`, `basic`, `none`) do not probe — only the
267
+ OIDC path runs the token-endpoint reachability check.
268
+
269
+ The probe runs on PUT too; an update that re-supplies any of
270
+ the three OIDC fields re-validates the token endpoint, so a
271
+ silent OIDC-provider outage can fail an otherwise-cosmetic
272
+ PUT.
273
+
274
+ ## 3. Field ownership conventions
275
+
276
+ ### Caller-supplied
277
+
278
+ ```
279
+ identification + metadata
280
+ name human-readable label (unique per datalake)
281
+ description (nullable) free text
282
+ intent ToolIntent enum value
283
+ status "draft" | "active" | "inactive" | "error" | "marked_for_deletion"
284
+ 'draft' — created but unconfigured (typical for
285
+ lambda tools awaiting deployment)
286
+ 'active' — invocable; the steady state
287
+ 'inactive' — paused / soft-deleted; invocations
288
+ short-circuit (see §2)
289
+ 'error' — failure state; surfaces via UI banners
290
+ and gates re-invocation until resolved
291
+ 'marked_for_deletion' — cleanup in flight (lambda undeploy etc.);
292
+ transient before DB row removal
293
+
294
+ scoping
295
+ datalake_id parent datalake (UUID)
296
+ data_source_id (optional) parent data source for embedded
297
+ tools; omit for standalone
298
+
299
+ body (polymorphic — required keys depend on tool_body_type)
300
+ tool_body_type discriminator — names which branch shape
301
+ the rest of `body` must satisfy
302
+ <branch-specific> auth + endpoint + credential fields
303
+ (use `<%= name %>` Eta refs for credentials;
304
+ never literal values)
305
+ ```
306
+
307
+ ### Server-derived
308
+
309
+ ```
310
+ id UUID
311
+ slug derived from name (lowercase + hyphen)
312
+ created_at ISO timestamp
313
+ updated_at ISO timestamp
314
+ deployment_status lambda tools only
315
+ deployment_error lambda tools only, when status="failed"
316
+ ```
317
+
318
+ The slug, like every other resource, is the canonical handle for
319
+ downstream references after create — never pre-compute it
320
+ client-side.
321
+
322
+ ## 4. Error envelopes
323
+
324
+ A `422` from `POST` / `PUT` returns:
325
+
326
+ ```
327
+ {
328
+ "errors": {
329
+ "field.path": ["check message", …],
330
+ "body.tool_body_type": ["is invalid"],
331
+ …
332
+ }
333
+ }
334
+ ```
335
+
336
+ Walking each entry:
337
+
338
+ 1. Look up the field on `ToolRequestWritable` (or the relevant
339
+ `Tool<Type>BodyWritable` branch when the path starts with
340
+ `body.`). The TS type's JSDoc names the semantic role.
341
+ 2. Read the 422 `detail` for the precise constraint that fired
342
+ (the field tables in this MD list each branch's required-list).
343
+ 3. For `body.<...>` failures, the FIRST diagnostic is usually
344
+ `tool_body_type` — fix the discriminator before chasing other
345
+ fields, because the wrong branch's required-list will fire
346
+ spuriously when the discriminator is wrong.
347
+
348
+ Common rejections:
349
+
350
+ ```
351
+ field check cause
352
+ ───────────────────────────── ─────────────────────────────
353
+ "is invalid" on intent value not in ToolIntent enum
354
+ "is invalid" on tool_body_type value not in
355
+ ToolBodyTypeEnum
356
+ "is invalid" on body intent ↔ body_type pairing
357
+ rejected at platform level
358
+ "can't be blank" on a body sub-field branch-specific required
359
+ field omitted
360
+ "must match auth_method" on credentials e.g. `iam_role` set but
361
+ `access_key_id` also supplied
362
+ ```
363
+
364
+ ## 5. Lifecycle
365
+
366
+ ### Create
367
+
368
+ `POST /tenants/:tenant_slug/datalakes/:datalake_slug/tools`
369
+ returns `201` with the full tool shape including server-derived
370
+ fields. For non-lambda body types the tool is invocable
371
+ immediately; for `aws_lambda` the deployment is async (see §2).
372
+
373
+ ### Test invocation
374
+
375
+ `POST /tenants/:tenant/datalakes/:datalake/tools/:id/test-
376
+ invocation` exercises the tool end-to-end against the live
377
+ external system.
378
+
379
+ The request body wraps the call payload under `tool_call`, whose
380
+ own polymorphic `tool_call_type` discriminator selects the call
381
+ shape:
382
+
383
+ ```typescript
384
+ await api.tools.testInvocation(
385
+ tenantSlug, datalakeSlug, toolId,
386
+ {
387
+ tool_call: {
388
+ tool_call_type: 'sms_request', // 'email_request' | ...
389
+ to: { type: 'custom', body: '+15551234567' },
390
+ body: { type: 'custom', body: 'Hello' },
391
+ sms_type: 'transactional', // sms_request-specific
392
+ },
393
+ },
394
+ )
395
+ ```
396
+
397
+ `to` and `body` are each `{ type, body }` embeds — `type: 'custom'`
398
+ inlines the value verbatim; `type: 'system'` references a
399
+ platform-shipped template (see `templates.md`). Variant-specific
400
+ fields carry their own enums and constraints; the per-branch
401
+ required-list:
402
+
403
+ ```
404
+ tool_call_type: 'sms_request'
405
+ required: to, body, sms_type
406
+ enums: sms_type ∈ { 'transactional' (default), 'promotional' }
407
+
408
+ tool_call_type: 'email_request'
409
+ required: to, subject, body
410
+ embeds: TemplateConfig variants accepted on each ({ type:
411
+ 'custom', body } or { type: 'identity' } or
412
+ { type: 'system', path }); 'null' is not valid here
413
+
414
+ tool_call_type: 'rest_api_request'
415
+ required: path, method, pagination_context_template
416
+ enums: method ∈ HTTP methods (GET, POST, PUT, PATCH, DELETE,
417
+ HEAD, OPTIONS)
418
+ optional: body, params (each TemplateConfig — 'identity' or
419
+ 'custom' both accepted)
420
+
421
+ tool_call_type: 'sql_query'
422
+ required: query (TemplateConfig — Liquid template producing
423
+ the SQL string)
424
+
425
+ tool_call_type: 'aws_lambda_request'
426
+ required: payload (TemplateConfig — 'identity' acceptable)
427
+ constraints: timeout_ms in inclusive range [1000, 900_000]
428
+ (defaults to 30_000 ms when omitted)
429
+
430
+ tool_call_type: 'sftp_request'
431
+ required: path, content_type
432
+ enums: content_type ∈ closed set (json, ndjson, csv, ...);
433
+ the SDK type names the live set per branch
434
+ format: path validated against an absolute-path pattern
435
+ and a maximum length
436
+
437
+ tool_call_type: 'microsoft_share_point_excel_request'
438
+ required: every field of the branch
439
+ constraints: sheet_number >= 0 (0-indexed; negative values are
440
+ rejected with a "greater than -1" message)
441
+ ```
442
+
443
+ Liquid template bodies (`query`, `payload`, body/params on
444
+ `rest_api_request`, `sms_request` body, `email_request` body) are
445
+ parsed at create time — invalid Liquid syntax surfaces as a field
446
+ rejection on the corresponding TemplateConfig path, not on the
447
+ outer request.
448
+
449
+ The response shape:
450
+
451
+ ```
452
+ {
453
+ id: UUID of the recorded invocation
454
+ status: "success" | "error"
455
+ error_message: string | null
456
+ tool_call: the input echoed back, with provider metadata
457
+ }
458
+ ```
459
+
460
+ This endpoint **never returns a 5xx for tool-side failures** —
461
+ provider errors (bad credentials, network refused, misconfigured
462
+ auth) surface as `status: "error"` with `error_message` populated.
463
+ Only platform-internal failures (DB unreachable, etc.) produce
464
+ 5xx. Treat 5xx as infra noise; treat `200 + status:"error"` as a
465
+ real (and handleable) configuration problem.
466
+
467
+ ### Update
468
+
469
+ `PUT /tenants/:tenant/datalakes/:datalake/tools/:id` replays the
470
+ full body — no PATCH. The polymorphic discriminator MUST match
471
+ the existing row's `tool_body_type`; changing body type requires
472
+ delete + recreate.
473
+
474
+ ### Delete
475
+
476
+ `DELETE /tenants/:tenant/datalakes/:datalake/tools/:id` removes
477
+ the row. For `aws_lambda` tools, this also enqueues a
478
+ `trigger_lambda_deletion` job — poll until the AWS function is
479
+ gone. Other body types delete synchronously.
480
+
481
+ ### Ordering
482
+
483
+ A tool MUST be created AFTER its parent datalake reaches
484
+ `status: "ready"`, and (if embedded) AFTER its parent data source
485
+ exists. Workflows and data activation clients reference tools by id, so any
486
+ downstream resource that names a tool must apply AFTER it.
487
+
488
+ ## 6. Gotchas
489
+
490
+ 1. **Discriminator wire name is `tool_body_type`** — not `type`,
491
+ not `kind`, not `__type__`. Always use the wire name on the
492
+ request body.
493
+
494
+ 2. **`manual_upload` body is the only branch with no auth /
495
+ endpoint fields** — its presigned URLs are issued by the
496
+ platform per-upload. `body: { tool_body_type: 'manual_upload' }`
497
+ with no other body fields is the full create body.
498
+
499
+ 3. **`sns` tools with `iam_role` auth + no `endpoint_url` MUST be
500
+ tested against real AWS** — `ExAws.Config.AuthCache` refreshes
501
+ SSO credentials at invocation time. In dev / E2E, use
502
+ `auth_method: 'access_key'` + `endpoint_url:
503
+ 'http://localhost:4566'` for LocalStack routing.
504
+
505
+ 4. **Test-invocation graceful-error contract is load-bearing** —
506
+ any code path that crashes the test-invocation route into a 500
507
+ is a regression. Misconfigured tools must surface
508
+ `status: "error"` + non-empty `error_message`. If you see a
509
+ 5xx, file a platform bug, do not retry.
510
+
511
+ 5. **Standalone vs embedded tool routing differs at list time**
512
+ — `tools.list(tenant, datalake)` returns ALL tools on the
513
+ datalake (standalone + embedded). To find tools owned by a
514
+ specific data source, filter the list client-side on
515
+ `data_source_id`.
516
+
517
+ 6. **Tool slug uniqueness is per-datalake, not per-tenant** —
518
+ two datalakes on the same tenant can have tools with the same
519
+ slug. Always scope tool references via the datalake slug,
520
+ never just the tool slug.
521
+
522
+ 7. **`tools.metadata(tenant, datalake)` returns a Markdown
523
+ catalog** — useful as a discovery surface for downstream
524
+ tooling. `tools.metadataDetails(tenant, datalake, toolId)`
525
+ returns Markdown for a single tool including the body
526
+ discriminator + per-branch fields.
527
+
528
+ 8. **`PUT` replays the full body** — to flip `status` from
529
+ `active` to `inactive`, resubmit the entire body including all
530
+ credential / endpoint fields. Forgetting the body branch
531
+ discriminator on PUT triggers the "tool_body_type required"
532
+ rejection.
533
+
534
+ 9. **Lambda deployment_status is the readiness signal, not
535
+ `status`** — for `aws_lambda` tools, `status: "active"` only
536
+ means "row exists and is invocation-eligible if deployed".
537
+ The actual readiness is `deployment_status: "deployed"`.
538
+
539
+ 10. **Credentials in the body MUST go through `<%= name %>`
540
+ Eta references** when the tool is authored via a manifest — never
541
+ literal strings on disk. The flat top-level identifier form is the
542
+ safe shape (`<%= secrets.name %>` nested under a `secrets` object
543
+ silently renders the string `"undefined"` if the key is missing).
544
+ The SDK does not enforce this (it accepts literals), but secret
545
+ references are the required operating mode for any non-test
546
+ environment.
@@ -0,0 +1,131 @@
1
+ # Type naming
2
+
3
+ The SDK's TypeScript codegen emits two type variants per
4
+ resource — a request shape (for POST/PUT bodies) and a response
5
+ shape (for GET output). They differ in which fields they
6
+ include.
7
+
8
+ ## Writable suffix for request shapes
9
+
10
+ Request types carry a `Writable` suffix. The codegen
11
+ auto-excludes any field marked `readOnly` in the OpenAPI spec —
12
+ these are the server-derived fields (`id`, `slug`, `created_at`,
13
+ `updated_at`, `status`, etc. — enumerated in the next section).
14
+
15
+ ```typescript
16
+ import type {
17
+ DatalakeRequestWritable, // POST/PUT body
18
+ DatalakeResponse, // GET output
19
+ } from '@alvera-ai/platform-sdk'
20
+ ```
21
+
22
+ Same convention for every resource:
23
+
24
+ ```
25
+ <Resource>RequestWritable POST/PUT body (request shape)
26
+ <Resource>Response GET output (response shape with
27
+ server-derived fields included)
28
+ ```
29
+
30
+ ## Server-derived fields auto-excluded from Writable
31
+
32
+ Every resource carries a consistent set of fields the platform
33
+ server derives on create. These appear in `<Resource>Response`
34
+ types but are NEVER part of `<Resource>RequestWritable` types —
35
+ the codegen auto-excludes them based on `readOnly` markers in
36
+ the OpenAPI spec:
37
+
38
+ ```
39
+ id UUID, allocated on create
40
+ slug derived from `name` via the platform's
41
+ slug-generation (lowercase + hyphenate +
42
+ de-duplicate within scope)
43
+ created_at ISO 8601 timestamp
44
+ updated_at ISO 8601 timestamp
45
+ status resource-specific lifecycle enum
46
+ (e.g. "new" → "ready" → ... per the
47
+ resource's MD §5)
48
+ status_reason nullable; populated when status enters a
49
+ terminal-failure state
50
+ ```
51
+
52
+ Including any of these in a POST/PUT body triggers a layer-1
53
+ 422 with an "unknown key" detail. The typed `*RequestWritable`
54
+ doesn't expose these fields, so TypeScript prevents it at
55
+ compile time when consumers use the typed methods.
56
+
57
+ ### Never pre-compute the slug client-side
58
+
59
+ Slugs are derived from `name` server-side and de-duplicated
60
+ within scope (per-resource — usually datalake-scoped for
61
+ datalake-owned resources, tenant-scoped for tenant-level
62
+ resources). A client-computed slug that conflicts with an
63
+ existing row gets a different suffix server-side, leaving the
64
+ client's local state out of sync.
65
+
66
+ The canonical pattern:
67
+
68
+ ```typescript
69
+ // 1. create — slug returned in the response
70
+ const { data: created } = await api.tools.create(
71
+ tenantSlug, datalakeSlug, body,
72
+ )
73
+ const toolSlug = created.slug // <-- canonical
74
+
75
+ // 2. downstream references use the returned slug
76
+ await api.tools.get(tenantSlug, datalakeSlug, toolSlug)
77
+ ```
78
+
79
+ Never:
80
+
81
+ ```typescript
82
+ // ❌ wrong — client-computed slug can drift from server-side
83
+ const toolSlug = body.name.toLowerCase().replace(/\s+/g, '-')
84
+ ```
85
+
86
+ ### Per-resource extras
87
+
88
+ Some resources have additional server-derived fields beyond
89
+ the universal set (e.g. AWS Lambda tools' `deployment_status`
90
+ + `deployment_error`; data-activation-client `last_run_at`).
91
+ Each resource's MD §3 (Field ownership) lists its full
92
+ server-derived set.
93
+
94
+ ## No client-side validators
95
+
96
+ The SDK ships **types only** — there is no paired runtime validator to
97
+ import (`v<Resource>RequestWritable` no longer exists). Validation is
98
+ server-authoritative: submit the body and handle the server's 422
99
+ `AlveraApiError` if it's malformed (see `errors.md`). If you author config
100
+ through the `alvera` CLI, `alvera plan` validates locally against the spec
101
+ before any HTTP call.
102
+
103
+ ## Enum types
104
+
105
+ Enums are exported as TypeScript const-style values (per the
106
+ SDK's strict-TS conventions — no TypeScript-native `enum`
107
+ keyword):
108
+
109
+ ```typescript
110
+ import { ToolIntent } from '@alvera-ai/platform-sdk'
111
+
112
+ ToolIntent.DATA_EXCHANGE // wire value: "data_exchange"
113
+ ToolIntent.SMS // wire value: "sms"
114
+ ```
115
+
116
+ The wire value is always snake_case; the TS-side identifier is
117
+ UPPER_SNAKE for readability. Each resource's MD names its
118
+ relevant enum types.
119
+
120
+ ## List response types
121
+
122
+ List endpoints return a typed envelope: `<Resource>List-
123
+ Response` with `data: <Resource>Response[]` and `meta:
124
+ PaginationMeta`:
125
+
126
+ ```typescript
127
+ import type { ToolListResponse } from '@alvera-ai/platform-sdk'
128
+
129
+ const { data } = await api.tools.list(tenantSlug, datalakeSlug)
130
+ // data is ToolListResponse: { data: ToolResponse[], meta: PaginationMeta }
131
+ ```