@alvera-ai/platform-sdk 0.14.0 → 0.15.0-next.g09659bd
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/action_status_updaters.md +200 -36
- package/.agent/cookbook/action-status-updaters.md +39 -0
- package/.agent/cookbook/appointment-review-sms-workflow.md +9 -0
- package/.agent/cookbook/birthday-greeting-sms-trigger.md +10 -1
- package/.agent/cookbook/bulk-ingest.md +9 -0
- package/.agent/cookbook/contact-us-triage-with-llm.md +1 -1
- package/.agent/cookbook/dunning-sms-for-delinquent.md +10 -1
- package/.agent/cookbook/kyc-notification-on-account-activation.md +10 -1
- package/.agent/cookbook/marketing-campaign-send.md +30 -3
- package/.agent/cookbook/paginated-restapi-poller.md +92 -21
- package/.agent/cookbook/rest-fetch.md +9 -0
- package/.agent/cookbook/sanctions-screening-with-agent-review.md +10 -1
- package/.agent/cookbook/score-leads-with-llm-categorization.md +1 -1
- package/.agent/cookbook/triage-prospects-by-priority.md +10 -1
- package/.agent/cookbook/welcome-sms-for-customers.md +10 -1
- package/.agent/datalakes.md +44 -3
- package/.agent/mdm.md +45 -2
- package/dist/index.d.mts +363 -12
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +61 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
|
@@ -82,11 +82,50 @@ updater_body_type: 'cloud_watch_request'
|
|
|
82
82
|
follow the same `<verb>_request` naming and carry the fields
|
|
83
83
|
their target API requires.)
|
|
84
84
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
`
|
|
88
|
-
|
|
89
|
-
|
|
85
|
+
**Four poll-contract fields are required on EVERY updater type**, and
|
|
86
|
+
they live at the **root of the create body** — not inside
|
|
87
|
+
`updater_body`:
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
events_template REQUIRED embed — { type, body } — event-list
|
|
91
|
+
extraction; maps the provider's
|
|
92
|
+
own id into `external_id`
|
|
93
|
+
pagination_context_template REQUIRED embed — { type, body } — must render JSON
|
|
94
|
+
carrying a boolean `has_next`
|
|
95
|
+
events_output_schema REQUIRED object — JSON Schema; FLOORED at an array
|
|
96
|
+
whose items list `external_id`
|
|
97
|
+
in `required`
|
|
98
|
+
pagination_context_output_schema REQUIRED object — JSON Schema; FLOORED at an object
|
|
99
|
+
listing `has_next` in `required`
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Mind the level.** These sit at the ASU root, deliberately — the
|
|
103
|
+
`restapi_request` body embed is shared with the data-activation
|
|
104
|
+
`tool_call`, and putting them there would force every DAC to supply
|
|
105
|
+
them too. A `restapi_request` body carries its own `events_template`;
|
|
106
|
+
that is a different field at a different level, and supplying it does
|
|
107
|
+
**not** satisfy the root requirement.
|
|
108
|
+
|
|
109
|
+
This was `restapi`-only while CloudWatch was a separate loop with no
|
|
110
|
+
templates of its own. It is not one any more: every source now renders
|
|
111
|
+
through `events_template` + `pagination_context_template` and
|
|
112
|
+
terminates on the same `has_next`, so a `cloud_watch` updater declares
|
|
113
|
+
the same contract a REST one does — the platform just ships the
|
|
114
|
+
templates it normally points at. An older `cloud_watch` manifest that
|
|
115
|
+
predates the change 422s on its next create.
|
|
116
|
+
|
|
117
|
+
> ⚠ **The OpenAPI schema is behind the changeset on two of these.**
|
|
118
|
+
> `events_output_schema` and `pagination_context_output_schema` are
|
|
119
|
+
> generated **optional**, but `validate_expected_output_schemas/1`
|
|
120
|
+
> requires them unconditionally. Omit them and the type-checker is
|
|
121
|
+
> happy while the server returns 422. Treat all four as required until
|
|
122
|
+
> the spec catches up.
|
|
123
|
+
|
|
124
|
+
The floors exist for a reason. `external_id` is what the apply path
|
|
125
|
+
pops off each rendered event to find the message and action-log rows to
|
|
126
|
+
update — an events contract that does not demand it accepts a poll that
|
|
127
|
+
fetches pages forever and reconciles nothing. `has_next` is the key the
|
|
128
|
+
page loop ends on.
|
|
90
129
|
|
|
91
130
|
`start_time` / `end_time` are Liquid, and they **must render to
|
|
92
131
|
unix milliseconds** — that is what the poll request takes. Use the
|
|
@@ -117,6 +156,10 @@ scope — do not loop.
|
|
|
117
156
|
{"external_id": "{{ notification.messageId }}", "status": "delivered"}
|
|
118
157
|
```
|
|
119
158
|
|
|
159
|
+
> **Shape only — do not copy this mapping.** A hardcoded `"delivered"`
|
|
160
|
+
> records every event as delivered, failures included. Real bodies
|
|
161
|
+
> branch on the provider's status; see the SNS and MMS sections below.
|
|
162
|
+
|
|
120
163
|
It must render a **flat** JSON object:
|
|
121
164
|
|
|
122
165
|
```
|
|
@@ -139,15 +182,107 @@ events for messages this updater doesn't own.
|
|
|
139
182
|
Custom Liquid filters (`json_escape`, `e164`, `to_json`, ...) are
|
|
140
183
|
available here, as everywhere.
|
|
141
184
|
|
|
142
|
-
###
|
|
185
|
+
### SMS-over-SNS delivery tracking — two `custom` bodies
|
|
186
|
+
|
|
187
|
+
The SNS SMS delivery receipt is **binary and single-shot**: one terminal
|
|
188
|
+
outcome, `status` of `SUCCESS` or `FAILURE`. Its event shape is
|
|
189
|
+
**disjoint from EUM's** — `status`, `notification.messageId`,
|
|
190
|
+
`notification.timestamp`, `delivery.phoneCarrier`,
|
|
191
|
+
`delivery.providerResponse` all sit at the root. Do not carry an EUM
|
|
192
|
+
template over to SNS or vice versa.
|
|
193
|
+
|
|
194
|
+
Two convenient consequences, both unlike MMS:
|
|
195
|
+
|
|
196
|
+
- `delivered_at` is `notification.timestamp` — a UTC
|
|
197
|
+
`"YYYY-MM-DD HH:MM:SS.sss"` string Ecto casts directly, so **no date
|
|
198
|
+
filter is needed** (EUM needs `from_unix_ms` on its Unix-ms field).
|
|
199
|
+
- the DLR **does** surface the US carrier as `delivery.phoneCarrier`.
|
|
200
|
+
|
|
201
|
+
**`message_config`:**
|
|
202
|
+
|
|
203
|
+
```liquid
|
|
204
|
+
{"external_id": "{{ notification.messageId }}", "status": "{% if status == 'SUCCESS' %}delivered{% else %}failed{% endif %}", "status_description": "{{ delivery.providerResponse | json_escape }}"{% if status == 'SUCCESS' and notification.timestamp %}, "delivered_at": "{{ notification.timestamp }}"{% endif %}{% unless status == 'SUCCESS' %}, "failure_reason": "{{ status | json_escape }}"{% endunless %}{% if delivery.phoneCarrier and delivery.phoneCarrier != '' %}, "sms_carrier": "{{ delivery.phoneCarrier | json_escape }}"{% endif %}}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**`action_log_config`:**
|
|
208
|
+
|
|
209
|
+
```liquid
|
|
210
|
+
{"external_id": "{{ notification.messageId }}", "status": "{% if status == 'SUCCESS' %}delivered{% else %}failed{% endif %}", "status_description": "{{ delivery.providerResponse | json_escape }}"}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
`status_description` is `delivery.providerResponse` verbatim on both
|
|
214
|
+
sides — the carrier's own text, on success and failure alike. Because
|
|
215
|
+
SNS's status is binary, `failure_reason` can only ever be `"FAILURE"`;
|
|
216
|
+
the granular carrier text stays in `status_description`.
|
|
217
|
+
|
|
218
|
+
**Note how the two SNS bodies differ from each other**: same status
|
|
219
|
+
mapping, fewer emitted fields on the action log (no `delivered_at`,
|
|
220
|
+
`sms_carrier`, or `failure_reason` — those are message concerns). That
|
|
221
|
+
is a *different* reason from MMS's, where the two also disagree on the
|
|
222
|
+
mapping itself. Both channels need two bodies; they need them for
|
|
223
|
+
different reasons.
|
|
224
|
+
|
|
225
|
+
### MMS delivery tracking needs TWO different templates — author both as `custom`
|
|
226
|
+
|
|
227
|
+
MMS delivery tracking is a `cloud_watch` updater. Author **both**
|
|
228
|
+
`message_config` and `action_log_config` as `custom` bodies — the two
|
|
229
|
+
render *different* JSON from the same event, so one body cannot serve
|
|
230
|
+
both. This is the single most-missed thing about updaters.
|
|
143
231
|
|
|
144
|
-
|
|
145
|
-
`
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
`
|
|
150
|
-
|
|
232
|
+
**Why they differ.** A message's status vocabulary includes
|
|
233
|
+
`customer_rejected`; an action log's does not. A recipient or carrier
|
|
234
|
+
rejection (`SPAM` / `BLOCKED` / `CARRIER_BLOCKED`) is a message-level
|
|
235
|
+
outcome — the action *did* deliver — so the action-log render must
|
|
236
|
+
**NO-OP** on those events while the message render records
|
|
237
|
+
`customer_rejected`. The message side also carries `delivered_at`,
|
|
238
|
+
`failure_reason`, and `sms_carrier`; only `status` +
|
|
239
|
+
`status_description` are ever applied to an action log.
|
|
240
|
+
|
|
241
|
+
AWS End User Messaging (`sms-voice`) event fields: top-level
|
|
242
|
+
`messageId`, `eventType` (`MEDIA_*`), `messageStatus`,
|
|
243
|
+
`messageStatusDescription`, `eventTimestamp` (Unix ms), `isFinal`.
|
|
244
|
+
|
|
245
|
+
**`message_config`** — map every status, emit the extra fields:
|
|
246
|
+
|
|
247
|
+
```liquid
|
|
248
|
+
{% case messageStatus %}
|
|
249
|
+
{% when 'DELIVERED' %}{% assign mapped_status = 'delivered' %}
|
|
250
|
+
{% when 'SUCCESSFUL' %}{% assign mapped_status = 'sent' %}
|
|
251
|
+
{% when 'PENDING' %}{% assign mapped_status = 'sent' %}
|
|
252
|
+
{% when 'QUEUED' %}{% assign mapped_status = 'queued' %}
|
|
253
|
+
{% when 'SPAM' %}{% assign mapped_status = 'customer_rejected' %}
|
|
254
|
+
{% when 'BLOCKED' %}{% assign mapped_status = 'customer_rejected' %}
|
|
255
|
+
{% when 'CARRIER_BLOCKED' %}{% assign mapped_status = 'customer_rejected' %}
|
|
256
|
+
{% else %}{% assign mapped_status = 'failed' %}
|
|
257
|
+
{% endcase %}
|
|
258
|
+
{"external_id": "{{ messageId }}", "status": "{{ mapped_status }}", "status_description": "{{ messageStatusDescription | json_escape }}"{% if messageStatus == 'DELIVERED' and eventTimestamp %}, "delivered_at": "{{ eventTimestamp | from_unix_ms }}"{% endif %}{% if mapped_status == 'failed' or mapped_status == 'customer_rejected' %}, "failure_reason": "{{ messageStatus | json_escape }}"{% endif %}{% if carrierName and carrierName != '' %}, "sms_carrier": "{{ carrierName | json_escape }}"{% endif %}}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
**`action_log_config`** — the rejection statuses fall through to a
|
|
262
|
+
bare `external_id`, which applies nothing:
|
|
263
|
+
|
|
264
|
+
```liquid
|
|
265
|
+
{% assign al_status = '' %}{% case messageStatus %}{% when 'DELIVERED' %}{% assign al_status = 'delivered' %}{% when 'SUCCESSFUL' %}{% assign al_status = 'sent' %}{% when 'PENDING' %}{% assign al_status = 'sent' %}{% when 'QUEUED' %}{% assign al_status = 'queued' %}{% when 'SPAM', 'BLOCKED', 'CARRIER_BLOCKED' %}{% else %}{% assign al_status = 'failed' %}{% endcase %}{"external_id": "{{ messageId }}"{% if al_status != '' %}, "status": "{{ al_status }}", "status_description": "{{ messageStatusDescription | json_escape }}"{% endif %}}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Note the `{% when 'SPAM', 'BLOCKED', 'CARRIER_BLOCKED' %}` arm is
|
|
269
|
+
deliberately **empty** — it leaves `al_status` blank, so the render
|
|
270
|
+
emits `external_id` alone and the action log is untouched.
|
|
271
|
+
|
|
272
|
+
**Capture every event, not just the terminal one.** 2–3 events arrive
|
|
273
|
+
per message — interim `MEDIA_QUEUED` / `MEDIA_PENDING` /
|
|
274
|
+
`MEDIA_SUCCESSFUL` (`isFinal: false`), then one terminal event
|
|
275
|
+
(`isFinal: true`), up to 72h late and out of order. Apply is
|
|
276
|
+
idempotent and a monotonic guard only ever advances `status`, so a
|
|
277
|
+
late or interim lower-rank event can never demote a message that
|
|
278
|
+
already reached a higher state. Poll a window that overlaps
|
|
279
|
+
deliberately; nothing is dropped.
|
|
280
|
+
|
|
281
|
+
`queued_at` and `sent_at` are **your outbound pipeline's** times, set
|
|
282
|
+
when the platform enqueues and dispatches — a delivery poll never
|
|
283
|
+
overwrites them. Only `delivered_at` comes from the carrier event
|
|
284
|
+
(`DELIVERED`'s own `eventTimestamp` via `from_unix_ms`), so
|
|
285
|
+
re-polling a wide window does not drift it.
|
|
151
286
|
|
|
152
287
|
### Carrier information is available for SMS, not MMS
|
|
153
288
|
|
|
@@ -160,12 +295,15 @@ events — not something the platform chooses to drop:
|
|
|
160
295
|
under `delivery.phoneCarrier` (e.g. `"T-mobile USA Inc."`, with
|
|
161
296
|
numeric `mcc`/`mnc`). The SNS status template maps it to
|
|
162
297
|
`sms_carrier`.
|
|
163
|
-
- **MMS via AWS End User Messaging (`sms-voice`)** — the
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
298
|
+
- **MMS via AWS End User Messaging (`sms-voice`)** — the event *can*
|
|
299
|
+
carry `carrierName`, but it is DLR-populated and comes back
|
|
300
|
+
**absent for US destinations** (the US DLR does not surface it),
|
|
301
|
+
unlike the SNS path's `phoneCarrier`, whose US DLR does. So map it
|
|
302
|
+
when present — `{% if carrierName and carrierName != '' %}` — and
|
|
303
|
+
expect `sms_carrier` to stay null for US long-code traffic.
|
|
304
|
+
Verified against real AWS delivery events: the terminal
|
|
305
|
+
`MEDIA_DELIVERED` event for a US destination exposes
|
|
306
|
+
`totalCarrierFee` (a cost) and no `carrierName`.
|
|
169
307
|
|
|
170
308
|
AWS's Nov-2024 EventBridge-integration announcement states MMS
|
|
171
309
|
delivery events carry carrier information in EventBridge, but the
|
|
@@ -291,8 +429,16 @@ datalake_id required UUID
|
|
|
291
429
|
updater_body required embed — { updater_body_type, ... }
|
|
292
430
|
message_config required embed — { type, body }
|
|
293
431
|
action_log_config required embed — { type, body } (required on create AND update)
|
|
432
|
+
|
|
433
|
+
events_template required embed — EVERY updater type, not just restapi
|
|
434
|
+
pagination_context_template required embed — EVERY updater type
|
|
435
|
+
events_output_schema required object — JSON Schema
|
|
436
|
+
pagination_context_output_schema required object — JSON Schema, floored at
|
|
437
|
+
`has_next` in `required`
|
|
294
438
|
```
|
|
295
439
|
|
|
440
|
+
The last four widened from `restapi`-only to universal — see §2.
|
|
441
|
+
|
|
296
442
|
**Write-only (Request-only).** None.
|
|
297
443
|
|
|
298
444
|
## 4. Error envelopes
|
|
@@ -449,31 +595,49 @@ while (Date.now() < deadline) {
|
|
|
449
595
|
// row.last_run_status / last_run_error now describe THIS run
|
|
450
596
|
```
|
|
451
597
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
run-outcome
|
|
455
|
-
|
|
598
|
+
The ASU now **does** have a per-run log — the sibling of a data
|
|
599
|
+
activation client's `logs` and a workflow's `batchLogs`. There are two
|
|
600
|
+
run-outcome surfaces and they answer different questions:
|
|
601
|
+
|
|
602
|
+
```
|
|
603
|
+
actionStatusUpdaters.runLogs.list(tenantSlug, datalakeSlug, updaterId, query?)
|
|
604
|
+
actionStatusUpdaters.runLogs.get(tenantSlug, datalakeSlug, updaterId, logId)
|
|
605
|
+
```
|
|
606
|
+
|
|
607
|
+
`.list` is Flop-paginated like every other list — same `page` /
|
|
608
|
+
`page_size` / `filters` / `order_by` vocabulary (see `async.md`).
|
|
609
|
+
|
|
610
|
+
**The summary surface — four fields on the updater row, plus the halt
|
|
611
|
+
flag:**
|
|
456
612
|
|
|
457
613
|
```
|
|
458
614
|
last_run_at when the poll worker last made progress
|
|
459
|
-
last_run_status 'ok' | '
|
|
460
|
-
last_run_error why the last run failed
|
|
615
|
+
last_run_status 'ok' | 'error'
|
|
616
|
+
last_run_error why the last run failed
|
|
461
617
|
last_run_events_found events fetched so far in the current run
|
|
462
618
|
status 'active' | 'cycle_detected'
|
|
463
619
|
```
|
|
464
620
|
|
|
465
|
-
**
|
|
466
|
-
|
|
621
|
+
**The per-run surface — one row per poll run, from `runLogs`:**
|
|
622
|
+
|
|
623
|
+
```
|
|
624
|
+
status 'ok' | 'partial' | 'error' ← the outcome of THIS page
|
|
625
|
+
```
|
|
467
626
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
627
|
+
**`'partial'` lives on the run log, NOT on the updater — this is the
|
|
628
|
+
trap.** A truncated fetch means the window was not fully read and the
|
|
629
|
+
events it missed are the **newest** ones. The updater's own
|
|
630
|
+
`last_run_status` cannot tell you that: it carries `'ok' | 'error'`
|
|
631
|
+
only, so a truncated run reads as `'ok'` there. If completeness of
|
|
632
|
+
reconciliation matters to you, read the run log — checking
|
|
633
|
+
`last_run_status === 'ok'` on the updater is **not** sufficient, and a
|
|
634
|
+
client that treats it as sufficient silently accepts an incomplete
|
|
635
|
+
reconciliation.
|
|
636
|
+
|
|
637
|
+
- `'ok'` — the page completed and read its whole slice of the window.
|
|
638
|
+
- `'partial'` — the page completed but its provider fetch was
|
|
639
|
+
**truncated**. Only visible on the run-log row.
|
|
640
|
+
- `'error'` — the run failed; `last_run_error` on the updater says why.
|
|
477
641
|
|
|
478
642
|
`last_run_events_found` is the event count for the current run; because
|
|
479
643
|
`refresh` is asynchronous (below), a read taken before the run finishes can
|
|
@@ -130,6 +130,28 @@ const { data: asu } = await api.actionStatusUpdaters.create(tenantSlug, datalake
|
|
|
130
130
|
start_time: '{{ now_msec | minutes_ago: 45 }}',
|
|
131
131
|
end_time: '{{ now_msec }}',
|
|
132
132
|
},
|
|
133
|
+
// The poll contract — REQUIRED on every updater type, and at the body ROOT,
|
|
134
|
+
// not inside `updater_body`. For an AWS poller, point at the templates the
|
|
135
|
+
// platform already ships rather than authoring Liquid: `events_template`
|
|
136
|
+
// projects each log event to `{ external_id, ... }`, and
|
|
137
|
+
// `pagination_context_template` renders the `has_next` the page loop ends on.
|
|
138
|
+
events_template: { type: 'system', path: 'status_poller/aws_sns/sms_events' },
|
|
139
|
+
pagination_context_template: {
|
|
140
|
+
type: 'system',
|
|
141
|
+
path: 'status_poller/aws_cloudwatch/pagination',
|
|
142
|
+
},
|
|
143
|
+
// The two floors. `external_id` is what the apply path pops off each rendered
|
|
144
|
+
// event to find the row it updates; `has_next` is what terminates paging.
|
|
145
|
+
// Demand more of your provider if you know it — you may not demand less.
|
|
146
|
+
events_output_schema: {
|
|
147
|
+
type: 'array',
|
|
148
|
+
items: { type: 'object', required: ['external_id'] },
|
|
149
|
+
},
|
|
150
|
+
pagination_context_output_schema: {
|
|
151
|
+
type: 'object',
|
|
152
|
+
required: ['has_next'],
|
|
153
|
+
properties: { has_next: { type: 'boolean' } },
|
|
154
|
+
},
|
|
133
155
|
message_config: {
|
|
134
156
|
type: 'custom',
|
|
135
157
|
body: '{"external_id": "{{ notification.messageId }}", "status": "delivered"}',
|
|
@@ -167,6 +189,23 @@ const { data: ck } = await api.actionStatusUpdaters.checksum(tenantSlug, datalak
|
|
|
167
189
|
start_time: '{{ now_msec | minutes_ago: 45 }}',
|
|
168
190
|
end_time: '{{ now_msec }}',
|
|
169
191
|
},
|
|
192
|
+
// Byte-identical to the create body above — the checksum is a fingerprint of
|
|
193
|
+
// the whole body, so omitting the poll contract here would compute a
|
|
194
|
+
// different one and read as drift on a resource that never changed.
|
|
195
|
+
events_template: { type: 'system', path: 'status_poller/aws_sns/sms_events' },
|
|
196
|
+
pagination_context_template: {
|
|
197
|
+
type: 'system',
|
|
198
|
+
path: 'status_poller/aws_cloudwatch/pagination',
|
|
199
|
+
},
|
|
200
|
+
events_output_schema: {
|
|
201
|
+
type: 'array',
|
|
202
|
+
items: { type: 'object', required: ['external_id'] },
|
|
203
|
+
},
|
|
204
|
+
pagination_context_output_schema: {
|
|
205
|
+
type: 'object',
|
|
206
|
+
required: ['has_next'],
|
|
207
|
+
properties: { has_next: { type: 'boolean' } },
|
|
208
|
+
},
|
|
170
209
|
message_config: {
|
|
171
210
|
type: 'custom',
|
|
172
211
|
body: '{"external_id": "{{ notification.messageId }}", "status": "delivered"}',
|
|
@@ -356,6 +356,15 @@ const dacResp = await api.dataActivationClients.create(tenantSlug, datalakeSlug,
|
|
|
356
356
|
tool_id: ctx.manualUploadToolId,
|
|
357
357
|
data_source_id: dataSourceId,
|
|
358
358
|
tool_call: { tool_call_type: 'manual_upload' },
|
|
359
|
+
// REQUIRED on every DAC create, whatever the `tool_call` protocol. A
|
|
360
|
+
// manual_upload client is push-based — it never fetches and never pages —
|
|
361
|
+
// but the poll contract is declared uniformly, and `has_next` is the key
|
|
362
|
+
// the fetch loop would terminate on for a protocol that does page.
|
|
363
|
+
pagination_context_output_schema: {
|
|
364
|
+
type: 'object',
|
|
365
|
+
required: ['has_next'],
|
|
366
|
+
properties: { has_next: { type: 'boolean' } },
|
|
367
|
+
},
|
|
359
368
|
interoperability_contract_ids: [interopContractId, ctx.appointmentContractId],
|
|
360
369
|
})
|
|
361
370
|
dacId = dacResp.data.id!
|
|
@@ -175,7 +175,7 @@ const workflowResp = await api.workflows.create(tenantSlug, datalakeSlug, {
|
|
|
175
175
|
position: 0,
|
|
176
176
|
trigger_template: TRIGGER_TEMPLATE,
|
|
177
177
|
idempotency_template:
|
|
178
|
-
'{{ subject_id }}-{{ workflow_id }}-{{ action_id }}
|
|
178
|
+
'{{ subject_id }}-{{ workflow_id }}-{{ action_id }}',
|
|
179
179
|
connected_app_id: connectedAppId,
|
|
180
180
|
connected_app_route: '/forms/birthday-greeting',
|
|
181
181
|
connected_app_metadata_template:
|
|
@@ -285,6 +285,15 @@ const dacResp = await api.dataActivationClients.create(tenantSlug, datalakeSlug,
|
|
|
285
285
|
tool_id: ctx.manualUploadToolId,
|
|
286
286
|
data_source_id: dataSourceId,
|
|
287
287
|
tool_call: { tool_call_type: 'manual_upload' },
|
|
288
|
+
// REQUIRED on every DAC create, whatever the `tool_call` protocol. A
|
|
289
|
+
// manual_upload client is push-based — it never fetches and never pages —
|
|
290
|
+
// but the poll contract is declared uniformly, and `has_next` is the key
|
|
291
|
+
// the fetch loop would terminate on for a protocol that does page.
|
|
292
|
+
pagination_context_output_schema: {
|
|
293
|
+
type: 'object',
|
|
294
|
+
required: ['has_next'],
|
|
295
|
+
properties: { has_next: { type: 'boolean' } },
|
|
296
|
+
},
|
|
288
297
|
interoperability_contract_ids: [interopContractId],
|
|
289
298
|
})
|
|
290
299
|
dacId = dacResp.data.id!
|
|
@@ -113,6 +113,15 @@ const { data: dac } = await api.dataActivationClients.create(tenantSlug, datalak
|
|
|
113
113
|
tool_id: ctx.manualUploadToolId,
|
|
114
114
|
data_source_id: dataSourceId,
|
|
115
115
|
tool_call: { tool_call_type: 'manual_upload' },
|
|
116
|
+
// REQUIRED on every DAC create, whatever the `tool_call` protocol. A
|
|
117
|
+
// manual_upload client is push-based — it never fetches and never pages —
|
|
118
|
+
// but the poll contract is declared uniformly, and `has_next` is the key
|
|
119
|
+
// the fetch loop would terminate on for a protocol that does page.
|
|
120
|
+
pagination_context_output_schema: {
|
|
121
|
+
type: 'object',
|
|
122
|
+
required: ['has_next'],
|
|
123
|
+
properties: { has_next: { type: 'boolean' } },
|
|
124
|
+
},
|
|
116
125
|
interoperability_contract_ids: [interopContractId],
|
|
117
126
|
})
|
|
118
127
|
dacId = dac.id!
|
|
@@ -326,7 +326,7 @@ const workflowResp = await api.workflows.create(tenantSlug, datalakeSlug, {
|
|
|
326
326
|
tool_id: toolId,
|
|
327
327
|
position: 0,
|
|
328
328
|
trigger_template: 'now',
|
|
329
|
-
idempotency_template: `{{ subject_id }}-{{ action_id }}-${bucket}
|
|
329
|
+
idempotency_template: `{{ subject_id }}-{{ action_id }}-${bucket}`,
|
|
330
330
|
tool_call: {
|
|
331
331
|
tool_call_type: 'sms_request',
|
|
332
332
|
to: { type: 'custom', body: '+15551234567' },
|
|
@@ -189,7 +189,7 @@ const workflowResp = await api.workflows.create(tenantSlug, datalakeSlug, {
|
|
|
189
189
|
decision_key: DECISION_KEY,
|
|
190
190
|
position: 0,
|
|
191
191
|
trigger_template: 'now',
|
|
192
|
-
idempotency_template: '{{ customer_id }}-{{ decision_key }}
|
|
192
|
+
idempotency_template: '{{ customer_id }}-{{ decision_key }}',
|
|
193
193
|
connected_app_id: connectedAppId,
|
|
194
194
|
connected_app_route: '/portal/pay',
|
|
195
195
|
connected_app_metadata_template:
|
|
@@ -303,6 +303,15 @@ const dacResp = await api.dataActivationClients.create(tenantSlug, datalakeSlug,
|
|
|
303
303
|
tool_id: ctx.manualUploadToolId,
|
|
304
304
|
data_source_id: dataSourceId,
|
|
305
305
|
tool_call: { tool_call_type: 'manual_upload' },
|
|
306
|
+
// REQUIRED on every DAC create, whatever the `tool_call` protocol. A
|
|
307
|
+
// manual_upload client is push-based — it never fetches and never pages —
|
|
308
|
+
// but the poll contract is declared uniformly, and `has_next` is the key
|
|
309
|
+
// the fetch loop would terminate on for a protocol that does page.
|
|
310
|
+
pagination_context_output_schema: {
|
|
311
|
+
type: 'object',
|
|
312
|
+
required: ['has_next'],
|
|
313
|
+
properties: { has_next: { type: 'boolean' } },
|
|
314
|
+
},
|
|
306
315
|
interoperability_contract_ids: [interopContractId],
|
|
307
316
|
})
|
|
308
317
|
dacId = dacResp.data.id!
|
|
@@ -188,7 +188,7 @@ const workflowResp = await api.workflows.create(tenantSlug, datalakeSlug, {
|
|
|
188
188
|
decision_key: DECISION_KEY,
|
|
189
189
|
position: 0,
|
|
190
190
|
trigger_template: 'now',
|
|
191
|
-
idempotency_template: '{{ payment_account.id }}-{{ decision_key }}
|
|
191
|
+
idempotency_template: '{{ payment_account.id }}-{{ decision_key }}',
|
|
192
192
|
connected_app_id: connectedAppId,
|
|
193
193
|
connected_app_route: '/portal/kyc',
|
|
194
194
|
connected_app_metadata_template: '{"payment_account_id":"{{ payment_account.id }}"}',
|
|
@@ -300,6 +300,15 @@ const dacResp = await api.dataActivationClients.create(tenantSlug, datalakeSlug,
|
|
|
300
300
|
tool_id: ctx.manualUploadToolId,
|
|
301
301
|
data_source_id: dataSourceId,
|
|
302
302
|
tool_call: { tool_call_type: 'manual_upload' },
|
|
303
|
+
// REQUIRED on every DAC create, whatever the `tool_call` protocol. A
|
|
304
|
+
// manual_upload client is push-based — it never fetches and never pages —
|
|
305
|
+
// but the poll contract is declared uniformly, and `has_next` is the key
|
|
306
|
+
// the fetch loop would terminate on for a protocol that does page.
|
|
307
|
+
pagination_context_output_schema: {
|
|
308
|
+
type: 'object',
|
|
309
|
+
required: ['has_next'],
|
|
310
|
+
properties: { has_next: { type: 'boolean' } },
|
|
311
|
+
},
|
|
303
312
|
interoperability_contract_ids: [interopContractId],
|
|
304
313
|
})
|
|
305
314
|
dacId = dacResp.data.id!
|
|
@@ -273,7 +273,7 @@ const ROSTER_MDM = `{% assign p = msg %}
|
|
|
273
273
|
"legal_entity_type": "business",
|
|
274
274
|
"business_name": "{{ p.business_name | json_escape }}",
|
|
275
275
|
"identifiers": [
|
|
276
|
-
{"system": "{{ p.source_uri | json_escape }}", "value": "{{ p.direct_customer_id | json_escape }}"}
|
|
276
|
+
{"system": "{{ p.source_uri | json_escape }}", "type": "digital_identifier", "value": "{{ p.direct_customer_id | json_escape }}"}
|
|
277
277
|
]
|
|
278
278
|
}`
|
|
279
279
|
|
|
@@ -304,6 +304,15 @@ const { data: rosterDac } = await api.dataActivationClients.create(tenantSlug, d
|
|
|
304
304
|
tool_id: ctx.uploadToolId,
|
|
305
305
|
data_source_id: dataSourceId,
|
|
306
306
|
tool_call: { tool_call_type: 'manual_upload' },
|
|
307
|
+
// REQUIRED on every DAC create, whatever the `tool_call` protocol. A
|
|
308
|
+
// manual_upload client is push-based — it never fetches and never pages —
|
|
309
|
+
// but the poll contract is declared uniformly, and `has_next` is the key
|
|
310
|
+
// the fetch loop would terminate on for a protocol that does page.
|
|
311
|
+
pagination_context_output_schema: {
|
|
312
|
+
type: 'object',
|
|
313
|
+
required: ['has_next'],
|
|
314
|
+
properties: { has_next: { type: 'boolean' } },
|
|
315
|
+
},
|
|
307
316
|
interoperability_contract_ids: [rosterLe.id!, rosterGt.id!],
|
|
308
317
|
})
|
|
309
318
|
ctx.rosterDacSlug = rosterDac.slug!
|
|
@@ -376,7 +385,7 @@ const AUDIENCE_MDM = `{% assign p = msg %}
|
|
|
376
385
|
{% if first_name != "" %}"first_name": "{{ first_name | json_escape }}",{% endif %}
|
|
377
386
|
{% if last_name != "" %}"last_name": "{{ last_name | json_escape }}",{% endif %}
|
|
378
387
|
"identifiers": [
|
|
379
|
-
{"system": "{{ p.source_uri | json_escape }}", "value": "{{ handle | json_escape }}"}
|
|
388
|
+
{"system": "{{ p.source_uri | json_escape }}", "type": "digital_identifier", "value": "{{ handle | json_escape }}"}
|
|
380
389
|
]
|
|
381
390
|
}`
|
|
382
391
|
|
|
@@ -406,6 +415,15 @@ const { data: audienceDac } = await api.dataActivationClients.create(tenantSlug,
|
|
|
406
415
|
tool_id: ctx.uploadToolId,
|
|
407
416
|
data_source_id: dataSourceId,
|
|
408
417
|
tool_call: { tool_call_type: 'manual_upload' },
|
|
418
|
+
// REQUIRED on every DAC create, whatever the `tool_call` protocol. A
|
|
419
|
+
// manual_upload client is push-based — it never fetches and never pages —
|
|
420
|
+
// but the poll contract is declared uniformly, and `has_next` is the key
|
|
421
|
+
// the fetch loop would terminate on for a protocol that does page.
|
|
422
|
+
pagination_context_output_schema: {
|
|
423
|
+
type: 'object',
|
|
424
|
+
required: ['has_next'],
|
|
425
|
+
properties: { has_next: { type: 'boolean' } },
|
|
426
|
+
},
|
|
409
427
|
interoperability_contract_ids: [audienceLe.id!, audienceGt.id!],
|
|
410
428
|
})
|
|
411
429
|
dacId = audienceDac.id!
|
|
@@ -844,7 +862,7 @@ const REPLY_MDM = `{% assign p = msg %}
|
|
|
844
862
|
{
|
|
845
863
|
"legal_entity_type": "individual",
|
|
846
864
|
"identifiers": [
|
|
847
|
-
{"system": "{{ p.source_uri | json_escape }}", "value": "{{ p.handle | json_escape }}"}
|
|
865
|
+
{"system": "{{ p.source_uri | json_escape }}", "type": "digital_identifier", "value": "{{ p.handle | json_escape }}"}
|
|
848
866
|
]
|
|
849
867
|
}`
|
|
850
868
|
|
|
@@ -864,6 +882,15 @@ const { data: replyDac } = await api.dataActivationClients.create(tenantSlug, da
|
|
|
864
882
|
tool_id: ctx.uploadToolId,
|
|
865
883
|
data_source_id: dataSourceId,
|
|
866
884
|
tool_call: { tool_call_type: 'manual_upload' },
|
|
885
|
+
// REQUIRED on every DAC create, whatever the `tool_call` protocol. A
|
|
886
|
+
// manual_upload client is push-based — it never fetches and never pages —
|
|
887
|
+
// but the poll contract is declared uniformly, and `has_next` is the key
|
|
888
|
+
// the fetch loop would terminate on for a protocol that does page.
|
|
889
|
+
pagination_context_output_schema: {
|
|
890
|
+
type: 'object',
|
|
891
|
+
required: ['has_next'],
|
|
892
|
+
properties: { has_next: { type: 'boolean' } },
|
|
893
|
+
},
|
|
867
894
|
interoperability_contract_ids: [replyGt.id!],
|
|
868
895
|
})
|
|
869
896
|
|