@alvera-ai/platform-sdk 0.15.0-next.gf9ab4f0 → 0.16.0
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/cookbook/_setup/foundation.md +62 -0
- package/.agent/cookbook/_setup/healthcare.md +62 -0
- package/.agent/cookbook/_setup/payments.md +62 -0
- package/.agent/cookbook/_setup/subscription.md +62 -0
- package/.agent/cookbook/appointment-review-sms-workflow.md +15 -8
- package/.agent/cookbook/birthday-greeting-sms-trigger.md +10 -5
- package/.agent/cookbook/contact-us-triage-with-llm.md +10 -5
- package/.agent/cookbook/dunning-sms-for-delinquent.md +15 -8
- package/.agent/cookbook/kyc-notification-on-account-activation.md +15 -8
- package/.agent/cookbook/marketing-campaign-send.md +12 -7
- package/.agent/cookbook/sanctions-screening-with-agent-review.md +10 -5
- package/.agent/cookbook/score-leads-with-llm-categorization.md +10 -5
- package/.agent/cookbook/triage-prospects-by-priority.md +10 -5
- package/.agent/cookbook/welcome-sms-for-customers.md +15 -8
- package/.agent/interoperability_contracts.md +58 -0
- package/.agent/tools.md +98 -4
- package/.agent/workflows.md +115 -39
- package/dist/index.d.mts +575 -108
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +133 -5
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
|
@@ -256,6 +256,63 @@ if (datalakeStatus !== 'ready') {
|
|
|
256
256
|
}
|
|
257
257
|
```
|
|
258
258
|
|
|
259
|
+
## 007 — a reusable "wait until the run has fired" helper
|
|
260
|
+
|
|
261
|
+
`workflows.run` only **schedules** a run. It records it and returns
|
|
262
|
+
immediately with `workflow_run_id` / `status` / `scheduled_at`; the
|
|
263
|
+
`workflow_run_log_id` and `batch_id` a scenario needs are written
|
|
264
|
+
later, when the run actually fires, and are read back from
|
|
265
|
+
`workflowRuns.get`.
|
|
266
|
+
|
|
267
|
+
Two traps live in that gap, so every foundation scenario shares one
|
|
268
|
+
helper rather than re-deriving it:
|
|
269
|
+
|
|
270
|
+
- **Poll until `workflow_run_log_id` is a string — NOT until `status`
|
|
271
|
+
leaves `'scheduled'`.** Those are different moments: the run reaches
|
|
272
|
+
`processing` first and writes the log id a beat later. A predicate on
|
|
273
|
+
status alone releases you to read a `null`, and because
|
|
274
|
+
`typeof null === 'object'` the symptom is a baffling *"expected
|
|
275
|
+
string, got object"* rather than an obvious nil.
|
|
276
|
+
- **Raise on `failed` carrying `failure_reason` rather than polling to
|
|
277
|
+
the deadline.** A scenario blocked on a run that will never fire
|
|
278
|
+
should say why on the first read, not thirty seconds later behind a
|
|
279
|
+
generic timeout.
|
|
280
|
+
|
|
281
|
+
The helper is stored on the shared `ctx` bag rather than declared as a
|
|
282
|
+
plain function because each numbered step compiles into its own `it()`
|
|
283
|
+
block — a bare `function` here would not be in scope for the steps that
|
|
284
|
+
call it.
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
const FIRED_TIMEOUT_MS = 60_000
|
|
288
|
+
const FIRED_POLL_MS = 1_000
|
|
289
|
+
|
|
290
|
+
ctx.waitForFiredRun = async (
|
|
291
|
+
runDatalakeSlug: string,
|
|
292
|
+
runId: string,
|
|
293
|
+
timeoutMs: number = FIRED_TIMEOUT_MS,
|
|
294
|
+
): Promise<{ workflowRunLogId: string; batchId: string | null }> => {
|
|
295
|
+
const deadline = Date.now() + timeoutMs
|
|
296
|
+
let lastStatus: string | undefined
|
|
297
|
+
while (Date.now() < deadline) {
|
|
298
|
+
const { data } = await api.workflowRuns.get(tenantSlug, runDatalakeSlug, runId)
|
|
299
|
+
lastStatus = data.status
|
|
300
|
+
if (data.status === 'failed') {
|
|
301
|
+
throw new Error(
|
|
302
|
+
`workflow run ${runId} failed: ${data.failure_reason ?? 'no failure_reason given'}`,
|
|
303
|
+
)
|
|
304
|
+
}
|
|
305
|
+
if (typeof data.workflow_run_log_id === 'string') {
|
|
306
|
+
return { workflowRunLogId: data.workflow_run_log_id, batchId: data.batch_id ?? null }
|
|
307
|
+
}
|
|
308
|
+
await new Promise((r) => setTimeout(r, FIRED_POLL_MS))
|
|
309
|
+
}
|
|
310
|
+
throw new Error(
|
|
311
|
+
`workflow run ${runId} never fired within ${timeoutMs}ms (last status: ${lastStatus})`,
|
|
312
|
+
)
|
|
313
|
+
}
|
|
314
|
+
```
|
|
315
|
+
|
|
259
316
|
# Rollback
|
|
260
317
|
|
|
261
318
|
The cookbook doctest harness does not currently tear down the
|
|
@@ -282,6 +339,11 @@ After this setup runs, the closure-scoped slots are populated as:
|
|
|
282
339
|
- `ctx.tenantApiKey` — the tenant's publishable API key (minted in
|
|
283
340
|
setup via `admin.createTenantApiKey`); thread it into any
|
|
284
341
|
additional tenant-scoped `createSession` a scenario performs
|
|
342
|
+
- `ctx.waitForFiredRun(datalakeSlug, runId)` — polls `workflowRuns.get`
|
|
343
|
+
until a scheduled run has actually fired, then returns its
|
|
344
|
+
`{ workflowRunLogId, batchId }`. Use it after every `workflows.run`;
|
|
345
|
+
reading `workflow_run_log_id` off the run response returns `null`
|
|
346
|
+
because run-workflow only schedules (see §007)
|
|
285
347
|
|
|
286
348
|
Scenario cookbooks under `industry: foundation` start at their own
|
|
287
349
|
`§001` with these slots already populated.
|
|
@@ -257,6 +257,63 @@ if (datalakeStatus !== 'ready') {
|
|
|
257
257
|
}
|
|
258
258
|
```
|
|
259
259
|
|
|
260
|
+
## 007 — a reusable "wait until the run has fired" helper
|
|
261
|
+
|
|
262
|
+
`workflows.run` only **schedules** a run. It records it and returns
|
|
263
|
+
immediately with `workflow_run_id` / `status` / `scheduled_at`; the
|
|
264
|
+
`workflow_run_log_id` and `batch_id` a scenario needs are written
|
|
265
|
+
later, when the run actually fires, and are read back from
|
|
266
|
+
`workflowRuns.get`.
|
|
267
|
+
|
|
268
|
+
Two traps live in that gap, so every healthcare scenario shares one
|
|
269
|
+
helper rather than re-deriving it:
|
|
270
|
+
|
|
271
|
+
- **Poll until `workflow_run_log_id` is a string — NOT until `status`
|
|
272
|
+
leaves `'scheduled'`.** Those are different moments: the run reaches
|
|
273
|
+
`processing` first and writes the log id a beat later. A predicate on
|
|
274
|
+
status alone releases you to read a `null`, and because
|
|
275
|
+
`typeof null === 'object'` the symptom is a baffling *"expected
|
|
276
|
+
string, got object"* rather than an obvious nil.
|
|
277
|
+
- **Raise on `failed` carrying `failure_reason` rather than polling to
|
|
278
|
+
the deadline.** A scenario blocked on a run that will never fire
|
|
279
|
+
should say why on the first read, not thirty seconds later behind a
|
|
280
|
+
generic timeout.
|
|
281
|
+
|
|
282
|
+
The helper is stored on the shared `ctx` bag rather than declared as a
|
|
283
|
+
plain function because each numbered step compiles into its own `it()`
|
|
284
|
+
block — a bare `function` here would not be in scope for the steps that
|
|
285
|
+
call it.
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
const FIRED_TIMEOUT_MS = 60_000
|
|
289
|
+
const FIRED_POLL_MS = 1_000
|
|
290
|
+
|
|
291
|
+
ctx.waitForFiredRun = async (
|
|
292
|
+
runDatalakeSlug: string,
|
|
293
|
+
runId: string,
|
|
294
|
+
timeoutMs: number = FIRED_TIMEOUT_MS,
|
|
295
|
+
): Promise<{ workflowRunLogId: string; batchId: string | null }> => {
|
|
296
|
+
const deadline = Date.now() + timeoutMs
|
|
297
|
+
let lastStatus: string | undefined
|
|
298
|
+
while (Date.now() < deadline) {
|
|
299
|
+
const { data } = await api.workflowRuns.get(tenantSlug, runDatalakeSlug, runId)
|
|
300
|
+
lastStatus = data.status
|
|
301
|
+
if (data.status === 'failed') {
|
|
302
|
+
throw new Error(
|
|
303
|
+
`workflow run ${runId} failed: ${data.failure_reason ?? 'no failure_reason given'}`,
|
|
304
|
+
)
|
|
305
|
+
}
|
|
306
|
+
if (typeof data.workflow_run_log_id === 'string') {
|
|
307
|
+
return { workflowRunLogId: data.workflow_run_log_id, batchId: data.batch_id ?? null }
|
|
308
|
+
}
|
|
309
|
+
await new Promise((r) => setTimeout(r, FIRED_POLL_MS))
|
|
310
|
+
}
|
|
311
|
+
throw new Error(
|
|
312
|
+
`workflow run ${runId} never fired within ${timeoutMs}ms (last status: ${lastStatus})`,
|
|
313
|
+
)
|
|
314
|
+
}
|
|
315
|
+
```
|
|
316
|
+
|
|
260
317
|
# Rollback
|
|
261
318
|
|
|
262
319
|
The cookbook doctest harness does not currently tear down the
|
|
@@ -283,6 +340,11 @@ After this setup runs, the closure-scoped slots are populated as:
|
|
|
283
340
|
- `ctx.tenantApiKey` — the tenant's publishable API key (minted in
|
|
284
341
|
setup via `admin.createTenantApiKey`); thread it into any
|
|
285
342
|
additional tenant-scoped `createSession` a scenario performs
|
|
343
|
+
- `ctx.waitForFiredRun(datalakeSlug, runId)` — polls `workflowRuns.get`
|
|
344
|
+
until a scheduled run has actually fired, then returns its
|
|
345
|
+
`{ workflowRunLogId, batchId }`. Use it after every `workflows.run`;
|
|
346
|
+
reading `workflow_run_log_id` off the run response returns `null`
|
|
347
|
+
because run-workflow only schedules (see §007)
|
|
286
348
|
|
|
287
349
|
Scenario cookbooks under `industry: healthcare` start at their own
|
|
288
350
|
`§001` with these slots already populated.
|
|
@@ -261,6 +261,63 @@ if (datalakeStatus !== 'ready') {
|
|
|
261
261
|
}
|
|
262
262
|
```
|
|
263
263
|
|
|
264
|
+
## 007 — a reusable "wait until the run has fired" helper
|
|
265
|
+
|
|
266
|
+
`workflows.run` only **schedules** a run. It records it and returns
|
|
267
|
+
immediately with `workflow_run_id` / `status` / `scheduled_at`; the
|
|
268
|
+
`workflow_run_log_id` and `batch_id` a scenario needs are written
|
|
269
|
+
later, when the run actually fires, and are read back from
|
|
270
|
+
`workflowRuns.get`.
|
|
271
|
+
|
|
272
|
+
Two traps live in that gap, so every payments scenario shares one
|
|
273
|
+
helper rather than re-deriving it:
|
|
274
|
+
|
|
275
|
+
- **Poll until `workflow_run_log_id` is a string — NOT until `status`
|
|
276
|
+
leaves `'scheduled'`.** Those are different moments: the run reaches
|
|
277
|
+
`processing` first and writes the log id a beat later. A predicate on
|
|
278
|
+
status alone releases you to read a `null`, and because
|
|
279
|
+
`typeof null === 'object'` the symptom is a baffling *"expected
|
|
280
|
+
string, got object"* rather than an obvious nil.
|
|
281
|
+
- **Raise on `failed` carrying `failure_reason` rather than polling to
|
|
282
|
+
the deadline.** A scenario blocked on a run that will never fire
|
|
283
|
+
should say why on the first read, not thirty seconds later behind a
|
|
284
|
+
generic timeout.
|
|
285
|
+
|
|
286
|
+
The helper is stored on the shared `ctx` bag rather than declared as a
|
|
287
|
+
plain function because each numbered step compiles into its own `it()`
|
|
288
|
+
block — a bare `function` here would not be in scope for the steps that
|
|
289
|
+
call it.
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
const FIRED_TIMEOUT_MS = 60_000
|
|
293
|
+
const FIRED_POLL_MS = 1_000
|
|
294
|
+
|
|
295
|
+
ctx.waitForFiredRun = async (
|
|
296
|
+
runDatalakeSlug: string,
|
|
297
|
+
runId: string,
|
|
298
|
+
timeoutMs: number = FIRED_TIMEOUT_MS,
|
|
299
|
+
): Promise<{ workflowRunLogId: string; batchId: string | null }> => {
|
|
300
|
+
const deadline = Date.now() + timeoutMs
|
|
301
|
+
let lastStatus: string | undefined
|
|
302
|
+
while (Date.now() < deadline) {
|
|
303
|
+
const { data } = await api.workflowRuns.get(tenantSlug, runDatalakeSlug, runId)
|
|
304
|
+
lastStatus = data.status
|
|
305
|
+
if (data.status === 'failed') {
|
|
306
|
+
throw new Error(
|
|
307
|
+
`workflow run ${runId} failed: ${data.failure_reason ?? 'no failure_reason given'}`,
|
|
308
|
+
)
|
|
309
|
+
}
|
|
310
|
+
if (typeof data.workflow_run_log_id === 'string') {
|
|
311
|
+
return { workflowRunLogId: data.workflow_run_log_id, batchId: data.batch_id ?? null }
|
|
312
|
+
}
|
|
313
|
+
await new Promise((r) => setTimeout(r, FIRED_POLL_MS))
|
|
314
|
+
}
|
|
315
|
+
throw new Error(
|
|
316
|
+
`workflow run ${runId} never fired within ${timeoutMs}ms (last status: ${lastStatus})`,
|
|
317
|
+
)
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
264
321
|
# Rollback
|
|
265
322
|
|
|
266
323
|
The cookbook doctest harness does not currently tear down the
|
|
@@ -287,6 +344,11 @@ After this setup runs, the closure-scoped slots are populated as:
|
|
|
287
344
|
- `ctx.tenantApiKey` — the tenant's publishable API key (minted in
|
|
288
345
|
setup via `admin.createTenantApiKey`); thread it into any
|
|
289
346
|
additional tenant-scoped `createSession` a scenario performs
|
|
347
|
+
- `ctx.waitForFiredRun(datalakeSlug, runId)` — polls `workflowRuns.get`
|
|
348
|
+
until a scheduled run has actually fired, then returns its
|
|
349
|
+
`{ workflowRunLogId, batchId }`. Use it after every `workflows.run`;
|
|
350
|
+
reading `workflow_run_log_id` off the run response returns `null`
|
|
351
|
+
because run-workflow only schedules (see §007)
|
|
290
352
|
|
|
291
353
|
Scenario cookbooks under `industry: payments` start at their
|
|
292
354
|
own `§001` with these slots already populated.
|
|
@@ -260,6 +260,63 @@ if (datalakeStatus !== 'ready') {
|
|
|
260
260
|
}
|
|
261
261
|
```
|
|
262
262
|
|
|
263
|
+
## 007 — a reusable "wait until the run has fired" helper
|
|
264
|
+
|
|
265
|
+
`workflows.run` only **schedules** a run. It records it and returns
|
|
266
|
+
immediately with `workflow_run_id` / `status` / `scheduled_at`; the
|
|
267
|
+
`workflow_run_log_id` and `batch_id` a scenario needs are written
|
|
268
|
+
later, when the run actually fires, and are read back from
|
|
269
|
+
`workflowRuns.get`.
|
|
270
|
+
|
|
271
|
+
Two traps live in that gap, so every subscription scenario shares one
|
|
272
|
+
helper rather than re-deriving it:
|
|
273
|
+
|
|
274
|
+
- **Poll until `workflow_run_log_id` is a string — NOT until `status`
|
|
275
|
+
leaves `'scheduled'`.** Those are different moments: the run reaches
|
|
276
|
+
`processing` first and writes the log id a beat later. A predicate on
|
|
277
|
+
status alone releases you to read a `null`, and because
|
|
278
|
+
`typeof null === 'object'` the symptom is a baffling *"expected
|
|
279
|
+
string, got object"* rather than an obvious nil.
|
|
280
|
+
- **Raise on `failed` carrying `failure_reason` rather than polling to
|
|
281
|
+
the deadline.** A scenario blocked on a run that will never fire
|
|
282
|
+
should say why on the first read, not thirty seconds later behind a
|
|
283
|
+
generic timeout.
|
|
284
|
+
|
|
285
|
+
The helper is stored on the shared `ctx` bag rather than declared as a
|
|
286
|
+
plain function because each numbered step compiles into its own `it()`
|
|
287
|
+
block — a bare `function` here would not be in scope for the steps that
|
|
288
|
+
call it.
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
const FIRED_TIMEOUT_MS = 60_000
|
|
292
|
+
const FIRED_POLL_MS = 1_000
|
|
293
|
+
|
|
294
|
+
ctx.waitForFiredRun = async (
|
|
295
|
+
runDatalakeSlug: string,
|
|
296
|
+
runId: string,
|
|
297
|
+
timeoutMs: number = FIRED_TIMEOUT_MS,
|
|
298
|
+
): Promise<{ workflowRunLogId: string; batchId: string | null }> => {
|
|
299
|
+
const deadline = Date.now() + timeoutMs
|
|
300
|
+
let lastStatus: string | undefined
|
|
301
|
+
while (Date.now() < deadline) {
|
|
302
|
+
const { data } = await api.workflowRuns.get(tenantSlug, runDatalakeSlug, runId)
|
|
303
|
+
lastStatus = data.status
|
|
304
|
+
if (data.status === 'failed') {
|
|
305
|
+
throw new Error(
|
|
306
|
+
`workflow run ${runId} failed: ${data.failure_reason ?? 'no failure_reason given'}`,
|
|
307
|
+
)
|
|
308
|
+
}
|
|
309
|
+
if (typeof data.workflow_run_log_id === 'string') {
|
|
310
|
+
return { workflowRunLogId: data.workflow_run_log_id, batchId: data.batch_id ?? null }
|
|
311
|
+
}
|
|
312
|
+
await new Promise((r) => setTimeout(r, FIRED_POLL_MS))
|
|
313
|
+
}
|
|
314
|
+
throw new Error(
|
|
315
|
+
`workflow run ${runId} never fired within ${timeoutMs}ms (last status: ${lastStatus})`,
|
|
316
|
+
)
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
263
320
|
# Rollback
|
|
264
321
|
|
|
265
322
|
The cookbook doctest harness does not currently tear down the
|
|
@@ -286,6 +343,11 @@ After this setup runs, the closure-scoped slots are populated as:
|
|
|
286
343
|
- `ctx.tenantApiKey` — the tenant's publishable API key (minted in
|
|
287
344
|
setup via `admin.createTenantApiKey`); thread it into any
|
|
288
345
|
additional tenant-scoped `createSession` a scenario performs
|
|
346
|
+
- `ctx.waitForFiredRun(datalakeSlug, runId)` — polls `workflowRuns.get`
|
|
347
|
+
until a scheduled run has actually fired, then returns its
|
|
348
|
+
`{ workflowRunLogId, batchId }`. Use it after every `workflows.run`;
|
|
349
|
+
reading `workflow_run_log_id` off the run response returns `null`
|
|
350
|
+
because run-workflow only schedules (see §007)
|
|
289
351
|
|
|
290
352
|
Scenario cookbooks under `industry: subscription` start at
|
|
291
353
|
their own `§001` with these slots already populated.
|
|
@@ -477,9 +477,11 @@ batches §009 ingested (`ra` is the regulated-appointments alias
|
|
|
477
477
|
the run-query exposes).
|
|
478
478
|
|
|
479
479
|
The SMS action's `trigger_template: 'now'` dispatches the action
|
|
480
|
-
immediately rather than
|
|
481
|
-
terminal status on its own — poll `batchLogs.refresh`
|
|
482
|
-
leaves `:pending`.
|
|
480
|
+
immediately rather than deferring it, so once the run fires it
|
|
481
|
+
reaches a terminal status on its own — poll `batchLogs.refresh`
|
|
482
|
+
until it leaves `:pending`. The run itself is still scheduled:
|
|
483
|
+
`workflows.run` records it and returns, which is why the setup
|
|
484
|
+
file's `ctx.waitForFiredRun` sits between the call and the log id. A `:partial` status is expected and fine here:
|
|
483
485
|
one row passed and one was filtered.
|
|
484
486
|
|
|
485
487
|
```typescript
|
|
@@ -488,8 +490,11 @@ const runResp = await api.workflows.run(tenantSlug, datalakeSlug, ctx.workflowSl
|
|
|
488
490
|
mode: 'live',
|
|
489
491
|
manual_override: false,
|
|
490
492
|
})
|
|
491
|
-
|
|
492
|
-
|
|
493
|
+
// run-workflow only SCHEDULES the run. The log id and batch id are
|
|
494
|
+
// written when it fires, so read them back via workflowRuns.get.
|
|
495
|
+
const fired = await ctx.waitForFiredRun(datalakeSlug, runResp.data.workflow_run_id)
|
|
496
|
+
ctx.runLogId = fired.workflowRunLogId
|
|
497
|
+
ctx.runBatchId = fired.batchId!
|
|
493
498
|
|
|
494
499
|
const deadline = Date.now() + 120_000
|
|
495
500
|
let status: string | null = null
|
|
@@ -693,14 +698,16 @@ if (wfRow.status !== 'live') {
|
|
|
693
698
|
}
|
|
694
699
|
// Behavioural probe — a dry run against a selection no row can match:
|
|
695
700
|
// the pipeline executes end-to-end, the final action call is
|
|
696
|
-
// intercepted, and the acknowledgement carries the run
|
|
701
|
+
// intercepted, and the acknowledgement carries the scheduled run id.
|
|
697
702
|
const { data: probeRun } = await api.workflows.run(tenantSlug, datalakeSlug, ctx.workflowSlug, {
|
|
698
703
|
sql_where_clause: "ra.batch_id = 'test-never-matching-batch'",
|
|
699
704
|
mode: 'dry_run',
|
|
700
705
|
manual_override: false,
|
|
701
706
|
})
|
|
702
|
-
|
|
703
|
-
|
|
707
|
+
// The run-log id does not exist until the run fires — wait, do not read a null.
|
|
708
|
+
const probeFired = await ctx.waitForFiredRun(datalakeSlug, probeRun.workflow_run_id)
|
|
709
|
+
if (probeFired.workflowRunLogId.length === 0) {
|
|
710
|
+
throw new Error('dry-run probe never produced a workflow_run_log_id')
|
|
704
711
|
}
|
|
705
712
|
```
|
|
706
713
|
|
|
@@ -399,8 +399,11 @@ const runResp = await api.workflows.run(tenantSlug, datalakeSlug, ctx.workflowSl
|
|
|
399
399
|
mode: 'live',
|
|
400
400
|
manual_override: false,
|
|
401
401
|
})
|
|
402
|
-
|
|
403
|
-
|
|
402
|
+
// run-workflow only SCHEDULES the run. The log id and batch id are
|
|
403
|
+
// written when it fires, so read them back via workflowRuns.get.
|
|
404
|
+
const fired = await ctx.waitForFiredRun(datalakeSlug, runResp.data.workflow_run_id)
|
|
405
|
+
ctx.runLogId = fired.workflowRunLogId
|
|
406
|
+
ctx.runBatchId = fired.batchId!
|
|
404
407
|
|
|
405
408
|
const deadline = Date.now() + 60_000
|
|
406
409
|
let totalWels = 0
|
|
@@ -595,7 +598,7 @@ if (wfRow.status !== 'live') {
|
|
|
595
598
|
}
|
|
596
599
|
// Behavioural probe — a dry run against a selection no row can match:
|
|
597
600
|
// the pipeline executes end-to-end, the final action call is
|
|
598
|
-
// intercepted, and the acknowledgement carries the run
|
|
601
|
+
// intercepted, and the acknowledgement carries the scheduled run id. The
|
|
599
602
|
// clause must speak this workflow's selection dialect — the dataset
|
|
600
603
|
// alias is `rle` here, the same alias the live run above uses.
|
|
601
604
|
const { data: probeRun } = await api.workflows.run(tenantSlug, datalakeSlug, ctx.workflowSlug, {
|
|
@@ -603,8 +606,10 @@ const { data: probeRun } = await api.workflows.run(tenantSlug, datalakeSlug, ctx
|
|
|
603
606
|
mode: 'dry_run',
|
|
604
607
|
manual_override: false,
|
|
605
608
|
})
|
|
606
|
-
|
|
607
|
-
|
|
609
|
+
// The run-log id does not exist until the run fires — wait, do not read a null.
|
|
610
|
+
const probeFired = await ctx.waitForFiredRun(datalakeSlug, probeRun.workflow_run_id)
|
|
611
|
+
if (probeFired.workflowRunLogId.length === 0) {
|
|
612
|
+
throw new Error('dry-run probe never produced a workflow_run_log_id')
|
|
608
613
|
}
|
|
609
614
|
```
|
|
610
615
|
|
|
@@ -479,8 +479,11 @@ const runResp = await api.workflows.run(tenantSlug, datalakeSlug, ctx.workflowSl
|
|
|
479
479
|
mode: 'live',
|
|
480
480
|
manual_override: true,
|
|
481
481
|
})
|
|
482
|
-
|
|
483
|
-
|
|
482
|
+
// run-workflow only SCHEDULES the run. The log id and batch id are
|
|
483
|
+
// written when it fires, so read them back via workflowRuns.get.
|
|
484
|
+
const fired = await ctx.waitForFiredRun(datalakeSlug, runResp.data.workflow_run_id)
|
|
485
|
+
ctx.runLogId = fired.workflowRunLogId
|
|
486
|
+
ctx.runBatchId = fired.batchId!
|
|
484
487
|
|
|
485
488
|
const deadline = Date.now() + 240_000
|
|
486
489
|
let status: string | null = null
|
|
@@ -561,7 +564,7 @@ if (wfRow.status !== 'live') {
|
|
|
561
564
|
}
|
|
562
565
|
// Behavioural probe — a dry run against a selection no row can match:
|
|
563
566
|
// the pipeline executes end-to-end, the final action call is
|
|
564
|
-
// intercepted, and the acknowledgement carries the run
|
|
567
|
+
// intercepted, and the acknowledgement carries the scheduled run id. The
|
|
565
568
|
// clause speaks this workflow's selection dialect: a GENERIC-TABLE
|
|
566
569
|
// dataset is addressed by its own columns (no `ra.` dataset alias —
|
|
567
570
|
// that alias exists only for system-dataset selections).
|
|
@@ -570,8 +573,10 @@ const { data: probeRun } = await api.workflows.run(tenantSlug, datalakeSlug, ctx
|
|
|
570
573
|
mode: 'dry_run',
|
|
571
574
|
manual_override: false,
|
|
572
575
|
})
|
|
573
|
-
|
|
574
|
-
|
|
576
|
+
// The run-log id does not exist until the run fires — wait, do not read a null.
|
|
577
|
+
const probeFired = await ctx.waitForFiredRun(datalakeSlug, probeRun.workflow_run_id)
|
|
578
|
+
if (probeFired.workflowRunLogId.length === 0) {
|
|
579
|
+
throw new Error('dry-run probe never produced a workflow_run_log_id')
|
|
575
580
|
}
|
|
576
581
|
```
|
|
577
582
|
|
|
@@ -396,9 +396,11 @@ exactly the two batches §008 ingested (`ra` is the
|
|
|
396
396
|
regulated-customer alias the run-query exposes).
|
|
397
397
|
|
|
398
398
|
The SMS action's `trigger_template: 'now'` dispatches the action
|
|
399
|
-
immediately rather than
|
|
400
|
-
terminal status on its own — poll `batchLogs.refresh`
|
|
401
|
-
leaves `:pending`.
|
|
399
|
+
immediately rather than deferring it, so once the run fires it
|
|
400
|
+
reaches a terminal status on its own — poll `batchLogs.refresh`
|
|
401
|
+
until it leaves `:pending`. The run itself is still scheduled:
|
|
402
|
+
`workflows.run` records it and returns, which is why the setup
|
|
403
|
+
file's `ctx.waitForFiredRun` sits between the call and the log id. A `:partial` status is expected and fine here:
|
|
402
404
|
one row passed and one was filtered.
|
|
403
405
|
|
|
404
406
|
```typescript
|
|
@@ -407,8 +409,11 @@ const runResp = await api.workflows.run(tenantSlug, datalakeSlug, ctx.workflowSl
|
|
|
407
409
|
mode: 'live',
|
|
408
410
|
manual_override: false,
|
|
409
411
|
})
|
|
410
|
-
|
|
411
|
-
|
|
412
|
+
// run-workflow only SCHEDULES the run. The log id and batch id are
|
|
413
|
+
// written when it fires, so read them back via workflowRuns.get.
|
|
414
|
+
const fired = await ctx.waitForFiredRun(datalakeSlug, runResp.data.workflow_run_id)
|
|
415
|
+
ctx.runLogId = fired.workflowRunLogId
|
|
416
|
+
ctx.runBatchId = fired.batchId!
|
|
412
417
|
|
|
413
418
|
const deadline = Date.now() + 120_000
|
|
414
419
|
let status: string | null = null
|
|
@@ -551,14 +556,16 @@ if (wfRow.status !== 'live') {
|
|
|
551
556
|
}
|
|
552
557
|
// Behavioural probe — a dry run against a selection no row can match:
|
|
553
558
|
// the pipeline executes end-to-end, the final action call is
|
|
554
|
-
// intercepted, and the acknowledgement carries the run
|
|
559
|
+
// intercepted, and the acknowledgement carries the scheduled run id.
|
|
555
560
|
const { data: probeRun } = await api.workflows.run(tenantSlug, datalakeSlug, ctx.workflowSlug, {
|
|
556
561
|
sql_where_clause: "ra.batch_id = 'test-never-matching-batch'",
|
|
557
562
|
mode: 'dry_run',
|
|
558
563
|
manual_override: false,
|
|
559
564
|
})
|
|
560
|
-
|
|
561
|
-
|
|
565
|
+
// The run-log id does not exist until the run fires — wait, do not read a null.
|
|
566
|
+
const probeFired = await ctx.waitForFiredRun(datalakeSlug, probeRun.workflow_run_id)
|
|
567
|
+
if (probeFired.workflowRunLogId.length === 0) {
|
|
568
|
+
throw new Error('dry-run probe never produced a workflow_run_log_id')
|
|
562
569
|
}
|
|
563
570
|
```
|
|
564
571
|
|
|
@@ -394,9 +394,11 @@ batches §008 ingested (`rpa` is the regulated-payment-account
|
|
|
394
394
|
alias the run-query exposes).
|
|
395
395
|
|
|
396
396
|
The SMS action's `trigger_template: 'now'` dispatches the action
|
|
397
|
-
immediately rather than
|
|
398
|
-
terminal status on its own — poll `batchLogs.refresh`
|
|
399
|
-
leaves `:pending`.
|
|
397
|
+
immediately rather than deferring it, so once the run fires it
|
|
398
|
+
reaches a terminal status on its own — poll `batchLogs.refresh`
|
|
399
|
+
until it leaves `:pending`. The run itself is still scheduled:
|
|
400
|
+
`workflows.run` records it and returns, which is why the setup
|
|
401
|
+
file's `ctx.waitForFiredRun` sits between the call and the log id. A `:partial` status is expected and fine here:
|
|
400
402
|
one row passed and one was filtered.
|
|
401
403
|
|
|
402
404
|
```typescript
|
|
@@ -405,8 +407,11 @@ const runResp = await api.workflows.run(tenantSlug, datalakeSlug, ctx.workflowSl
|
|
|
405
407
|
mode: 'live',
|
|
406
408
|
manual_override: false,
|
|
407
409
|
})
|
|
408
|
-
|
|
409
|
-
|
|
410
|
+
// run-workflow only SCHEDULES the run. The log id and batch id are
|
|
411
|
+
// written when it fires, so read them back via workflowRuns.get.
|
|
412
|
+
const fired = await ctx.waitForFiredRun(datalakeSlug, runResp.data.workflow_run_id)
|
|
413
|
+
ctx.runLogId = fired.workflowRunLogId
|
|
414
|
+
ctx.runBatchId = fired.batchId!
|
|
410
415
|
|
|
411
416
|
const deadline = Date.now() + 120_000
|
|
412
417
|
let status: string | null = null
|
|
@@ -549,7 +554,7 @@ if (wfRow.status !== 'live') {
|
|
|
549
554
|
}
|
|
550
555
|
// Behavioural probe — a dry run against a selection no row can match:
|
|
551
556
|
// the pipeline executes end-to-end, the final action call is
|
|
552
|
-
// intercepted, and the acknowledgement carries the run
|
|
557
|
+
// intercepted, and the acknowledgement carries the scheduled run id. The
|
|
553
558
|
// clause must speak this workflow's selection dialect — the dataset
|
|
554
559
|
// alias is `rpa` here, the same alias the live run above uses.
|
|
555
560
|
const { data: probeRun } = await api.workflows.run(tenantSlug, datalakeSlug, ctx.workflowSlug, {
|
|
@@ -557,8 +562,10 @@ const { data: probeRun } = await api.workflows.run(tenantSlug, datalakeSlug, ctx
|
|
|
557
562
|
mode: 'dry_run',
|
|
558
563
|
manual_override: false,
|
|
559
564
|
})
|
|
560
|
-
|
|
561
|
-
|
|
565
|
+
// The run-log id does not exist until the run fires — wait, do not read a null.
|
|
566
|
+
const probeFired = await ctx.waitForFiredRun(datalakeSlug, probeRun.workflow_run_id)
|
|
567
|
+
if (probeFired.workflowRunLogId.length === 0) {
|
|
568
|
+
throw new Error('dry-run probe never produced a workflow_run_log_id')
|
|
562
569
|
}
|
|
563
570
|
```
|
|
564
571
|
|
|
@@ -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 }}", "value": "{{ p.direct_customer_id | json_escape }}", "type": "digital_identifier"}
|
|
277
277
|
]
|
|
278
278
|
}`
|
|
279
279
|
|
|
@@ -376,7 +376,7 @@ const AUDIENCE_MDM = `{% assign p = msg %}
|
|
|
376
376
|
{% if first_name != "" %}"first_name": "{{ first_name | json_escape }}",{% endif %}
|
|
377
377
|
{% if last_name != "" %}"last_name": "{{ last_name | json_escape }}",{% endif %}
|
|
378
378
|
"identifiers": [
|
|
379
|
-
{"system": "{{ p.source_uri | json_escape }}", "value": "{{ handle | json_escape }}"}
|
|
379
|
+
{"system": "{{ p.source_uri | json_escape }}", "value": "{{ handle | json_escape }}", "type": "digital_identifier"}
|
|
380
380
|
]
|
|
381
381
|
}`
|
|
382
382
|
|
|
@@ -691,7 +691,10 @@ const { data: run } = await api.workflows.run(tenantSlug, datalakeSlug, ctx.work
|
|
|
691
691
|
mode: 'live',
|
|
692
692
|
manual_override: false,
|
|
693
693
|
})
|
|
694
|
-
|
|
694
|
+
// run-workflow only SCHEDULES the run. The log id is written when it
|
|
695
|
+
// fires, so read it back via workflowRuns.get.
|
|
696
|
+
const fired = await ctx.waitForFiredRun(datalakeSlug, run.workflow_run_id)
|
|
697
|
+
ctx.runLogId = fired.workflowRunLogId
|
|
695
698
|
|
|
696
699
|
const deadline = Date.now() + 180_000
|
|
697
700
|
let byStatus: Record<string, number> = {}
|
|
@@ -845,7 +848,7 @@ const REPLY_MDM = `{% assign p = msg %}
|
|
|
845
848
|
{
|
|
846
849
|
"legal_entity_type": "individual",
|
|
847
850
|
"identifiers": [
|
|
848
|
-
{"system": "{{ p.source_uri | json_escape }}", "value": "{{ p.handle | json_escape }}"}
|
|
851
|
+
{"system": "{{ p.source_uri | json_escape }}", "value": "{{ p.handle | json_escape }}", "type": "digital_identifier"}
|
|
849
852
|
]
|
|
850
853
|
}`
|
|
851
854
|
|
|
@@ -936,7 +939,7 @@ if (wfRow.status !== 'live') {
|
|
|
936
939
|
}
|
|
937
940
|
// Behavioural probe — a dry run against a selection no row can match:
|
|
938
941
|
// the pipeline executes end-to-end, the final action call is
|
|
939
|
-
// intercepted, and the acknowledgement carries the run
|
|
942
|
+
// intercepted, and the acknowledgement carries the scheduled run id. The
|
|
940
943
|
// clause speaks this workflow's selection dialect: a GENERIC-TABLE
|
|
941
944
|
// audience is addressed by its own columns (no `ra.` dataset alias —
|
|
942
945
|
// that alias exists only for system-dataset selections).
|
|
@@ -945,8 +948,10 @@ const { data: probeRun } = await api.workflows.run(tenantSlug, datalakeSlug, ctx
|
|
|
945
948
|
mode: 'dry_run',
|
|
946
949
|
manual_override: false,
|
|
947
950
|
})
|
|
948
|
-
|
|
949
|
-
|
|
951
|
+
// The run-log id does not exist until the run fires — wait, do not read a null.
|
|
952
|
+
const probeFired = await ctx.waitForFiredRun(datalakeSlug, probeRun.workflow_run_id)
|
|
953
|
+
if (probeFired.workflowRunLogId.length === 0) {
|
|
954
|
+
throw new Error('dry-run probe never produced a workflow_run_log_id')
|
|
950
955
|
}
|
|
951
956
|
```
|
|
952
957
|
|
|
@@ -515,8 +515,11 @@ const runResp = await api.workflows.run(tenantSlug, datalakeSlug, ctx.workflowSl
|
|
|
515
515
|
mode: 'live',
|
|
516
516
|
manual_override: false,
|
|
517
517
|
})
|
|
518
|
-
|
|
519
|
-
|
|
518
|
+
// run-workflow only SCHEDULES the run. The log id and batch id are
|
|
519
|
+
// written when it fires, so read them back via workflowRuns.get.
|
|
520
|
+
const fired = await ctx.waitForFiredRun(datalakeSlug, runResp.data.workflow_run_id)
|
|
521
|
+
ctx.runLogId = fired.workflowRunLogId
|
|
522
|
+
ctx.runBatchId = fired.batchId!
|
|
520
523
|
|
|
521
524
|
const deadline = Date.now() + 240_000
|
|
522
525
|
let status: string | null = null
|
|
@@ -659,7 +662,7 @@ if (wfRow.status !== 'live') {
|
|
|
659
662
|
}
|
|
660
663
|
// Behavioural probe — a dry run against a selection no row can match:
|
|
661
664
|
// the pipeline executes end-to-end, the final action call is
|
|
662
|
-
// intercepted, and the acknowledgement carries the run
|
|
665
|
+
// intercepted, and the acknowledgement carries the scheduled run id. The
|
|
663
666
|
// clause must speak this workflow's selection dialect — the dataset
|
|
664
667
|
// alias is `rcs` here, the same alias the live run above uses.
|
|
665
668
|
const { data: probeRun } = await api.workflows.run(tenantSlug, datalakeSlug, ctx.workflowSlug, {
|
|
@@ -667,8 +670,10 @@ const { data: probeRun } = await api.workflows.run(tenantSlug, datalakeSlug, ctx
|
|
|
667
670
|
mode: 'dry_run',
|
|
668
671
|
manual_override: false,
|
|
669
672
|
})
|
|
670
|
-
|
|
671
|
-
|
|
673
|
+
// The run-log id does not exist until the run fires — wait, do not read a null.
|
|
674
|
+
const probeFired = await ctx.waitForFiredRun(datalakeSlug, probeRun.workflow_run_id)
|
|
675
|
+
if (probeFired.workflowRunLogId.length === 0) {
|
|
676
|
+
throw new Error('dry-run probe never produced a workflow_run_log_id')
|
|
672
677
|
}
|
|
673
678
|
```
|
|
674
679
|
|