@alvera-ai/platform-sdk 0.10.0-rc.18 → 0.10.0-rc.19

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
@@ -66,7 +66,7 @@ corpus page documents the surface you already know:
66
66
  | Host | The platform itself | This corpus + `account_management.md` |
67
67
  | Client | Data Activation Client | `data_activation_clients.md` |
68
68
  | Server | Agentic Workflow | `workflows.md` |
69
- | Resources | Datasets (per data domain) | `custom_datasets.md`, `mdm.md` |
69
+ | Resources | Datasets (per data domain) | `generic_tables.md`, `mdm.md` |
70
70
  | Prompts | Interoperability Contracts | `interoperability_contracts.md`, `templates.md` |
71
71
  | Sampling | Dataset event triggers | `data_activation_clients.md` §6.4 logs |
72
72
  | Tools | Tool Calls (polymorphic body)| `tools.md` |
@@ -159,7 +159,7 @@ clobbers earlier instances' auth.
159
159
 
160
160
  DATA ACTIVATION (how data flows in)
161
161
  ───────────────
162
- custom_datasets.md Schema-on-write tables.
162
+ generic_tables.md Schema-on-write tables.
163
163
  interoperability_contracts.md Liquid template mappings.
164
164
  data_activation_clients.md Ingestion pipelines — both the
165
165
  binding row (CRUD) and the
@@ -192,7 +192,7 @@ wire name (snake_case):
192
192
  api.actionStatusUpdaters action_status_updaters.md
193
193
  api.connectedApps connected_apps.md
194
194
  api.mdm mdm.md
195
- api.genericTables custom_datasets.md
195
+ api.genericTables generic_tables.md
196
196
  api.interoperabilityContracts interoperability_contracts.md
197
197
  api.dataActivationClients data_activation_clients.md
198
198
  api.workflows workflows.md
@@ -87,7 +87,7 @@ ctx.sarahTenantlessApi = createIsolatedPlatformApi({
87
87
  const tenantResp = await ctx.sarahTenantlessApi.tenants.create({
88
88
  name: `Cookbook Accounts Receivable ${runSuffix}`,
89
89
  })
90
- tenantSlug = tenantResp.data.tenant!.slug!
90
+ tenantSlug = tenantResp.data.slug!
91
91
  ```
92
92
 
93
93
  ## 003 — sarah signs in tenant-scoped — the canonical client
@@ -85,7 +85,7 @@ ctx.sarahTenantlessApi = createIsolatedPlatformApi({
85
85
  const tenantResp = await ctx.sarahTenantlessApi.tenants.create({
86
86
  name: `Cookbook Foundation ${runSuffix}`,
87
87
  })
88
- tenantSlug = tenantResp.data.tenant!.slug!
88
+ tenantSlug = tenantResp.data.slug!
89
89
  ```
90
90
 
91
91
  ## 003 — sarah signs in tenant-scoped — the canonical client
@@ -85,7 +85,7 @@ ctx.sarahTenantlessApi = createIsolatedPlatformApi({
85
85
  const tenantResp = await ctx.sarahTenantlessApi.tenants.create({
86
86
  name: `Cookbook Healthcare ${runSuffix}`,
87
87
  })
88
- tenantSlug = tenantResp.data.tenant!.slug!
88
+ tenantSlug = tenantResp.data.slug!
89
89
  ```
90
90
 
91
91
  ## 003 — sarah signs in tenant-scoped — the canonical client
@@ -87,7 +87,7 @@ ctx.sarahTenantlessApi = createIsolatedPlatformApi({
87
87
  const tenantResp = await ctx.sarahTenantlessApi.tenants.create({
88
88
  name: `Cookbook Payment Risk ${runSuffix}`,
89
89
  })
90
- tenantSlug = tenantResp.data.tenant!.slug!
90
+ tenantSlug = tenantResp.data.slug!
91
91
  ```
92
92
 
93
93
  ## 003 — sarah signs in tenant-scoped — the canonical client
@@ -5,7 +5,7 @@ industry: healthcare
5
5
  slug: contact-us-triage-with-llm
6
6
  vitest_source:
7
7
  - integration-tests/tests/healthcare/agent-driven-workflow.test.ts
8
- - integration-tests/tests/healthcare/custom-datasets.test.ts
8
+ - integration-tests/tests/healthcare/generic-tables.test.ts
9
9
  - integration-tests/tests/healthcare/tools.test.ts
10
10
  - integration-tests/tests/healthcare/bootstrap.test.ts
11
11
  status: green
@@ -34,7 +34,7 @@ Three SMS actions are registered against the workflow, one per
34
34
  bucket; the agent's output picks which one runs.
35
35
 
36
36
  This cookbook walks the **whole** scenario: it provisions the
37
- custom dataset, agent, and workflow, ingests three contact-us
37
+ generic table, agent, and workflow, ingests three contact-us
38
38
  submissions through the generic-table's auto-provisioned default
39
39
  DAC, runs the workflow so the agent classifies each row, and
40
40
  verifies the routing row-by-row — every Workflow Execution Log
@@ -58,7 +58,7 @@ this cookbook starts from there.
58
58
  | Contact Us Triage AI agent | build |
59
59
  | Contact Us Triage workflow | build |
60
60
 
61
- The custom dataset's **default DAC** — auto-provisioned by the
61
+ The generic table's **default DAC** — auto-provisioned by the
62
62
  platform when the generic table is created — is reused for
63
63
  ingestion; no separate data source / tool / interop contract / DAC
64
64
  is created. The setup file `_setup/healthcare.md` already
@@ -67,7 +67,7 @@ cookbook starts from there.
67
67
 
68
68
  # Walkthrough
69
69
 
70
- ## 001 — create the Contact Us submissions custom dataset
70
+ ## 001 — create the Contact Us submissions generic table
71
71
 
72
72
  The agent-driven workflow targets a Generic Table dataset
73
73
  (`dataset_type: 'generic_table'`) rather than a regulated domain
@@ -79,7 +79,7 @@ The server-derived `name` is captured — the workflow run in §009
79
79
  addresses the regulated table as `regulated_<name>`.
80
80
 
81
81
  ```typescript
82
- const customDatasetResp = await api.genericTables.create(tenantSlug, datalakeSlug, {
82
+ const genericTableResp = await api.genericTables.create(tenantSlug, datalakeSlug, {
83
83
  title: `Cookbook Contact Us ${runSuffix}`,
84
84
  description: 'Inbound contact-us form submissions the Triage agent classifies.',
85
85
  columns: [
@@ -125,8 +125,8 @@ const customDatasetResp = await api.genericTables.create(tenantSlug, datalakeSlu
125
125
  },
126
126
  ],
127
127
  })
128
- customDatasetId = customDatasetResp.data.id!
129
- ctx.gtName = customDatasetResp.data.name!
128
+ genericTableId = genericTableResp.data.id!
129
+ ctx.gtName = genericTableResp.data.name!
130
130
  ```
131
131
 
132
132
  ## 002 — create the SMS tool
@@ -286,7 +286,7 @@ const workflowResp = await api.workflows.create(tenantSlug, datalakeSlug, {
286
286
  name: `Cookbook Contact Us Triage Workflow ${runSuffix}`,
287
287
  description: 'Triages contact-us submissions into appointment_request/job_inquiry/flag_spam via an LLM agent; one SMS action per bucket.',
288
288
  dataset_type: 'generic_table',
289
- generic_table_id: customDatasetId,
289
+ generic_table_id: genericTableId,
290
290
  skip_mdm_resolution: true,
291
291
  status: 'live',
292
292
  filter_config: {
@@ -593,11 +593,11 @@ end-to-end, not merely provisioned.
593
593
  - `.agent/workflows.md` — standard and agent-driven workflow
594
594
  primitives; `ai_agents`, `context_mapping_config`, and
595
595
  agent-output decision interpolation
596
- - `.agent/custom_datasets.md` — generic-table creation and the
596
+ - `.agent/generic_tables.md` — generic-table creation and the
597
597
  auto-provisioned identity contract + default DAC
598
598
  - `integration-tests/tests/healthcare/agent-driven-workflow.test.ts` —
599
599
  the anchor green test (positive branch §1, §3, §4, §6, §7) these
600
600
  snippets are lifted from
601
- - `integration-tests/tests/healthcare/custom-datasets.test.ts`,
601
+ - `integration-tests/tests/healthcare/generic-tables.test.ts`,
602
602
  `tools.test.ts` — the per-resource create snippets are lifted
603
603
  from
@@ -5,7 +5,7 @@ industry: foundation
5
5
  slug: score-leads-with-llm-categorization
6
6
  vitest_source:
7
7
  - integration-tests/tests/foundation/agent-driven-workflow.test.ts
8
- - integration-tests/tests/foundation/custom-datasets.test.ts
8
+ - integration-tests/tests/foundation/generic-tables.test.ts
9
9
  - integration-tests/tests/foundation/tools.test.ts
10
10
  - integration-tests/tests/foundation/bootstrap.test.ts
11
11
  status: green
@@ -55,13 +55,13 @@ cookbook starts from there.
55
55
 
56
56
  | Resource provisioned | Owner |
57
57
  |----------------------------------|-------------|
58
- | Lead Submissions custom dataset | build |
58
+ | Lead Submissions generic table | build |
59
59
  | SMS tool (SNS-backed) | build |
60
60
  | LLM tool (Ollama chat completion)| build |
61
61
  | Score Lead AI agent | build |
62
62
  | Score Lead workflow | build |
63
63
 
64
- The custom dataset's **default DAC** — auto-provisioned by the
64
+ The generic table's **default DAC** — auto-provisioned by the
65
65
  platform when the generic table is created — is reused for ingestion;
66
66
  no separate data source / tool / interop contract / DAC is created.
67
67
  The setup file `_setup/foundation.md` already provisioned the tenant +
@@ -69,7 +69,7 @@ datalake + tenant-scoped client; this cookbook starts from there.
69
69
 
70
70
  # Walkthrough
71
71
 
72
- ## 001 — create the Lead Submissions custom dataset
72
+ ## 001 — create the Lead Submissions generic table
73
73
 
74
74
  The agent-driven workflow targets a Generic Table dataset
75
75
  (`dataset_type: 'generic_table'`) rather than a regulated domain
@@ -81,7 +81,7 @@ in production. The server-derived `name` is captured — the workflow
81
81
  run in §009 addresses the regulated table as `regulated_<name>`.
82
82
 
83
83
  ```typescript
84
- const customDatasetResp = await api.genericTables.create(tenantSlug, datalakeSlug, {
84
+ const genericTableResp = await api.genericTables.create(tenantSlug, datalakeSlug, {
85
85
  title: `Cookbook Lead Submissions ${runSuffix}`,
86
86
  description: 'Inbound lead-capture form rows the Score Lead agent classifies.',
87
87
  columns: [
@@ -135,8 +135,8 @@ const customDatasetResp = await api.genericTables.create(tenantSlug, datalakeSlu
135
135
  },
136
136
  ],
137
137
  })
138
- customDatasetId = customDatasetResp.data.id!
139
- ctx.gtName = customDatasetResp.data.name!
138
+ genericTableId = genericTableResp.data.id!
139
+ ctx.gtName = genericTableResp.data.name!
140
140
  ```
141
141
 
142
142
  ## 002 — create the SMS tool
@@ -294,7 +294,7 @@ const workflowResp = await api.workflows.create(tenantSlug, datalakeSlug, {
294
294
  name: `Cookbook Score Lead Workflow ${runSuffix}`,
295
295
  description: 'Classifies lead_submissions into hot/warm/cold/spam via an LLM agent; one SMS action per band.',
296
296
  dataset_type: 'generic_table',
297
- generic_table_id: customDatasetId,
297
+ generic_table_id: genericTableId,
298
298
  skip_mdm_resolution: true,
299
299
  status: 'live',
300
300
  filter_config: {
@@ -590,13 +590,13 @@ provisioned.
590
590
  - `.agent/workflows.md` — standard and agent-driven workflow
591
591
  primitives; `ai_agents`, `context_mapping_config`, and agent-output
592
592
  decision interpolation
593
- - `.agent/custom_datasets.md` — generic-table creation and the
593
+ - `.agent/generic_tables.md` — generic-table creation and the
594
594
  auto-provisioned identity contract + default DAC
595
595
  - `.agent/ai_sandbox.md` — bounds on the Liquid sandbox the
596
596
  context-mapping and decision templates execute inside
597
597
  - `integration-tests/tests/foundation/agent-driven-workflow.test.ts` —
598
598
  the anchor green test (§1–§5) these snippets are lifted from
599
- - `integration-tests/tests/foundation/custom-datasets.test.ts` — §2 the
599
+ - `integration-tests/tests/foundation/generic-tables.test.ts` — §2 the
600
600
  Generic Table create snippet is lifted from
601
601
  - `integration-tests/tests/foundation/tools.test.ts` — the SMS tool
602
602
  create snippet is lifted from
@@ -89,7 +89,7 @@ A single activation client can bind:
89
89
 
90
90
  - one or more `custom` / `system` contracts (e.g. patient +
91
91
  appointment for a multi-resource ingestion), OR
92
- - a single `identity` contract bound to a custom dataset
92
+ - a single `identity` contract bound to a generic table
93
93
  (the auto-created identity contract from
94
94
  `interoperability_contracts.md` §5).
95
95
 
@@ -489,7 +489,7 @@ first.
489
489
  Cross-datalake references return 404 on the offending id.
490
490
 
491
491
  4. **Custom-dataset identity contracts get their own
492
- activation client.** When you ingest into a custom dataset,
492
+ activation client.** When you ingest into a generic table,
493
493
  bind the table's auto-created identity contract (see
494
494
  `interoperability_contracts.md` §5) to a dedicated activation
495
495
  client. Don't mix it with non-identity contracts on the
@@ -125,10 +125,10 @@ Standard JSON:API envelopes per `errors.md`. Common rejections:
125
125
  | `/status` | value not in the typed enum |
126
126
  | `/is_default` | another source already marked default |
127
127
 
128
- Client-side: `vDataSourceRequestWritable` catches structural
129
- issues before submit as `ValiError`. Server-side semantic
130
- rejections (uniqueness, is_default conflict) come back as
131
- 422 thrown from the SDK on submit. See `errors.md`.
128
+ All of these come back as a 422 `AlveraApiError` thrown from the
129
+ SDK on submit — both structural failures (Layer 1) and semantic
130
+ rejections like uniqueness / is_default conflict (Layer 2). The
131
+ SDK does not validate client-side. See `errors.md`.
132
132
 
133
133
  ## 5. Lifecycle
134
134
 
@@ -33,11 +33,11 @@ import type {
33
33
  DatalakeRequestWritable,
34
34
  DatalakeResponse,
35
35
  } from '@alvera-ai/platform-sdk'
36
- import { vDatalakeRequestWritable } from '@alvera-ai/platform-sdk'
37
36
  ```
38
37
 
39
- See `type_naming.md` for the `Writable` / `v`-prefix conventions
40
- and the universal server-derived field set.
38
+ See `type_naming.md` for the `Writable` convention and the
39
+ universal server-derived field set. (The SDK exports types only —
40
+ there is no runtime validator; validation is server-authoritative.)
41
41
 
42
42
  ### Polymorphic cloud-storage embed
43
43
 
@@ -322,10 +322,9 @@ The platform returns a standardized JSON:API envelope — see
322
322
  { pointer }, title, detail }, ...] }`) and the two-layer
323
323
  validation model (structural cast → server-side semantic check).
324
324
 
325
- Validator-rejected requests throw `ValiError` synchronously,
326
- before any HTTP round-trip — same envelope shape as the server
327
- returns. See `errors.md` "Client-side validation" for the typed
328
- narrowing pattern.
325
+ All validation errors come back from the server as a thrown 422
326
+ `AlveraApiError` (the SDK does not validate client-side). See
327
+ `errors.md` for the typed narrowing pattern.
329
328
 
330
329
  Datalake-specific common rejections:
331
330
 
@@ -451,11 +450,9 @@ instead.
451
450
  cloud-storage embeds' `access_key_id` / `secret_access_key` — are
452
451
  write-only: present on the request type, absent from the response
453
452
  type. A naive spread of `current` into the PUT body is therefore
454
- missing those 8 required fields. The validator
455
- (`vDatalakeRequestWritable`) catches this client-side as a
456
- `ValiError`; submitting without pre-validation throws the
457
- equivalent envelope from the server (see `errors.md`). Re-supply
458
- them explicitly from your secret store:
453
+ missing those 8 required fields. The server rejects this with a 422
454
+ `AlveraApiError` pointing at the missing fields (see `errors.md`).
455
+ Re-supply them explicitly from your secret store:
459
456
 
460
457
  ```typescript
461
458
  const { data: current } = await api.datalakes.get(tenantSlug, datalakeId)
@@ -591,7 +588,7 @@ for (const name of catalog.datasets) {
591
588
 
592
589
  1. **POST does NOT auto-enqueue migration.** The create response
593
590
  returns immediately with `status: 'new'`; Datalake-DB-resident
594
- downstream resources (tools, AI agents, custom datasets,
591
+ downstream resources (tools, AI agents, generic tables,
595
592
  interoperability contracts, data activation clients,
596
593
  workflows) MUST wait for `ready` (see §5 Lifecycle for the
597
594
  storage-location split). The canonical sequence:
@@ -656,8 +653,8 @@ for (const name of catalog.datasets) {
656
653
  would have hit the conflict.
657
654
 
658
655
  6. **`iam_role` auth still requires a `*_pass` value structurally.**
659
- The TypeScript type and the valibot validator both treat the
660
- field as a required string. Supply a sentinel literal (e.g.
656
+ The TypeScript type (and the server's schema) treat the field as
657
+ a required string. Supply a sentinel literal (e.g.
661
658
  `"unused-iam-role"`); the platform clears it server-side.
662
659
 
663
660
  7. **`cloud_storage_type` lives inside each embed, not at the
@@ -45,9 +45,9 @@ HTTP traffic at all — nothing to redact.
45
45
 
46
46
  ## Safety: the load-bearing piece
47
47
 
48
- The SDK never sees secret REFERENCES (`${secrets.X}` syntax
49
- is a consumer-side convention used by some — CLI manifests,
50
- for instance). By the time a request body reaches the SDK,
48
+ The SDK never sees secret REFERENCES (`<%= name %>` Eta
49
+ syntax is a consumer-side convention used by some — CLI
50
+ manifests, for instance). By the time a request body reaches the SDK,
51
51
  credentials are already substituted to their literal values.
52
52
  If you enable debug logging without populating
53
53
  `redactStrings`, those literals appear in plaintext in your
package/.agent/errors.md CHANGED
@@ -1,56 +1,29 @@
1
1
  # Errors
2
2
 
3
- When you call an SDK method, three things can happen:
3
+ When you call an SDK method, two things can happen:
4
4
 
5
5
  1. **Success** — `{ data, meta? }` returned (typed).
6
- 2. **Synchronous `ValiError`** — the SDK's valibot validator
7
- rejected your input BEFORE any HTTP call.
8
- 3. **Asynchronous throw** — the SDK threw the server's response
9
- body verbatim after the HTTP call returned non-2xx.
6
+ 2. **Throw** — the SDK throws the server's response body verbatim after
7
+ the HTTP call returned a non-2xx status.
10
8
 
11
- Consumers handle (2) and (3) with try/catch. The two cases
12
- differ in WHEN they fire and what the thrown value looks like.
9
+ The SDK does **not** validate request bodies on the client. Validation is
10
+ server-authoritative: a malformed body is rejected by the platform and
11
+ surfaced to you as a thrown **422** body. There is no synchronous
12
+ client-side validation error to catch — every validation failure comes back
13
+ from the server in the standardized envelope below.
13
14
 
14
- ## Client-side: ValiError (no network)
15
+ > Driving the platform through the `alvera` **CLI** instead of the SDK? Then
16
+ > `alvera plan` runs a local pre-flight check against the spec and reports
17
+ > errors *before* any HTTP call. That's a CLI feature (it validates the spec
18
+ > locally); it is not SDK runtime behavior. SDK callers get errors from the
19
+ > server.
15
20
 
16
- Every resource has a generated valibot validator
17
- (`v<Resource>RequestWritable`). The SDK runs this validator on
18
- the request body BEFORE issuing the HTTP call. If your input
19
- fails the validator, the SDK throws a `ValiError` synchronously
20
- — nothing reaches the platform.
21
+ ## The thrown value (post-HTTP)
21
22
 
22
- ```typescript
23
- import { ValiError } from 'valibot'
24
-
25
- try {
26
- const { data } = await api.tools.create(
27
- tenantSlug, datalakeSlug, body,
28
- )
29
- } catch (err) {
30
- if (err instanceof ValiError) {
31
- // err.issues is an array of valibot issues; each has
32
- // .path (a JSON-pointer-like path) and .message
33
- for (const issue of err.issues) {
34
- // issue.path is an array of path segments;
35
- // issue.message is the failure reason.
36
- }
37
- return
38
- }
39
- throw err // re-throw anything not a ValiError
40
- }
41
- ```
42
-
43
- Layer-1 errors (missing required, wrong type, invalid enum,
44
- format violations) almost always fire here client-side.
45
- Running the validator client-side avoids wasted HTTP round-
46
- trips — consumers should NOT bypass it on mutations.
47
-
48
- ## Server-side: SDK throw (post-HTTP)
49
-
50
- When the platform returns a non-2xx status, **the SDK throws the
51
- parsed response body verbatim** as the thrown value — not an
52
- `Error` subclass, just the body object. Consumers narrow it with
53
- a type guard:
23
+ When the platform returns a non-2xx status, **the SDK throws the parsed
24
+ response body verbatim** as the thrown value — not an `Error` subclass, just
25
+ the body object (decorated with an `_httpStatus` field so you can branch on
26
+ the status). Narrow it with a type guard:
54
27
 
55
28
  ```typescript
56
29
  try {
@@ -78,17 +51,13 @@ try {
78
51
  }
79
52
  ```
80
53
 
81
- This path is reserved for cases the client-side validator
82
- couldn't catch (layer 2 below): uniqueness, cross-field
83
- semantic rules, foreign-key constraints. If you see this
84
- thrown body and the same input passes valibot, the failure
85
- is a server-side semantic rule the OpenAPI spec can't
86
- express.
54
+ The thrown body is typed as `AlveraApiError` (exported from the package root)
55
+ for 422s, and the simpler `ErrorResponse` for some other statuses.
87
56
 
88
57
  ## 422 envelope shape
89
58
 
90
- The platform standardizes ALL 422 response bodies on a single
91
- shape — regardless of which validation layer fired:
59
+ The platform standardizes ALL 422 response bodies on a single shape —
60
+ regardless of which validation layer fired:
92
61
 
93
62
  ```json
94
63
  {
@@ -103,45 +72,41 @@ shape — regardless of which validation layer fired:
103
72
  }
104
73
  ```
105
74
 
106
- - `errors` is always an ARRAY of error objects (never a
107
- field-keyed map).
108
- - `source.pointer` is a JSON Pointer (`/field_name` for top-
109
- level fields; `/nested/field` for embeds) naming the
110
- offending field on the request body.
75
+ - `errors` is always an ARRAY of error objects (never a field-keyed map).
76
+ - `source.pointer` is a JSON Pointer (`/field_name` for top-level fields;
77
+ `/nested/field` for embeds) naming the offending field on the request body.
111
78
  - `detail` is the specific reason; `title` is a brief summary.
112
- - Multiple errors may share the same `source.pointer` if more
113
- than one check fired on the same field.
79
+ - Multiple errors may share the same `source.pointer` if more than one check
80
+ fired on the same field.
114
81
 
115
82
  ## Two layers behind the standardized 422
116
83
 
117
- The platform validates a request in two layers; both surface
118
- through the same envelope above:
84
+ The platform validates a request in two layers; both surface through the
85
+ same envelope above:
119
86
 
120
87
  **Layer 1 — OpenAPI cast (structural)**
121
88
 
122
- Runs at the plug layer before the controller. Catches: missing
123
- required fields, wrong types, invalid enums, unknown keys,
124
- format violations (UUID patterns, etc.). The SDK's valibot
125
- validator mirrors this layer client-side, so most layer-1
126
- errors NEVER reach the server.
89
+ Runs at the plug layer (`OpenApiSpex`) before the controller. Catches:
90
+ missing required fields, wrong types, invalid enums, unknown keys, format
91
+ violations (UUID patterns, etc.). This is the *same* OpenAPI schema the SDK's
92
+ types are generated from, so a body that satisfies the TypeScript types
93
+ almost always clears layer 1.
127
94
 
128
95
  **Layer 2 — server-side semantic validator (post-cast)**
129
96
 
130
- Runs after the structural cast succeeds. Catches: uniqueness,
131
- cross-field rules (e.g. `auth_method = "iam_role"` combined with
132
- conflicting credential fields), foreign-key constraints, custom
133
- platform validations. Layer-2 errors are normalized into the
134
- same envelope shape as layer 1 — consumers don't distinguish
135
- layers from the JSON.
97
+ Runs after the structural cast succeeds. Catches: uniqueness, cross-field
98
+ rules (e.g. `auth_method = "iam_role"` combined with conflicting credential
99
+ fields), foreign-key constraints, custom platform validations. Layer-2
100
+ errors are normalized into the same envelope shape as layer 1 — consumers
101
+ don't distinguish layers from the JSON.
136
102
 
137
- Practical consequence: a post-HTTP throw indicates layer-2
138
- (or a layer-1 case your valibot version doesn't yet
139
- have — keep the SDK pin fresh).
103
+ Practical consequence: every validation error reaches you as a thrown 422
104
+ body. Read `source.pointer`, fix that field, resubmit.
140
105
 
141
106
  ## Concrete examples
142
107
 
143
- **Layer 1 — missing required fields** (POST `/agentic-
144
- workflows` with empty body):
108
+ **Layer 1 — missing required fields** (POST `/agentic-workflows` with empty
109
+ body):
145
110
 
146
111
  ```json
147
112
  {
@@ -154,8 +119,7 @@ workflows` with empty body):
154
119
  }
155
120
  ```
156
121
 
157
- **Layer 1 — invalid enum value** (`dataset_type:
158
- "nonexistent_type"`):
122
+ **Layer 1 — invalid enum value** (`dataset_type: "nonexistent_type"`):
159
123
 
160
124
  ```json
161
125
  {
@@ -166,8 +130,8 @@ workflows` with empty body):
166
130
  }
167
131
  ```
168
132
 
169
- **Layer 2 — semantic rule** (DELETE on a default-flagged data
170
- activation client):
133
+ **Layer 2 — semantic rule** (DELETE on a default-flagged data activation
134
+ client):
171
135
 
172
136
  ```json
173
137
  {
@@ -178,8 +142,8 @@ activation client):
178
142
  }
179
143
  ```
180
144
 
181
- **Layer 2 — uniqueness** (POST `/admin/invites` for an
182
- already-invited email):
145
+ **Layer 2 — uniqueness** (POST `/admin/invites` for an already-invited
146
+ email):
183
147
 
184
148
  ```json
185
149
  {
@@ -192,10 +156,10 @@ already-invited email):
192
156
 
193
157
  ## HTTP status codes (under the hood)
194
158
 
195
- The SDK abstracts HTTP responses behind typed methods.
196
- Consumers mostly see typed data or thrown bodies; raw status
197
- codes are usually visible only via the SDK's `--debug`-style
198
- interceptor. The platform maps:
159
+ The SDK abstracts HTTP responses behind typed methods. Consumers mostly see
160
+ typed data or thrown bodies; the thrown body carries `_httpStatus`, and raw
161
+ status is also visible via the SDK's `--debug`-style interceptor. The
162
+ platform maps:
199
163
 
200
164
  | Code | When it fires |
201
165
  |------|----------------------------------------------------------------|
@@ -210,26 +174,23 @@ interceptor. The platform maps:
210
174
 
211
175
  ## Retry semantics
212
176
 
213
- Treat 5xx as infra noise — retry with exponential backoff.
214
- Treat 422 as a real consumer-correctable error — fix the body
215
- and resubmit. **Never retry a 422 with the same body.**
177
+ Treat 5xx as infra noise — retry with exponential backoff. Treat 422 as a
178
+ real consumer-correctable error — fix the body and resubmit. **Never retry a
179
+ 422 with the same body.**
216
180
 
217
181
  ## Mapping `source.pointer` back to your TypeScript type
218
182
 
219
- A `source.pointer` like `/regulated_data_db_reader_user` maps
220
- directly to the `regulated_data_db_reader_user` field on
221
- `DatalakeRequestWritable`. For nested embeds, the pointer
222
- carries the embed path:
183
+ A `source.pointer` like `/regulated_data_db_reader_user` maps directly to the
184
+ `regulated_data_db_reader_user` field on `DatalakeRequestWritable`. For
185
+ nested embeds, the pointer carries the embed path:
223
186
  `/unregulated_cloud_storage/access_key_id`.
224
187
 
225
- For polymorphic embeds, check the discriminator field's
226
- pointer first — if `/tool_body_type` (or
227
- `/cloud_storage_type`, etc.) is in the errors array, fix that
228
- BEFORE chasing other fields on the embed. The wrong
229
- discriminator value cascades into spurious required-list
230
- failures on the wrong branch's fields. See `mutations.md`
231
- for the discriminator naming convention.
232
-
233
- Each per-resource MD names its `<Resource>RequestWritable`
234
- TypeScript type — the corpus's resource pages are the source
235
- of truth for which fields exist on which embed.
188
+ For polymorphic embeds, check the discriminator field's pointer first — if
189
+ `/tool_body_type` (or `/cloud_storage_type`, etc.) is in the errors array,
190
+ fix that BEFORE chasing other fields on the embed. The wrong discriminator
191
+ value cascades into spurious required-list failures on the wrong branch's
192
+ fields. See `mutations.md` for the discriminator naming convention.
193
+
194
+ Each per-resource MD names its `<Resource>RequestWritable` TypeScript type —
195
+ the corpus's resource pages are the source of truth for which fields exist on
196
+ which embed.