@alvera-ai/platform-sdk 0.21.3 → 0.21.4
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 -18
- package/.agent/async.md +4 -4
- package/.agent/cookbook/_fixtures/README.md +28 -32
- package/.agent/cookbook/organic-marketing.md +0 -5
- package/.agent/cookbook/payments-compliance.md +0 -10
- package/.agent/cookbook/primary-care.md +0 -5
- package/.agent/cookbook/subscription-saas.md +0 -8
- package/.agent/interoperability_contracts.md +4 -7
- package/.agent/mdm.md +165 -81
- package/dist/index.d.mts +13 -30
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +15 -14
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/.agent/AGENTS.md
CHANGED
|
@@ -320,8 +320,8 @@ forms are the SQL table names used by `executeSql`):
|
|
|
320
320
|
|
|
321
321
|
`.agent/cookbook/<slug>.md` files are pure-markdown scenario
|
|
322
322
|
stories — one business outcome each, told as a numbered API-call
|
|
323
|
-
sequence
|
|
324
|
-
platform
|
|
323
|
+
sequence that the cookbook validator runs, step by step, against a
|
|
324
|
+
live platform. Agents read these directly
|
|
325
325
|
from the filesystem at
|
|
326
326
|
`node_modules/@alvera-ai/platform-sdk/.agent/cookbook/`; there
|
|
327
327
|
is no SDK function and no CLI verb that returns cookbook bytes
|
|
@@ -333,24 +333,21 @@ cookbook that is missing one, repeats one, reorders them, leaves one
|
|
|
333
333
|
empty, or adds a top-level section that is not on this list:
|
|
334
334
|
|
|
335
335
|
- **Front matter** — `title`, `summary`, `use_case`, `slug`,
|
|
336
|
-
`
|
|
337
|
-
are lifted from, anchor file first), `status`. `use_case` is one
|
|
336
|
+
`status`. `use_case` is one
|
|
338
337
|
of the four surfaces — `organic-marketing`,
|
|
339
338
|
`subscription-saas`, `payments-compliance`,
|
|
340
339
|
`primary-care-feedback` — and there is exactly one cookbook per use
|
|
341
340
|
case. Three are at `cookbook/<use-case>.md`; the fourth is at
|
|
342
|
-
`cookbook/primary-care.md`, because `use_case`
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
than deriving it from the filename.
|
|
341
|
+
`cookbook/primary-care.md`, because `use_case` keeps the use case's
|
|
342
|
+
full name and the filename does not. Read `use_case` from the front
|
|
343
|
+
matter rather than deriving it from the filename.
|
|
346
344
|
- **Problem** — the business-outcome statement in domain terms,
|
|
347
|
-
sourced from the
|
|
345
|
+
sourced from what the walk's steps actually do (not from
|
|
348
346
|
customer-narrative documentation).
|
|
349
347
|
- **Composition** — table of resources provisioned, mapped to
|
|
350
348
|
the audience skill that owns each (setup / build / compose).
|
|
351
349
|
- **Walkthrough** — numbered `## NNN — <step>` subsections, each
|
|
352
|
-
carrying one fenced TypeScript block
|
|
353
|
-
ancillary vitest. State threads forward across subsections via
|
|
350
|
+
carrying one fenced TypeScript block. State threads forward across subsections via
|
|
354
351
|
plain JavaScript variables in the validator's generated
|
|
355
352
|
`describe` closure. A numbered step anywhere else in the file
|
|
356
353
|
is refused.
|
|
@@ -363,8 +360,7 @@ empty, or adds a top-level section that is not on this list:
|
|
|
363
360
|
step the cookbook does not have — and every step runs against a
|
|
364
361
|
live platform — so a gotcha cannot outlive the behaviour it warns
|
|
365
362
|
about.
|
|
366
|
-
- **See also** — links to relevant per-resource reference MDs
|
|
367
|
-
and to the anchor + ancillary vitest files.
|
|
363
|
+
- **See also** — links to relevant per-resource reference MDs.
|
|
368
364
|
|
|
369
365
|
### Every cookbook is self-reliant, and repeats what it needs
|
|
370
366
|
|
|
@@ -403,17 +399,14 @@ map inbound rows into the platform's upsert shapes. Inlining
|
|
|
403
399
|
those templates verbatim inside every cookbook markdown
|
|
404
400
|
bloats each recipe to several hundred lines of Liquid noise
|
|
405
401
|
that obscures the cookbook's lesson. The corpus ships them
|
|
406
|
-
as **vendored fixtures** instead
|
|
407
|
-
repo's own integration tests use.
|
|
402
|
+
as **vendored fixtures** instead.
|
|
408
403
|
|
|
409
404
|
- Fixtures live under
|
|
410
405
|
`.agent/cookbook/_fixtures/<industry>/`. The leading
|
|
411
406
|
underscore marks the directory as fragment infrastructure
|
|
412
407
|
(not a cookbook); the validator's discovery walk skips it.
|
|
413
408
|
Each `<industry>/` subdirectory carries a `README.md`
|
|
414
|
-
listing every template
|
|
415
|
-
production source-of-truth path it was vendored from
|
|
416
|
-
(`platform/priv/liquid_templates/…`).
|
|
409
|
+
listing every template and which cookbook consumes it.
|
|
417
410
|
- Cookbook code loads fixtures via the
|
|
418
411
|
`COOKBOOK_FIXTURES_DIR` environment variable:
|
|
419
412
|
```typescript
|
package/.agent/async.md
CHANGED
|
@@ -99,10 +99,10 @@ retry should:
|
|
|
99
99
|
that names the uniqueness key (usually `/name` or `/slug`).
|
|
100
100
|
3. Treat as "already created" and fetch the existing row.
|
|
101
101
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
102
|
+
This is the "idempotent retry path": use it wherever local
|
|
103
|
+
state may have diverged from server state (e.g. a prior run
|
|
104
|
+
wrote to the server but failed to capture the response
|
|
105
|
+
locally).
|
|
106
106
|
|
|
107
107
|
## Polling cost
|
|
108
108
|
|
|
@@ -52,15 +52,11 @@ the package.
|
|
|
52
52
|
|
|
53
53
|
## Provenance
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
this directory extends that chain into the SDK package so the
|
|
61
|
-
cookbook corpus is self-contained at npm-install time.
|
|
62
|
-
|
|
63
|
-
When a production template changes, re-copy the file here and
|
|
55
|
+
Each template is a verbatim copy of the template the platform
|
|
56
|
+
ships for that feed, vendored here so the cookbook corpus is
|
|
57
|
+
self-contained at npm-install time.
|
|
58
|
+
|
|
59
|
+
When the platform's template changes, re-copy the file here and
|
|
64
60
|
re-run `make validate-cookbook` to confirm the cookbook still
|
|
65
61
|
passes against the new template shape.
|
|
66
62
|
|
|
@@ -68,21 +64,21 @@ passes against the new template shape.
|
|
|
68
64
|
|
|
69
65
|
### `foundation/`
|
|
70
66
|
|
|
71
|
-
| File | Consumed by cookbook(s) |
|
|
72
|
-
|
|
73
|
-
| `_lead_submissions_foundation_legal_entity.liquid` | `birthday-greeting-sms-trigger`, `score-leads-with-llm-categorization` |
|
|
74
|
-
| `_lead_submissions_foundation_generic_table.liquid` | `score-leads-with-llm-categorization` |
|
|
75
|
-
| `_lead_submissions_foundation_mdm.liquid` | `score-leads-with-llm-categorization` |
|
|
67
|
+
| File | Consumed by cookbook(s) |
|
|
68
|
+
|---|---|
|
|
69
|
+
| `_lead_submissions_foundation_legal_entity.liquid` | `birthday-greeting-sms-trigger`, `score-leads-with-llm-categorization` |
|
|
70
|
+
| `_lead_submissions_foundation_generic_table.liquid` | `score-leads-with-llm-categorization` |
|
|
71
|
+
| `_lead_submissions_foundation_mdm.liquid` | `score-leads-with-llm-categorization` |
|
|
76
72
|
|
|
77
73
|
### `healthcare/`
|
|
78
74
|
|
|
79
|
-
| File | Consumed by cookbook(s) |
|
|
80
|
-
|
|
81
|
-
| `_cahps_appointments_healthcare_patient.liquid` | `appointment-review-sms-workflow` |
|
|
82
|
-
| `_cahps_appointments_healthcare_appointment.liquid` | `appointment-review-sms-workflow` |
|
|
83
|
-
| `_cahps_appointments_healthcare_mdm.liquid` | `appointment-review-sms-workflow` |
|
|
84
|
-
| `sample_two_page.pdf` (data) | `ai-agent-invoke` |
|
|
85
|
-
| `memorandum-of-association-01.png` (data) | `ai-agent-invoke` |
|
|
75
|
+
| File | Consumed by cookbook(s) |
|
|
76
|
+
|---|---|
|
|
77
|
+
| `_cahps_appointments_healthcare_patient.liquid` | `appointment-review-sms-workflow` |
|
|
78
|
+
| `_cahps_appointments_healthcare_appointment.liquid` | `appointment-review-sms-workflow` |
|
|
79
|
+
| `_cahps_appointments_healthcare_mdm.liquid` | `appointment-review-sms-workflow` |
|
|
80
|
+
| `sample_two_page.pdf` (data) | `ai-agent-invoke` |
|
|
81
|
+
| `memorandum-of-association-01.png` (data) | `ai-agent-invoke` |
|
|
86
82
|
|
|
87
83
|
The `contact-us-triage-with-llm` cookbook ingests into a generic
|
|
88
84
|
table via the auto-provisioned identity contract, so it loads no
|
|
@@ -90,17 +86,17 @@ vendored template.
|
|
|
90
86
|
|
|
91
87
|
### `subscription/`
|
|
92
88
|
|
|
93
|
-
| File | Consumed by cookbook(s) |
|
|
94
|
-
|
|
95
|
-
| `_customers_subscription_customer.liquid` | `welcome-sms-for-customers`, `dunning-sms-for-delinquent`, `bulk-ingest`, `rest-fetch` |
|
|
96
|
-
| `_customers_subscription_mdm.liquid` | `welcome-sms-for-customers`, `dunning-sms-for-delinquent`, `bulk-ingest`, `rest-fetch` |
|
|
97
|
-
| `stripe_customers_batch1.csv` (data) | `bulk-ingest` |
|
|
89
|
+
| File | Consumed by cookbook(s) |
|
|
90
|
+
|---|---|
|
|
91
|
+
| `_customers_subscription_customer.liquid` | `welcome-sms-for-customers`, `dunning-sms-for-delinquent`, `bulk-ingest`, `rest-fetch` |
|
|
92
|
+
| `_customers_subscription_mdm.liquid` | `welcome-sms-for-customers`, `dunning-sms-for-delinquent`, `bulk-ingest`, `rest-fetch` |
|
|
93
|
+
| `stripe_customers_batch1.csv` (data) | `bulk-ingest` |
|
|
98
94
|
|
|
99
95
|
### `payments/`
|
|
100
96
|
|
|
101
|
-
| File | Consumed by cookbook(s) |
|
|
102
|
-
|
|
103
|
-
| `_payment_accounts_payments_payment_account.liquid` | `kyc-notification-on-account-activation` |
|
|
104
|
-
| `_payment_accounts_payments_mdm.liquid` | `kyc-notification-on-account-activation` |
|
|
105
|
-
| `_compliance_screenings_payments_compliance_screening.liquid` | `sanctions-screening-with-agent-review` |
|
|
106
|
-
| `_compliance_screenings_payments_mdm.liquid` | `sanctions-screening-with-agent-review` |
|
|
97
|
+
| File | Consumed by cookbook(s) |
|
|
98
|
+
|---|---|
|
|
99
|
+
| `_payment_accounts_payments_payment_account.liquid` | `kyc-notification-on-account-activation` |
|
|
100
|
+
| `_payment_accounts_payments_mdm.liquid` | `kyc-notification-on-account-activation` |
|
|
101
|
+
| `_compliance_screenings_payments_compliance_screening.liquid` | `sanctions-screening-with-agent-review` |
|
|
102
|
+
| `_compliance_screenings_payments_mdm.liquid` | `sanctions-screening-with-agent-review` |
|
|
@@ -3,11 +3,6 @@ title: "Organic marketing: score, greet, campaign — on one raw lake"
|
|
|
3
3
|
summary: The whole organic-marketing surface in one walk. Stand up a tenant and a raw datalake, then run the scenarios end to end — an LLM agent banding inbound leads into a four-way SMS fan-out, a pure-Liquid birthday trigger that schedules a greeting a year out and closes the loop through its authenticated link, and an A/B campaign that gates on suppression, splits inside the workflow and dispatches on SMS and email in one run. Ends with delivery reconciliation and a natural-language read of the lake.
|
|
4
4
|
use_case: organic-marketing
|
|
5
5
|
slug: organic-marketing
|
|
6
|
-
vitest_source:
|
|
7
|
-
- integration-tests/tests/organic-marketing/agent-driven-workflow.test.ts
|
|
8
|
-
- integration-tests/tests/organic-marketing/generic-tables.test.ts
|
|
9
|
-
- integration-tests/tests/organic-marketing/manual-upload-tool.test.ts
|
|
10
|
-
- integration-tests/tests/bootstrap/bootstrap.test.ts
|
|
11
6
|
status: draft
|
|
12
7
|
---
|
|
13
8
|
|
|
@@ -3,16 +3,6 @@ title: "Payments compliance: screen, decide, notify — on a tokenized lake"
|
|
|
3
3
|
summary: The whole payments-compliance surface in one walk. Stand up a tenant and a raw datalake, turn tokenization on because a model is going to read this data, then run two scenarios end to end — an LLM agent disambiguating gray-zone sanctions screenings into a verdict-keyed SMS fan-out, and a KYC notification fired by an account activation. Ends by reading one row back through raw, tokenized and redacted keys to show what each reader actually sees, then inspecting how far the tokenized copy has got and repairing one of its tables.
|
|
4
4
|
use_case: payments-compliance
|
|
5
5
|
slug: payments-compliance
|
|
6
|
-
vitest_source:
|
|
7
|
-
- integration-tests/tests/payments-compliance/sanctions-review-workflow.test.ts
|
|
8
|
-
- integration-tests/tests/payments-compliance/kyc-upload-workflow.test.ts
|
|
9
|
-
- integration-tests/tests/payments-compliance/interoperability-contracts.test.ts
|
|
10
|
-
- integration-tests/tests/payments-compliance/run-dac-single.test.ts
|
|
11
|
-
- integration-tests/tests/payments-compliance/create-dac.test.ts
|
|
12
|
-
- integration-tests/tests/payments-compliance/system-templates.test.ts
|
|
13
|
-
- integration-tests/tests/payments-compliance/manual-upload-tool.test.ts
|
|
14
|
-
- integration-tests/tests/payments-compliance/data-sources.test.ts
|
|
15
|
-
- integration-tests/tests/bootstrap/bootstrap.test.ts
|
|
16
6
|
status: draft
|
|
17
7
|
---
|
|
18
8
|
|
|
@@ -3,11 +3,6 @@ title: "Primary care: review, triage, extract — on a tokenized lake"
|
|
|
3
3
|
summary: "The whole primary-care surface in one walk. Stand up a tenant, a raw datalake and the tokenized and redacted copies, then run three scenarios end to end — a post-visit review request that only reaches patients whose appointment was actually fulfilled, an LLM agent triaging inbound contact-us messages into a category-keyed fan-out, and a vision agent reading a scanned document. Ends by reading one appointment back through all three lakes to show what each reader sees, then inspecting how far the tokenized copy has got and repairing one of its tables."
|
|
4
4
|
use_case: primary-care-feedback
|
|
5
5
|
slug: primary-care
|
|
6
|
-
vitest_source:
|
|
7
|
-
- integration-tests/tests/primary-care-feedback/standard-workflow.test.ts
|
|
8
|
-
- integration-tests/tests/primary-care-feedback/agent-driven-workflow.test.ts
|
|
9
|
-
- integration-tests/tests/primary-care-feedback/ai-agent-invoke.test.ts
|
|
10
|
-
- integration-tests/tests/bootstrap/bootstrap.test.ts
|
|
11
6
|
status: draft
|
|
12
7
|
---
|
|
13
8
|
|
|
@@ -3,14 +3,6 @@ title: "Subscription SaaS: one customer spine, three workflows, three ways in"
|
|
|
3
3
|
summary: "The whole subscription-billing surface in one walk. Stand up a tenant and a raw datalake, declare one customers table, and run three workflows over it — a welcome SMS gated on reachability, a dunning reminder gated on reachability and KYC, and an LLM agent that bands accounts by priority and fans out one SMS per band. Then fill the same table three more ways — a bulk CSV upload, a pull-based REST fetch, and finally a teammate invited onto the tenant."
|
|
4
4
|
use_case: subscription-saas
|
|
5
5
|
slug: subscription-saas
|
|
6
|
-
vitest_source:
|
|
7
|
-
- integration-tests/tests/subscription-saas/standard-workflow.test.ts
|
|
8
|
-
- integration-tests/tests/subscription-saas/dunning-sms-workflow.test.ts
|
|
9
|
-
- integration-tests/tests/subscription-saas/agent-lead-triage.test.ts
|
|
10
|
-
- integration-tests/tests/subscription-saas/run-dac-bulk.test.ts
|
|
11
|
-
- integration-tests/tests/subscription-saas/run-dac-fetch.test.ts
|
|
12
|
-
- integration-tests/tests/subscription-saas/invite-team.test.ts
|
|
13
|
-
- integration-tests/tests/bootstrap/bootstrap.test.ts
|
|
14
6
|
status: draft
|
|
15
7
|
---
|
|
16
8
|
|
|
@@ -251,13 +251,10 @@ not interchangeable.
|
|
|
251
251
|
|
|
252
252
|
Two related shapes worth keeping straight:
|
|
253
253
|
|
|
254
|
-
- **`mdm.verify`** (see `mdm.md`)
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
one.
|
|
258
|
-
- **payments** additionally requires `id_type` on each entry of its own
|
|
259
|
-
verify payload. Per-domain differences are real; check the domain you are
|
|
260
|
-
writing for.
|
|
254
|
+
- **`mdm.verify`** (see `mdm.md`) asks "is this really you?" about an
|
|
255
|
+
already-known subject. Its identifier entries carry `system` and `value`,
|
|
256
|
+
both required, with `id_type` and `country` optional — the same on every
|
|
257
|
+
datalake. Its example is not a template for this one.
|
|
261
258
|
|
|
262
259
|
## 4. Error envelopes
|
|
263
260
|
|
package/.agent/mdm.md
CHANGED
|
@@ -41,8 +41,8 @@ vocabulary — `given_name`, `family_name`, `birth_date` and
|
|
|
41
41
|
`gender` are gone, not renamed variants to pick between.
|
|
42
42
|
|
|
43
43
|
```
|
|
44
|
-
MDMVerifyRequest every field the
|
|
45
|
-
────────────────
|
|
44
|
+
MDMVerifyRequest every field the request takes
|
|
45
|
+
──────────────── ─────────────────────────────
|
|
46
46
|
required legal_entity_id
|
|
47
47
|
individual first_name middle_name last_name
|
|
48
48
|
preferred_name date_of_birth
|
|
@@ -53,6 +53,18 @@ vocabulary — `given_name`, `family_name`, `birth_date` and
|
|
|
53
53
|
identifiers addresses
|
|
54
54
|
```
|
|
55
55
|
|
|
56
|
+
Not every field is evidence on its own (§2):
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
can stand alone first_name last_name preferred_name
|
|
60
|
+
date_of_birth business_name date_formed
|
|
61
|
+
phone identifiers addresses
|
|
62
|
+
only beside one legal_entity_type middle_name
|
|
63
|
+
of those citizenship_country nationality
|
|
64
|
+
doing_business_as_names
|
|
65
|
+
jurisdiction_country email
|
|
66
|
+
```
|
|
67
|
+
|
|
56
68
|
An individual example:
|
|
57
69
|
|
|
58
70
|
```typescript
|
|
@@ -88,24 +100,56 @@ const { data: result } = await api.mdm.verify(
|
|
|
88
100
|
],
|
|
89
101
|
},
|
|
90
102
|
)
|
|
91
|
-
// result.status
|
|
103
|
+
// result.status === 'verified' — the only success body
|
|
92
104
|
// result.legal_entity_id === body.legal_entity_id
|
|
93
|
-
// result.verified_at
|
|
94
|
-
// when status === 'not_verified':
|
|
95
|
-
// result.errors — { <field_name>: [reason, ...] } per-field failure detail
|
|
105
|
+
// result.verified_at — ISO 8601 timestamp
|
|
96
106
|
```
|
|
97
107
|
|
|
98
|
-
|
|
108
|
+
`verified` is the **only** success body. A subject that does not
|
|
109
|
+
verify is not a body you read — the call throws, like every other
|
|
110
|
+
route that answers 4xx:
|
|
99
111
|
|
|
100
112
|
```
|
|
101
|
-
|
|
102
|
-
|
|
113
|
+
verified → 200 { status: 'verified', legal_entity_id, verified_at }
|
|
114
|
+
did not verify → throws AlveraApiError, _httpStatus 422
|
|
115
|
+
{ errors: [ { title: 'Invalid value',
|
|
116
|
+
source: { pointer: '/<field>' },
|
|
117
|
+
detail: '<why it failed>' } ] }
|
|
118
|
+
one entry per field that did not match
|
|
119
|
+
subject not found → throws AlveraApiError, _httpStatus 404
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
So `data` is only ever the verified answer, and every failed check
|
|
123
|
+
reaches you in `catch`, in the same envelope every other 422 uses
|
|
124
|
+
(see `errors.md`). Read which fields failed from each entry's
|
|
125
|
+
`source.pointer`:
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
import type { AlveraApiError } from '@alvera-ai/platform-sdk'
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
const { data } = await api.mdm.verify(tenantSlug, datalakeSlug, {
|
|
132
|
+
legal_entity_id: '00000000-0000-0000-0000-000000000000',
|
|
133
|
+
last_name: 'Garcia',
|
|
134
|
+
addresses: [{ postal_code: '94110', country: 'US' }],
|
|
135
|
+
})
|
|
136
|
+
// data.status === 'verified' — act on this person
|
|
137
|
+
} catch (err) {
|
|
138
|
+
const status = (err as { _httpStatus?: number })._httpStatus
|
|
139
|
+
if (status === 422) {
|
|
140
|
+
for (const e of (err as AlveraApiError).errors) {
|
|
141
|
+
// e.source.pointer — '/last_name', '/addresses', '/base', …
|
|
142
|
+
// e.detail — 'does not match any known name', …
|
|
143
|
+
}
|
|
144
|
+
} else if (status === 404) {
|
|
145
|
+
// no legal entity has this id
|
|
146
|
+
} else {
|
|
147
|
+
throw err
|
|
148
|
+
}
|
|
149
|
+
}
|
|
103
150
|
```
|
|
104
151
|
|
|
105
|
-
A
|
|
106
|
-
returned a negative result — it arrives as a 422 whose body
|
|
107
|
-
still carries the full envelope. Distinguish it from the other
|
|
108
|
-
rejections (404 for missing subject, 401 for missing auth).
|
|
152
|
+
A 401 (missing or wrong key) throws too.
|
|
109
153
|
|
|
110
154
|
### One subject: the legal entity
|
|
111
155
|
|
|
@@ -128,14 +172,23 @@ declare the column; every generic-table row struct already has it.
|
|
|
128
172
|
|
|
129
173
|
## 2. Rules the type cannot encode
|
|
130
174
|
|
|
131
|
-
### `legal_entity_id` is required; at least one
|
|
175
|
+
### `legal_entity_id` is required; at least one field that stands alone with it
|
|
132
176
|
|
|
133
177
|
`legal_entity_id` (the master record to verify against) is the
|
|
134
178
|
only required field. Every other property is optional, and the
|
|
135
|
-
verifier requires **at least one
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
179
|
+
verifier requires **at least one field that can stand alone as
|
|
180
|
+
evidence** at runtime: `first_name`, `last_name`, `preferred_name`,
|
|
181
|
+
`date_of_birth`, `business_name`, `date_formed`, `phone`,
|
|
182
|
+
`identifiers` or `addresses`. The other seven (`legal_entity_type`,
|
|
183
|
+
`middle_name`, `citizenship_country`, `nationality`,
|
|
184
|
+
`doing_business_as_names`, `jurisdiction_country`, `email`) are
|
|
185
|
+
accepted, but count only beside one of those nine.
|
|
186
|
+
|
|
187
|
+
- a body with only `legal_entity_id`, or with only fields from the
|
|
188
|
+
seven → 422, one entry with `source.pointer: '/base'` and
|
|
189
|
+
`detail: 'at least one verification field is required'`
|
|
190
|
+
- an address alone is enough evidence; if it does not match, the
|
|
191
|
+
422 carries the `/addresses` entry and nothing else
|
|
139
192
|
|
|
140
193
|
A body carrying the retired vocabulary — `given_name`,
|
|
141
194
|
`family_name`, `birth_date`, `gender` — falls into that same 422,
|
|
@@ -160,9 +213,33 @@ addresses per-component exact, case- and whitespace-
|
|
|
160
213
|
insensitive; at least one address must match
|
|
161
214
|
```
|
|
162
215
|
|
|
163
|
-
All supplied fields must pass for the
|
|
164
|
-
`'verified'`.
|
|
165
|
-
|
|
216
|
+
All supplied fields must pass for the call to return
|
|
217
|
+
`'verified'`. Each field that fails adds one entry to the thrown
|
|
218
|
+
422, pointing at the field you sent:
|
|
219
|
+
|
|
220
|
+
```
|
|
221
|
+
source.pointer detail
|
|
222
|
+
/last_name does not match any known name
|
|
223
|
+
/first_name does not match any known name (see below)
|
|
224
|
+
/business_name does not match any known name
|
|
225
|
+
/date_of_birth does not match
|
|
226
|
+
/date_formed does not match
|
|
227
|
+
/identifiers no matching identifier found
|
|
228
|
+
/phone does not match any known phone number
|
|
229
|
+
/addresses no matching address found
|
|
230
|
+
/addresses/country can't be blank (an address sent without a country)
|
|
231
|
+
/base at least one verification field is required
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
First, middle and preferred names are checked together as one
|
|
235
|
+
bag of given names, so they fail as one entry. It points at a
|
|
236
|
+
given-name field you actually sent: `/first_name` if you sent one,
|
|
237
|
+
else `/middle_name`, else `/preferred_name`.
|
|
238
|
+
|
|
239
|
+
Two fields failing is two entries; nothing is summarised into a
|
|
240
|
+
single verdict. The mock answers its wrong-details case with one
|
|
241
|
+
`/base` entry, `detail: 'Verification match failed'`; the platform
|
|
242
|
+
itself names the field.
|
|
166
243
|
|
|
167
244
|
### `legal_entity_id` is the legal entity's own id
|
|
168
245
|
|
|
@@ -176,11 +253,12 @@ redacted lakes — rather than a second row with its own id. The
|
|
|
176
253
|
old failure mode where passing the regulated twin's id returned a
|
|
177
254
|
404 cannot arise.
|
|
178
255
|
|
|
179
|
-
If the supplied `legal_entity_id` doesn't resolve, the
|
|
180
|
-
|
|
256
|
+
If the supplied `legal_entity_id` doesn't resolve, the call throws
|
|
257
|
+
a 404 envelope (`title: 'Not Found'`, `detail: 'Subject not found'`,
|
|
258
|
+
empty `source.pointer`), not a 422. The distinction matters:
|
|
181
259
|
|
|
182
|
-
-
|
|
183
|
-
- 404
|
|
260
|
+
- 422 -> "entity exists, identity fields didn't match"
|
|
261
|
+
- 404 -> "entity doesn't exist at all"
|
|
184
262
|
|
|
185
263
|
A consumer that conflates the two will mis-route an identity
|
|
186
264
|
challenge as a missing-record error.
|
|
@@ -245,13 +323,14 @@ Two limits that follow from the same rule:
|
|
|
245
323
|
**Server-derived (Response-only).**
|
|
246
324
|
|
|
247
325
|
```
|
|
248
|
-
status enum — 'verified'
|
|
249
|
-
verified_at string — ISO 8601 timestamp of the verification
|
|
250
|
-
errors object — present only when status === 'not_verified';
|
|
251
|
-
maps field name to an array of failure reasons
|
|
326
|
+
status enum — 'verified', its only value
|
|
327
|
+
verified_at string — ISO 8601 timestamp of the verification
|
|
252
328
|
```
|
|
253
329
|
|
|
254
|
-
|
|
330
|
+
There is no `errors` field on the response: a failed check is a
|
|
331
|
+
thrown 422 (§4), never a body.
|
|
332
|
+
|
|
333
|
+
**Caller-supplied (round-trip in the verified answer).**
|
|
255
334
|
|
|
256
335
|
```
|
|
257
336
|
legal_entity_id required UUID — the master record to verify against
|
|
@@ -270,35 +349,35 @@ legal_entity_id required UUID — the master record to verify against
|
|
|
270
349
|
|
|
271
350
|
## 4. Error envelopes
|
|
272
351
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
|
278
|
-
|
|
279
|
-
|
|
|
280
|
-
| `/
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
352
|
+
Every error `.verify` returns is the standard JSON:API envelope
|
|
353
|
+
per `errors.md`, thrown as `AlveraApiError`. There is no second
|
|
354
|
+
shape. Common rejections:
|
|
355
|
+
|
|
356
|
+
| HTTP | `source.pointer` | Cause |
|
|
357
|
+
|------|---------------------------|-------|
|
|
358
|
+
| 422 | `/<field>` | a field you sent did not match the master record — one entry per field (§2 lists the pointers and details) |
|
|
359
|
+
| 422 | `/base` | no field that can stand alone as evidence was supplied (§2) — including a body of only `email`, `middle_name` or the other five that count only beside one, or only retired fields (`given_name`, `family_name`, `birth_date`, `gender`), which are ignored |
|
|
360
|
+
| 422 | `/legal_entity_id` | `legal_entity_id` missing from the body |
|
|
361
|
+
| 422 | `/identifiers/0/system` | missing system on an identifier entry |
|
|
362
|
+
| 422 | `/identifiers/0/value` | missing value on an identifier entry |
|
|
363
|
+
| 404 | `""` (empty) | no legal entity has this id — `title: 'Not Found'`, `detail: 'Subject not found'` |
|
|
364
|
+
|
|
365
|
+
A subject that does not verify and a body the platform refuses
|
|
366
|
+
arrive the same way: a 422 whose entries all carry
|
|
367
|
+
`title: 'Invalid value'`. Read the field from `source.pointer`.
|
|
368
|
+
There is no `errors` map keyed by field name, and a failure body
|
|
369
|
+
carries no `status`, `legal_entity_id` or `verified_at`.
|
|
370
|
+
|
|
371
|
+
`system` and `value` on an identifier are required by the request
|
|
372
|
+
schema, so a missing one is refused before verification runs, and
|
|
373
|
+
its pointer carries the entry's index. An embed the verifier
|
|
374
|
+
itself rejects points at the embed's field without an index: an
|
|
375
|
+
address sent without a country is `/addresses/country`, not
|
|
376
|
+
`/addresses/0/country`. `id_type` and `country` on an identifier
|
|
377
|
+
are optional. Either way it is a 422, never a 500.
|
|
378
|
+
|
|
379
|
+
Branch on `_httpStatus`: 422 for a failed check or a refused body,
|
|
380
|
+
404 for a subject that does not exist (§6 gotcha 2).
|
|
302
381
|
|
|
303
382
|
## 5. Lifecycle
|
|
304
383
|
|
|
@@ -344,26 +423,30 @@ through `api.mdm`.
|
|
|
344
423
|
|
|
345
424
|
## 6. Gotchas
|
|
346
425
|
|
|
347
|
-
1. **
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
`
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
426
|
+
1. **A subject that does not verify throws; there is no
|
|
427
|
+
`not_verified` body.** `data` is only ever the verified answer.
|
|
428
|
+
Every failed check is a thrown `AlveraApiError` with
|
|
429
|
+
`_httpStatus === 422`, one entry per mismatched field. Code
|
|
430
|
+
written for the old `{ status: 'not_verified', errors: {…} }`
|
|
431
|
+
body never runs: its branch is unreachable and the 422 lands in
|
|
432
|
+
`catch`. Don't let generic 4xx handling swallow it — read each
|
|
433
|
+
entry's `source.pointer` to learn which fields failed.
|
|
434
|
+
|
|
435
|
+
2. **Subject missing → 404, identity mismatch → 422.** Both
|
|
436
|
+
throw, in the same envelope. A verify call against a deleted
|
|
437
|
+
or never-existed entity id throws a 404 (`title: 'Not Found'`,
|
|
438
|
+
empty pointer), not a 422. Branch on `_httpStatus` before
|
|
439
|
+
reading pointers, per `errors.md`.
|
|
440
|
+
|
|
441
|
+
3. **At least one field that can stand alone is required.** A body
|
|
442
|
+
with only `legal_entity_id` — or only fields that count just
|
|
443
|
+
beside another (`email`, `middle_name`, `nationality`, …), or
|
|
444
|
+
only retired fields like `given_name` or `birth_date`, which are
|
|
445
|
+
ignored — throws a 422 with one
|
|
446
|
+
entry: `source.pointer: '/base'`, `detail: 'at least one
|
|
447
|
+
verification field is required'`. This is enforced at runtime
|
|
448
|
+
rather than in the request schema, so the TypeScript type
|
|
449
|
+
permits the body but the wire rejects it.
|
|
367
450
|
|
|
368
451
|
4. **Unknown fields are ignored, not rejected.** There is one
|
|
369
452
|
field set (§1) and no domain scoping, but anything outside it
|
|
@@ -385,8 +468,9 @@ through `api.mdm`.
|
|
|
385
468
|
case, and trailing slashes in `system` URIs all matter.
|
|
386
469
|
Compare with the master record's identifier system strings
|
|
387
470
|
character-for-character; a `'http://hospital.org/mrn'` vs
|
|
388
|
-
`'http://hospital.org/mrn/'` mismatch
|
|
389
|
-
|
|
471
|
+
`'http://hospital.org/mrn/'` mismatch fails the check with an
|
|
472
|
+
`/identifiers` entry (`no matching identifier found`) even when
|
|
473
|
+
the value is correct.
|
|
390
474
|
`system` and `value` are the required pair. `id_type`
|
|
391
475
|
(`'digital_identifier'`, `'us_ssn'`, `'lei'`) and `country`
|
|
392
476
|
are optional — the per-domain rule that once made `id_type`
|