pipework 0.175.0 → 0.177.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/CHANGELOG.md +17 -0
- package/REFERENCE.md +10 -2
- package/dist/REFERENCE.md +131 -10
- package/dist/infra/alerts/async-sources.d.ts +54 -0
- package/dist/infra/alerts/async-sources.d.ts.map +1 -0
- package/dist/infra/alerts/async-sources.js +233 -0
- package/dist/infra/alerts/async-sources.js.map +1 -0
- package/dist/infra/alerts/infra-sources.d.ts +49 -0
- package/dist/infra/alerts/infra-sources.d.ts.map +1 -0
- package/dist/infra/alerts/infra-sources.js +176 -0
- package/dist/infra/alerts/infra-sources.js.map +1 -0
- package/dist/infra/alerts/namespace.d.ts +18 -0
- package/dist/infra/alerts/namespace.d.ts.map +1 -1
- package/dist/infra/alerts/namespace.js +18 -0
- package/dist/infra/alerts/namespace.js.map +1 -1
- package/dist/infra/alerts/source-options.d.ts +13 -0
- package/dist/infra/alerts/source-options.d.ts.map +1 -0
- package/dist/infra/alerts/source-options.js +19 -0
- package/dist/infra/alerts/source-options.js.map +1 -0
- package/dist/infra/alerts/subsystem-sources.d.ts +1 -8
- package/dist/infra/alerts/subsystem-sources.d.ts.map +1 -1
- package/dist/infra/alerts/subsystem-sources.js +1 -17
- package/dist/infra/alerts/subsystem-sources.js.map +1 -1
- package/dist/infra/email/inbound.js +1 -1
- package/dist/infra/email/inbound.js.map +1 -1
- package/dist/infra/email/mail.js +1 -1
- package/dist/infra/email/mail.js.map +1 -1
- package/dist/infra/email/schema.d.ts +10 -0
- package/dist/infra/email/schema.d.ts.map +1 -1
- package/dist/infra/email/schema.js +2 -0
- package/dist/infra/email/schema.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.177.0
|
|
4
|
+
|
|
5
|
+
- **Added: alert sources for schedules, step graphs, pipelines and traces** (failure-alert class, part 3 of 4):
|
|
6
|
+
- `alerts.source.schedules({ graceMs? })` raises `schedule-overdue:<name>` while a schedule's next run is more than 5 min past, which means no scheduler is firing it.
|
|
7
|
+
- `alerts.source.steps({ notifyAfterMs?, waitingAfterMs?, runningAfterMs? })` raises `step-run-failed:<graph>` for runs that failed since the last tick, `step-run-unnotified:<graph>` while finished runs were never notified, `step-task-waiting:<graph>` while a task waits on a person too long, and `step-run-stalled:<graph>` while a run stays running with nothing waiting on a person.
|
|
8
|
+
- `alerts.source.pipelines({ waitingAfterMs?, runningAfterMs? })` raises `pipeline-failed:<pipeline>`, `pipeline-waiting:<pipeline>` and `pipeline-stalled:<pipeline>`.
|
|
9
|
+
- `alerts.source.traces({ graceMs? })` raises `trace-expired:<entryKind>` for traces the expiry sweep force-closed, and `trace-expiry-undrained:all` while open traces are more than 26 h past expiry (the expiry job is not running).
|
|
10
|
+
|
|
11
|
+
## 0.176.0
|
|
12
|
+
|
|
13
|
+
- **Added: alert sources for mail, storage, access links and sign-in lockouts** (failure-alert class, part 2 of 3):
|
|
14
|
+
- `alerts.source.email({ stuckAfterMs?, queuedAfterMs?, receivedAfterMs?, sweepGraceMs? })` raises `email-failed:outbound` and `email-failed:inbound` for messages marked failed since the last tick, `email-stuck:sending` and `email-stuck:queued` while sends stall or nothing drains the queue, `email-inbound-stuck:received` while received mail is not handled, and `email-raw-undrained:all` while raw inbound copies outlive their retention.
|
|
15
|
+
- `alerts.source.storage({ sweepGraceMs? })` raises `storage-expiry-undrained:all` while expired files are not swept.
|
|
16
|
+
- `alerts.source.accessLinks({ graceMs? })` raises `access-link-abandoned:<tenantId>` while link uses stay reserved and never finish.
|
|
17
|
+
- `alerts.source.lockouts({ tables? })` raises `auth-lockout:<table>` (warning) while sign-in or guard lockouts are active: counts only, never an account. Without `tables` it watches whichever lockout tables exist.
|
|
18
|
+
- `pipework_email_messages` and `pipework_email_inbound` gain a nullable `failed_at`, set when a message is marked failed. Run `pipework generate` and migrate.
|
|
19
|
+
|
|
3
20
|
## 0.175.0
|
|
4
21
|
|
|
5
22
|
- **Fixed: a worker surface with `schedules: []` failed every scheduler check** with `syntax error at or near ")"`. The scheduler built `name IN ()` from the empty list. **Added: `sql.inList(values, cast?)`**, the one safe way to match a column against a JS list. `` col ${sql.inList(ids, 'uuid')} `` renders `= ANY(...)` over one jsonb parameter, so an empty list matches nothing instead of failing to parse. A `Date` travels as ISO text. The scheduler, trace partition sync, access-link deletes and step-graph queries now use it; those other sites were already guarded against an empty list.
|
package/REFERENCE.md
CHANGED
|
@@ -1284,7 +1284,7 @@ Signs a payload with HMAC for outbound delivery — returns body, signature, tim
|
|
|
1284
1284
|
|
|
1285
1285
|
Mail namespace — email through a job queue, SES v2 or a dev stand-in, bounce and complaint suppression, and receiving.
|
|
1286
1286
|
|
|
1287
|
-
Opt in with blueprint `email: {}`; the generated migration adds `pipework_email_messages` (`key` unique, `to_address`, `template`, `data`, `status` `queued | sending | sent | suppressed | failed`, `provider_message_id`, `error`, `trace_id`, `created_at`, `sent_at`, `sending_at`) and `pipework_email_suppressions` (`address` primary key, lowercased; `reason` `bounce | complaint`; `detail`; `created_at`), both `IF NOT EXISTS` and not tenant-scoped. `pipework_email_messages` also has `conversation_id` and `in_reply_to_inbound`. Receiving adds `pipework_email_inbound` (see `mail.inbound`).
|
|
1287
|
+
Opt in with blueprint `email: {}`; the generated migration adds `pipework_email_messages` (`key` unique, `to_address`, `template`, `data`, `status` `queued | sending | sent | suppressed | failed`, `provider_message_id`, `error`, `trace_id`, `created_at`, `sent_at`, `sending_at`, `failed_at`) and `pipework_email_suppressions` (`address` primary key, lowercased; `reason` `bounce | complaint`; `detail`; `created_at`), both `IF NOT EXISTS` and not tenant-scoped. `pipework_email_messages` also has `conversation_id` and `in_reply_to_inbound`. Receiving adds `pipework_email_inbound` (see `mail.inbound`).
|
|
1288
1288
|
|
|
1289
1289
|
### `mail.create`
|
|
1290
1290
|
|
|
@@ -1316,7 +1316,7 @@ Inbound handler for SES bounce and complaint events delivered through SNS; suppr
|
|
|
1316
1316
|
|
|
1317
1317
|
Receiving email: `inbound({ source, queue, onMessage, ... })` returns `{ webhook, jobs, scan, sweep, get }`; messages arrive parsed, threaded into conversations, attachments stored as tenant files.
|
|
1318
1318
|
|
|
1319
|
-
`mail.inbound({ source, queue, onMessage, storage?, resolveTenant?, rawRetentionDays? = 30, maxRawBytes? = 41943040, environment? })`. Needs blueprint `email: {}`: the table `pipework_email_inbound` (`source_id` unique, `source` `ses | folder`, `raw_location`, `raw_expires_at`, `raw_deleted_at`, `status` `received | processed | failed`, `error`, `message_id_header`, `in_reply_to`, `references`, `from_address`, `subject`, `conversation_id`, `tenant_id`, `verdicts`, `recipients`, `attachment_files`, `received_at`, `processed_at`, `trace_id`; `references`, `recipients` and `attachment_files` are jsonb arrays). Not tenant-scoped (the row exists before a tenant is known). It holds no bodies: the raw message is the copy, deleted by `sweep` after `rawRetentionDays`.
|
|
1319
|
+
`mail.inbound({ source, queue, onMessage, storage?, resolveTenant?, rawRetentionDays? = 30, maxRawBytes? = 41943040, environment? })`. Needs blueprint `email: {}`: the table `pipework_email_inbound` (`source_id` unique, `source` `ses | folder`, `raw_location`, `raw_expires_at`, `raw_deleted_at`, `status` `received | processed | failed`, `error`, `message_id_header`, `in_reply_to`, `references`, `from_address`, `subject`, `conversation_id`, `tenant_id`, `verdicts`, `recipients`, `attachment_files`, `received_at`, `processed_at`, `failed_at`, `trace_id`; `references`, `recipients` and `attachment_files` are jsonb arrays). Not tenant-scoped (the row exists before a tenant is known). It holds no bodies: the raw message is the copy, deleted by `sweep` after `rawRetentionDays`.
|
|
1320
1320
|
|
|
1321
1321
|
`webhook({ path })` (SES source) returns a public POST handler: the SNS message is verified exactly as for `mail.snsWebhook` (signature, certificate host, `topicArns`; 401 otherwise, nothing recorded), then one `pipework_email_inbound` row keyed by SES's `mail.messageId` and one `pipework.email.receive` job are written in one transaction; the same notification again adds nothing. A notification that is not `Received`, or whose receipt action is not `S3`, is answered 200 and ignored. Spread `jobs` into the worker surface's `jobs` (same queue).
|
|
1322
1322
|
|
|
@@ -1750,6 +1750,14 @@ Pass in `sources`; each raises with a fixed key on a tick and, where it can tell
|
|
|
1750
1750
|
- `alerts.source.deliveryFailed({ tables? = ['pipework_alert_deliveries'], stuckAfterMs? = 900_000, settleMs?, severity? = 'error', owner?, runbook?, database? })` — outbox rows that gave up: `delivery-failed:<table>:<sink>`, message `<n> delivery(ies) to <sink> gave up; last error: <last_error>` (clears by the quiet period); and rows still pending more than `stuckAfterMs` past `next_attempt_at`: `delivery-stuck:<table>:<sink>` (resolves when they drain). Name `support_event_delivery` in `tables` to cover the support outbox.
|
|
1751
1751
|
- `alerts.source.webhookFailed({ table? = '__pipework_webhook_deliveries', stalledAfterMs? = 900_000, settleMs?, severity?, owner?, runbook?, database? })` — outbound webhook deliveries (set `table` to your `deliveryTable`): `webhook-failed:<endpointId>` for deliveries that completed with no 2xx status, message `<n> webhook delivery(ies) to endpoint <id> gave up; last status <code|none>`; `webhook-stalled:<endpointId>` while deliveries are more than `stalledAfterMs` past `next_retry_at`.
|
|
1752
1752
|
- `alerts.source.credits({ heldOlderThanMs? = 3_600_000, expiryGraceMs? = 3_600_000, settleMs?, severity?, owner?, runbook?, database? })` — `credit-overdraft:<tenantId>` for settlements that overdrew a tenant (`<n> settlement(s) overdrew <tenantId> by <sum>`); `credit-held:<tenantId>` while reservations stay held past `heldOlderThanMs`; `credit-expiry-undrained:all` while grants expired more than `expiryGraceMs` ago still have credit (`<n> expired grant(s) not drained; is the credits expiry job scheduled?`). Needs `credits` configured.
|
|
1753
|
+
- `alerts.source.email({ stuckAfterMs? = 900_000, queuedAfterMs? = 3_600_000, receivedAfterMs? = 900_000, sweepGraceMs? = 3_600_000, settleMs?, severity?, owner?, runbook?, database? })` — needs `email: {}`. `email-failed:outbound` / `email-failed:inbound` for messages marked `failed` since the last tick (the new nullable `failed_at` column on both tables; run `pipework generate` and migrate; count and last error, no addresses); `email-stuck:sending` past `stuckAfterMs`; `email-stuck:queued` past `queuedAfterMs` (nothing drains the send queue); `email-inbound-stuck:received` past `receivedAfterMs`; `email-raw-undrained:all` while raw messages are `sweepGraceMs` past `raw_expires_at` and not deleted.
|
|
1754
|
+
- `alerts.source.storage({ sweepGraceMs? = 3_600_000, severity?, owner?, runbook?, database? })` — needs `storage: {}`. `storage-expiry-undrained:all` while `storage_file` rows are `sweepGraceMs` past `expires_at` and not swept.
|
|
1755
|
+
- `alerts.source.accessLinks({ graceMs? = 900_000, severity?, owner?, runbook?, database? })` — needs `accessLinks: {}`. `access-link-abandoned:<tenantId>` while uses stay `pending` `graceMs` past `reserved_until`.
|
|
1756
|
+
- `alerts.source.lockouts({ tables? = the ones that exist, severity? = 'warning', owner?, runbook?, database? })` — `auth-lockout:<table>` while rows have `locked_until` in the future (count only; no account is named). `pipework_signin_attempts` needs `passwords: {}`, `pipework_guard_attempts` needs `guards: {}`; with `tables` omitted, the tables that exist are resolved once on the first evaluate (it throws, naming both keys, if neither does); pass `tables` to override.
|
|
1757
|
+
- `alerts.source.schedules({ graceMs? = 300_000, severity?, owner?, runbook?, database? })` — `schedule-overdue:<name>` while a schedule's `next_run_at` is more than `graceMs` past (every scheduler pass moves it forward, firing or skipping, so overdue means no scheduler is running); resolves when it catches up. Needs a worker surface with schedules.
|
|
1758
|
+
- `alerts.source.steps({ notifyAfterMs? = 900_000, waitingAfterMs? = 3_600_000, runningAfterMs? = 3_600_000, settleMs?, severity?, owner?, runbook?, database? })` — `step-run-failed:<graph>` for runs that failed since the last tick (context `count`, `lastError`, `lastRunId`, `lastTenantId`; clears by the quiet period); `step-run-unnotified:<graph>` while runs finished more than `notifyAfterMs` ago still have `notified = false`; `step-task-waiting:<graph>` while a task has waited for a person's decision more than `waitingAfterMs` (measured from its run's `created_at`); `step-run-stalled:<graph>` while runs are still `running` more than `runningAfterMs` after `created_at` with no task `waiting` (`blocked` and `queued` tasks are work in flight). Needs a step graph.
|
|
1759
|
+
- `alerts.source.pipelines({ waitingAfterMs? = 3_600_000, runningAfterMs? = 3_600_000, settleMs?, severity?, owner?, runbook?, database? })` — `pipeline-failed:<pipeline>` for runs that failed since the last tick (context `count`, `lastError`, `lastRunId`, `lastTenantId`); `pipeline-waiting:<pipeline>` while runs have been `waiting` more than `waitingAfterMs` since they started (`started_at`; no wait time is recorded); `pipeline-stalled:<pipeline>` while runs are `running` more than `runningAfterMs` after `started_at`. Needs the app's `pipework_pipeline_runs` table.
|
|
1760
|
+
- `alerts.source.traces({ graceMs? = 93_600_000, settleMs?, severity?, owner?, runbook?, database? })` — `trace-expired:<entryKind>` for traces the expiry sweep force-closed since the last tick (read from `closed_at` where `status = 'expired'`); `trace-expiry-undrained:all` while open traces are more than `graceMs` (default 26 h, as the maintenance job is daily) past `expires_at`, meaning the expiry job is not running. Needs the `trace` block in the blueprint.
|
|
1753
1761
|
- `queue.totals(db)` — `{ overall, byType }`, each `{ ready, running, oldestReadyWaitMs }` (ready: pending and not waiting on a retry time; running: claimed or running; wait from `queued_at`, null when none is ready); `byType` omits a type with neither.
|
|
1754
1762
|
- `alerts.source.health(checks, { severity? = 'error' })` — `health:<name>` while a `HealthCheck` is unhealthy or throws; resolves when it recovers. Degraded is not a fault.
|
|
1755
1763
|
|
package/dist/REFERENCE.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Pipework API Reference
|
|
2
2
|
|
|
3
3
|
> Auto-generated from source JSDoc. Do not edit manually.
|
|
4
|
-
> Generated: 2026-10-
|
|
4
|
+
> Generated: 2026-10-22
|
|
5
5
|
|
|
6
6
|
## Contents
|
|
7
7
|
|
|
@@ -816,6 +816,10 @@ Acquires a transaction-scoped advisory lock. Released at commit/rollback.
|
|
|
816
816
|
|
|
817
817
|
Runs a function in a SERIALIZABLE transaction with automatic retry on serialization failures and deadlocks.
|
|
818
818
|
|
|
819
|
+
### `pipe.withTimeouts`
|
|
820
|
+
|
|
821
|
+
Sets statement_timeout and/or lock_timeout for the current transaction in one statement, then runs fn; a statement cancelled for them rejects with StatementTimeoutError or LockTimeoutError.
|
|
822
|
+
|
|
819
823
|
### `pipe.bulkSet`
|
|
820
824
|
|
|
821
825
|
Bulk UPDATE ... FROM (VALUES ...) — updates multiple rows with per-row values in a single statement.
|
|
@@ -824,6 +828,10 @@ Bulk UPDATE ... FROM (VALUES ...) — updates multiple rows with per-row values
|
|
|
824
828
|
|
|
825
829
|
Produces a typed VALUES clause for bulk operations — the base primitive for bulkSet and bulkUpsert.
|
|
826
830
|
|
|
831
|
+
### `pipe.fn`
|
|
832
|
+
|
|
833
|
+
A set-returning SQL function as a typed source for `.from()` and joins — `pipe.fn('schema.f', { args: [field.uuid()], columns: { path: field.text() } })`. Arguments are bound and cast to their declared types.
|
|
834
|
+
|
|
827
835
|
### `pipe.field`
|
|
828
836
|
|
|
829
837
|
Field builders for domain definitions — uuid(), text(), integer(), etc.
|
|
@@ -836,6 +844,10 @@ Registers a domain definition with table, validators, and factory projections. T
|
|
|
836
844
|
|
|
837
845
|
Returns the global registry of all domain definitions.
|
|
838
846
|
|
|
847
|
+
### `pipe.external`
|
|
848
|
+
|
|
849
|
+
Declares a table or view another service owns (`pipe.external(name, fields, { schema, table? })`): typed columns and every query operator, read-only, never migrated, not tenant-scoped.
|
|
850
|
+
|
|
839
851
|
### `pipe.update`
|
|
840
852
|
|
|
841
853
|
Pipework-owned UPDATE builder — the only consumer-facing way to update pipework-managed tables (Q8).
|
|
@@ -866,12 +878,54 @@ interface WindowSpec
|
|
|
866
878
|
interface ValueColumnDef
|
|
867
879
|
```
|
|
868
880
|
|
|
881
|
+
**`JsonbElements`** — A jsonb array read as its elements' text. Produced by {@link jsonbElements}.
|
|
882
|
+
|
|
883
|
+
```typescript
|
|
884
|
+
interface JsonbElements
|
|
885
|
+
```
|
|
886
|
+
|
|
887
|
+
**`ArraySource`** — Something whose elements `arrayAny` / `arrayAll` can test.
|
|
888
|
+
|
|
889
|
+
```typescript
|
|
890
|
+
type ArraySource =
|
|
891
|
+
```
|
|
892
|
+
|
|
893
|
+
**`ElementOf`** — The element type of an {@link ArraySource}.
|
|
894
|
+
|
|
895
|
+
```typescript
|
|
896
|
+
type ElementOf<S> =
|
|
897
|
+
```
|
|
898
|
+
|
|
899
|
+
**`FunctionSpec`** — Declares a SQL function's argument and result-column shapes. See {@link fn}.
|
|
900
|
+
|
|
901
|
+
```typescript
|
|
902
|
+
interface FunctionSpec<TArgs extends readonly AnyField[], TColumns extends Record<string, AnyField>>
|
|
903
|
+
```
|
|
904
|
+
|
|
905
|
+
**`FunctionSourceFields`** — The selected fields of a function source: each result column as a typed SQL expression.
|
|
906
|
+
|
|
907
|
+
```typescript
|
|
908
|
+
type FunctionSourceFields<TColumns extends Record<string, AnyField>> =
|
|
909
|
+
```
|
|
910
|
+
|
|
911
|
+
**`FunctionSource`** — A function call usable in `.from()` and joins; its result columns are properties of it.
|
|
912
|
+
|
|
913
|
+
```typescript
|
|
914
|
+
type FunctionSource<TAlias extends string, TColumns extends Record<string, AnyField>> =
|
|
915
|
+
```
|
|
916
|
+
|
|
869
917
|
**`SerializableOptions`** — Retry policy for {@link serializable}: how many attempts on a serialization failure / deadlock, and the base backoff between them.
|
|
870
918
|
|
|
871
919
|
```typescript
|
|
872
920
|
interface SerializableOptions
|
|
873
921
|
```
|
|
874
922
|
|
|
923
|
+
**`TimeoutOptions`** — Which timeouts {@link withTimeouts} sets; each a positive integer number of milliseconds.
|
|
924
|
+
|
|
925
|
+
```typescript
|
|
926
|
+
interface TimeoutOptions
|
|
927
|
+
```
|
|
928
|
+
|
|
875
929
|
**`ManagedConnection`** — A standalone pooled connection: the raw postgres.js client, its drizzle handle, and a `close()` that ends the pool.
|
|
876
930
|
|
|
877
931
|
```typescript
|
|
@@ -892,6 +946,10 @@ JSONB query operators — typed helpers for PostgreSQL JSONB containment, key ch
|
|
|
892
946
|
|
|
893
947
|
The @> containment operator — tests whether a JSONB column contains the given value.
|
|
894
948
|
|
|
949
|
+
### `jsonb.merge`
|
|
950
|
+
|
|
951
|
+
Merges an object into a JSONB column in `.set()`: `coalesce(col, '{}') || value`. Top-level keys overwrite; others are kept.
|
|
952
|
+
|
|
895
953
|
### `jsonb.containedBy`
|
|
896
954
|
|
|
897
955
|
The <@ contained-by operator — tests whether a JSONB column is contained by the given value.
|
|
@@ -958,6 +1016,60 @@ type Schema<T = unknown> = z.ZodType<T>
|
|
|
958
1016
|
type Infer<T extends Schema> = z.infer<T>
|
|
959
1017
|
```
|
|
960
1018
|
|
|
1019
|
+
**`StringSchema`** — A string validator — what check.string() returns, nameable without importing zod.
|
|
1020
|
+
|
|
1021
|
+
```typescript
|
|
1022
|
+
interface StringSchema extends z.ZodString
|
|
1023
|
+
```
|
|
1024
|
+
|
|
1025
|
+
**`NumberSchema`** — A number validator — what check.number() returns.
|
|
1026
|
+
|
|
1027
|
+
```typescript
|
|
1028
|
+
interface NumberSchema extends z.ZodNumber
|
|
1029
|
+
```
|
|
1030
|
+
|
|
1031
|
+
**`BooleanSchema`** — A boolean validator — what check.boolean() returns.
|
|
1032
|
+
|
|
1033
|
+
```typescript
|
|
1034
|
+
interface BooleanSchema extends z.ZodBoolean
|
|
1035
|
+
```
|
|
1036
|
+
|
|
1037
|
+
**`EnumSchema`** — An enum validator over string literals — what check.enum([...]) returns.
|
|
1038
|
+
|
|
1039
|
+
```typescript
|
|
1040
|
+
interface EnumSchema<V extends string = string> extends z.ZodEnum<
|
|
1041
|
+
```
|
|
1042
|
+
|
|
1043
|
+
**`LiteralSchema`** — A literal validator — what check.literal(v) returns.
|
|
1044
|
+
|
|
1045
|
+
```typescript
|
|
1046
|
+
interface LiteralSchema<V extends string | number | boolean | null = string> extends z.ZodLiteral<V>
|
|
1047
|
+
```
|
|
1048
|
+
|
|
1049
|
+
**`ArraySchema`** — An array validator — what check.array(item) returns.
|
|
1050
|
+
|
|
1051
|
+
```typescript
|
|
1052
|
+
interface ArraySchema<Item extends z.core.SomeType = z.core.SomeType> extends z.ZodArray<Item>
|
|
1053
|
+
```
|
|
1054
|
+
|
|
1055
|
+
**`OptionalSchema`** — An optional validator — what check.optional(inner) returns.
|
|
1056
|
+
|
|
1057
|
+
```typescript
|
|
1058
|
+
interface OptionalSchema<Inner extends z.core.SomeType = z.core.SomeType> extends z.ZodOptional<Inner>
|
|
1059
|
+
```
|
|
1060
|
+
|
|
1061
|
+
**`NullableSchema`** — A nullable validator — what check.nullable(inner) returns.
|
|
1062
|
+
|
|
1063
|
+
```typescript
|
|
1064
|
+
interface NullableSchema<Inner extends z.core.SomeType = z.core.SomeType> extends z.ZodNullable<Inner>
|
|
1065
|
+
```
|
|
1066
|
+
|
|
1067
|
+
**`ObjectSchema`** — An object validator whose .extend, .pick, .omit and .partial results stay ObjectSchema — what check.object({...}) returns.
|
|
1068
|
+
|
|
1069
|
+
```typescript
|
|
1070
|
+
type ObjectSchema<S extends Shape = Shape> =
|
|
1071
|
+
```
|
|
1072
|
+
|
|
961
1073
|
## temporal — Bitemporal Data
|
|
962
1074
|
|
|
963
1075
|
Temporal (bitemporal) data namespace — version history with effective date ranges.
|
|
@@ -1474,7 +1586,11 @@ interface RateLimitConfig
|
|
|
1474
1586
|
interface RateLimitWindow
|
|
1475
1587
|
```
|
|
1476
1588
|
|
|
1477
|
-
**`RateLimitClass`**
|
|
1589
|
+
**`RateLimitClass`**
|
|
1590
|
+
|
|
1591
|
+
A named limit class: any of a per-user, per-tenant, per-address and total window, each counted on its own.
|
|
1592
|
+
Each key takes one window or an array of windows (distinct `windowSeconds`); a request is counted against
|
|
1593
|
+
all of a key's windows and refused if any is over.
|
|
1478
1594
|
|
|
1479
1595
|
```typescript
|
|
1480
1596
|
interface RateLimitClass
|
|
@@ -1490,8 +1606,8 @@ interface RateLimitRefusal
|
|
|
1490
1606
|
|
|
1491
1607
|
Named limit classes, checked after auth and counted in Postgres (`pipework_rate_limit_counts`, opted in
|
|
1492
1608
|
with the blueprint's `rateLimits: {}`), so every server process on the same database shares one count.
|
|
1493
|
-
|
|
1494
|
-
refused requests don't spend the tenant's budget. A route is in `signedIn` (auth required, or optional with
|
|
1609
|
+
Keys are counted perUser, perTenant, perAddress, total in turn, up to the first key with a window over its max, so one user's
|
|
1610
|
+
refused requests don't spend the tenant's budget. The 429's Retry-After is the longest wait of the windows that refused. A route is in `signedIn` (auth required, or optional with
|
|
1495
1611
|
a user), `public` (no auth, or optional without a user) or the class its fitting names with `.rateLimit(name)`.
|
|
1496
1612
|
|
|
1497
1613
|
```typescript
|
|
@@ -1643,15 +1759,20 @@ interface TraceTree
|
|
|
1643
1759
|
|
|
1644
1760
|
## data
|
|
1645
1761
|
|
|
1646
|
-
### `
|
|
1762
|
+
### `StatementTimeoutError`
|
|
1763
|
+
|
|
1764
|
+
A statement was cancelled by Postgres for running past the statement timeout a {@link withTimeouts} call set. `cause` is the driver error.
|
|
1765
|
+
|
|
1766
|
+
```typescript
|
|
1767
|
+
class StatementTimeoutError extends Error constructor(readonly statementTimeoutMs: number, cause: unknown)
|
|
1768
|
+
```
|
|
1769
|
+
|
|
1770
|
+
### `LockTimeoutError`
|
|
1647
1771
|
|
|
1648
|
-
|
|
1649
|
-
sanctioned door for contexts where `pipe()` structurally cannot resolve:
|
|
1650
|
-
`worker_threads`, detached monitors, one-off scripts. Callers own the
|
|
1651
|
-
lifecycle: always `await conn.close()`.
|
|
1772
|
+
A statement gave up waiting for a lock past the lock timeout a {@link withTimeouts} call set. `cause` is the driver error.
|
|
1652
1773
|
|
|
1653
1774
|
```typescript
|
|
1654
|
-
|
|
1775
|
+
class LockTimeoutError extends Error constructor(readonly lockTimeoutMs: number, cause: unknown)
|
|
1655
1776
|
```
|
|
1656
1777
|
|
|
1657
1778
|
## request
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { type AlertSource } from './sources.js';
|
|
2
|
+
import { type Basics } from './source-options.js';
|
|
3
|
+
/**
|
|
4
|
+
* Raises `schedule-overdue:<schedule name>` while a cron schedule's `next_run_at` is more than `graceMs` (default 5 min)
|
|
5
|
+
* in the past. Every scheduler pass moves `next_run_at` into the future, whether it fires the job or skips it because
|
|
6
|
+
* the previous run is still pending, so an overdue row means no scheduler is running (or it cannot reach the row).
|
|
7
|
+
* Resolves when the scheduler catches up. Needs a worker surface that declares schedules (the `pipework_schedules` table; no blueprint key).
|
|
8
|
+
*/
|
|
9
|
+
export declare function schedulesSource(options?: Basics & {
|
|
10
|
+
graceMs?: number;
|
|
11
|
+
}): AlertSource;
|
|
12
|
+
/**
|
|
13
|
+
* Raises `step-run-failed:<graph>` for step runs that failed since the last tick (message with the count and the last
|
|
14
|
+
* error; context `count`, `lastError`, `lastRunId`, `lastTenantId`), `step-run-unnotified:<graph>` while runs finished
|
|
15
|
+
* more than `notifyAfterMs` (default 15 min) ago still have `notified = false` (the graph's `onDone` has not run), and
|
|
16
|
+
* `step-task-waiting:<graph>` while a task has waited for a person's decision more than `waitingAfterMs` (default 1 h);
|
|
17
|
+
* the wait is measured from its run's `created_at`, as tasks carry no timestamp, and `step-run-stalled:<graph>` while runs
|
|
18
|
+
* are still `running` more than `runningAfterMs` (default 1 h) after `created_at` with no task `waiting` (a `waiting` task is a
|
|
19
|
+
* person's decision and is covered above; `blocked` and `queued` tasks are work in flight, so a run stuck on them is stalled). A failed task always fails its run in the
|
|
20
|
+
* same transaction, so failed runs cover failed tasks. The failed condition clears by the handle's quiet period; the other two
|
|
21
|
+
* resolve when the rows drain. Needs a step graph defined in the app (the `pipework_step_run` and `pipework_step_task` tables; no blueprint key).
|
|
22
|
+
*/
|
|
23
|
+
export declare function stepsSource(options?: Basics & {
|
|
24
|
+
notifyAfterMs?: number;
|
|
25
|
+
waitingAfterMs?: number;
|
|
26
|
+
runningAfterMs?: number;
|
|
27
|
+
settleMs?: number;
|
|
28
|
+
}): AlertSource;
|
|
29
|
+
/**
|
|
30
|
+
* Raises `pipeline-failed:<pipeline name>` for pipeline runs that failed since the last tick (context `count`,
|
|
31
|
+
* `lastError`, `lastRunId`, `lastTenantId`), and `pipeline-waiting:<pipeline name>` while runs sit in `waiting` more than
|
|
32
|
+
* `waitingAfterMs` (default 1 h) after they started (the table keeps no time for when a run began to wait, so the age is
|
|
33
|
+
* `started_at`), and `pipeline-stalled:<pipeline name>` while runs are `running` more than `runningAfterMs` (default 1 h)
|
|
34
|
+
* after `started_at` (the process died mid-run). The failed condition clears by the handle's quiet period; the others
|
|
35
|
+
* resolve when the runs resume or finish. Needs the app's own `pipework_pipeline_runs` table (pipelines have no blueprint key; the app creates it).
|
|
36
|
+
*/
|
|
37
|
+
export declare function pipelinesSource(options?: Basics & {
|
|
38
|
+
waitingAfterMs?: number;
|
|
39
|
+
runningAfterMs?: number;
|
|
40
|
+
settleMs?: number;
|
|
41
|
+
}): AlertSource;
|
|
42
|
+
/**
|
|
43
|
+
* Raises `trace-expired:<entry kind>` (http, job, cron, test) for traces the expiry sweep force-closed since the last
|
|
44
|
+
* tick: the sweep stamps `status = 'expired'` and `closed_at`, so `closed_at` is the cursor. Also raises
|
|
45
|
+
* `trace-expiry-undrained:all` while traces still `open` have an `expires_at` more than `graceMs` (default 26 h: the maintenance job that
|
|
46
|
+
* runs the sweep is daily, so a trace legitimately stays open up to a day past its deadline) in the past, which means
|
|
47
|
+
* nothing is running the expiry job. The expired condition clears by the handle's quiet period; the
|
|
48
|
+
* undrained one resolves once the sweep runs. Needs the blueprint's `trace` block (the `trace` table).
|
|
49
|
+
*/
|
|
50
|
+
export declare function tracesSource(options?: Basics & {
|
|
51
|
+
graceMs?: number;
|
|
52
|
+
settleMs?: number;
|
|
53
|
+
}): AlertSource;
|
|
54
|
+
//# sourceMappingURL=async-sources.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"async-sources.d.ts","sourceRoot":"","sources":["../../../src/infra/alerts/async-sources.ts"],"names":[],"mappings":"AAIA,OAAO,EAA8C,KAAK,WAAW,EAAE,MAAM,cAAc,CAAA;AAC3F,OAAO,EAA6C,KAAK,MAAM,EAAE,MAAM,qBAAqB,CAAA;AAuB5F;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,OAAO,GAAE,MAAM,GAAG;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,WAAW,CAoBxF;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,OAAO,GAAE,MAAM,GAAG;IAAE,aAAa,CAAC,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,WAAW,CAiE/J;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,OAAO,GAAE,MAAM,GAAG;IAAE,cAAc,CAAC,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,WAAW,CA+C3I;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,OAAO,GAAE,MAAM,GAAG;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,WAAW,CA+BxG"}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
// Sources over the failures the async subsystems already record: cron schedules, step graphs, pipelines and
|
|
2
|
+
// trace expiry. Each is a composition of `querySource` and `newRowsSource`.
|
|
3
|
+
import { sql } from '../../data/db/sql.js';
|
|
4
|
+
import { composeSources, newRowsSource, querySource } from './sources.js';
|
|
5
|
+
import { common, positive, text, FIFTEEN_MIN, HOUR } from './source-options.js';
|
|
6
|
+
const FIVE_MIN = 5 * 60_000;
|
|
7
|
+
const TRACE_SWEEP_GRACE = 26 * HOUR;
|
|
8
|
+
/** The `error` jsonb of a recorded failure as one line: its `message`, `error` or `step`/`error` pair, else the JSON. */
|
|
9
|
+
function errorText(v) {
|
|
10
|
+
if (v === null || v === undefined)
|
|
11
|
+
return null;
|
|
12
|
+
const parsed = typeof v === 'string' ? safeParse(v) : v;
|
|
13
|
+
if (typeof parsed === 'string')
|
|
14
|
+
return parsed;
|
|
15
|
+
if (typeof parsed === 'object' && parsed !== null) {
|
|
16
|
+
const o = parsed;
|
|
17
|
+
const inner = o['message'] ?? o['error'];
|
|
18
|
+
if (typeof inner === 'string')
|
|
19
|
+
return typeof o['step'] === 'string' ? `${o['step']}: ${inner}` : inner;
|
|
20
|
+
if (typeof inner === 'object' && inner !== null)
|
|
21
|
+
return errorText(inner);
|
|
22
|
+
}
|
|
23
|
+
return JSON.stringify(parsed);
|
|
24
|
+
}
|
|
25
|
+
function safeParse(s) {
|
|
26
|
+
try {
|
|
27
|
+
return JSON.parse(s);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return s;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Raises `schedule-overdue:<schedule name>` while a cron schedule's `next_run_at` is more than `graceMs` (default 5 min)
|
|
35
|
+
* in the past. Every scheduler pass moves `next_run_at` into the future, whether it fires the job or skips it because
|
|
36
|
+
* the previous run is still pending, so an overdue row means no scheduler is running (or it cannot reach the row).
|
|
37
|
+
* Resolves when the scheduler catches up. Needs a worker surface that declares schedules (the `pipework_schedules` table; no blueprint key).
|
|
38
|
+
*/
|
|
39
|
+
export function schedulesSource(options = {}) {
|
|
40
|
+
const grace = positive(options.graceMs ?? FIVE_MIN, 'graceMs', 'alerts.source.schedules');
|
|
41
|
+
return querySource({
|
|
42
|
+
...common(options),
|
|
43
|
+
name: 'schedule-overdue',
|
|
44
|
+
query: sql `
|
|
45
|
+
SELECT name, next_run_at, last_run_at, (extract(epoch FROM clock_timestamp() - next_run_at) * 1000)::bigint AS overdue_ms
|
|
46
|
+
FROM pipework_schedules
|
|
47
|
+
WHERE next_run_at < clock_timestamp() - ${grace} * interval '1 millisecond'
|
|
48
|
+
ORDER BY name`,
|
|
49
|
+
id: r => String(r['name']),
|
|
50
|
+
message: r => `schedule ${String(r['name'])} is ${String(r['overdue_ms'])} ms past its next run; is the scheduler running?`,
|
|
51
|
+
subject: r => ({ entity: 'schedule', entityId: String(r['name']) }),
|
|
52
|
+
context: r => ({
|
|
53
|
+
schedule: String(r['name']),
|
|
54
|
+
overdueMs: Number(r['overdue_ms']),
|
|
55
|
+
nextRunAt: new Date(String(r['next_run_at'])).toISOString(),
|
|
56
|
+
lastRunAt: r['last_run_at'] === null ? null : new Date(String(r['last_run_at'])).toISOString(),
|
|
57
|
+
}),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Raises `step-run-failed:<graph>` for step runs that failed since the last tick (message with the count and the last
|
|
62
|
+
* error; context `count`, `lastError`, `lastRunId`, `lastTenantId`), `step-run-unnotified:<graph>` while runs finished
|
|
63
|
+
* more than `notifyAfterMs` (default 15 min) ago still have `notified = false` (the graph's `onDone` has not run), and
|
|
64
|
+
* `step-task-waiting:<graph>` while a task has waited for a person's decision more than `waitingAfterMs` (default 1 h);
|
|
65
|
+
* the wait is measured from its run's `created_at`, as tasks carry no timestamp, and `step-run-stalled:<graph>` while runs
|
|
66
|
+
* are still `running` more than `runningAfterMs` (default 1 h) after `created_at` with no task `waiting` (a `waiting` task is a
|
|
67
|
+
* person's decision and is covered above; `blocked` and `queued` tasks are work in flight, so a run stuck on them is stalled). A failed task always fails its run in the
|
|
68
|
+
* same transaction, so failed runs cover failed tasks. The failed condition clears by the handle's quiet period; the other two
|
|
69
|
+
* resolve when the rows drain. Needs a step graph defined in the app (the `pipework_step_run` and `pipework_step_task` tables; no blueprint key).
|
|
70
|
+
*/
|
|
71
|
+
export function stepsSource(options = {}) {
|
|
72
|
+
const notifyAfter = positive(options.notifyAfterMs ?? FIFTEEN_MIN, 'notifyAfterMs', 'alerts.source.steps');
|
|
73
|
+
const waitingAfter = positive(options.waitingAfterMs ?? HOUR, 'waitingAfterMs', 'alerts.source.steps');
|
|
74
|
+
const runningAfter = positive(options.runningAfterMs ?? HOUR, 'runningAfterMs', 'alerts.source.steps');
|
|
75
|
+
const failed = newRowsSource({
|
|
76
|
+
...common(options),
|
|
77
|
+
name: 'step-run-failed',
|
|
78
|
+
table: 'pipework_step_run',
|
|
79
|
+
at: 'finished_at',
|
|
80
|
+
where: sql `t.status = 'failed'`,
|
|
81
|
+
groupBy: 'graph',
|
|
82
|
+
...(options.settleMs !== undefined ? { settleMs: options.settleMs } : {}),
|
|
83
|
+
message: g => `${String(g.count)} run(s) of step graph ${g.id} failed; last error: ${text(errorText(g.last['error']))}`,
|
|
84
|
+
context: g => ({
|
|
85
|
+
graph: g.id,
|
|
86
|
+
count: g.count,
|
|
87
|
+
lastError: errorText(g.last['error']),
|
|
88
|
+
lastRunId: String(g.last['id']),
|
|
89
|
+
lastTenantId: String(g.last['tenant_id']),
|
|
90
|
+
}),
|
|
91
|
+
});
|
|
92
|
+
const unnotified = querySource({
|
|
93
|
+
...common(options),
|
|
94
|
+
name: 'step-run-unnotified',
|
|
95
|
+
query: sql `
|
|
96
|
+
SELECT graph, count(*)::int AS n, (extract(epoch FROM clock_timestamp() - min(finished_at)) * 1000)::bigint AS oldest_ms
|
|
97
|
+
FROM pipework_step_run
|
|
98
|
+
WHERE status = 'done' AND notified = false
|
|
99
|
+
AND finished_at < clock_timestamp() - ${notifyAfter} * interval '1 millisecond'
|
|
100
|
+
GROUP BY graph ORDER BY graph`,
|
|
101
|
+
id: r => String(r['graph']),
|
|
102
|
+
message: r => `${String(r['n'])} finished run(s) of step graph ${String(r['graph'])} not notified for more than ${String(notifyAfter)} ms (oldest ${String(r['oldest_ms'])} ms)`,
|
|
103
|
+
subject: r => ({ entity: 'step-graph', entityId: String(r['graph']) }),
|
|
104
|
+
context: r => ({ graph: String(r['graph']), count: Number(r['n']), oldestMs: Number(r['oldest_ms']) }),
|
|
105
|
+
});
|
|
106
|
+
const waiting = querySource({
|
|
107
|
+
...common(options),
|
|
108
|
+
name: 'step-task-waiting',
|
|
109
|
+
query: sql `
|
|
110
|
+
SELECT r.graph, count(*)::int AS n, (extract(epoch FROM clock_timestamp() - min(r.created_at)) * 1000)::bigint AS oldest_ms
|
|
111
|
+
FROM pipework_step_task k JOIN pipework_step_run r ON r.id = k.run_id
|
|
112
|
+
WHERE k.status = 'waiting' AND r.status = 'running'
|
|
113
|
+
AND r.created_at < clock_timestamp() - ${waitingAfter} * interval '1 millisecond'
|
|
114
|
+
GROUP BY r.graph ORDER BY r.graph`,
|
|
115
|
+
id: r => String(r['graph']),
|
|
116
|
+
message: r => `${String(r['n'])} task(s) of step graph ${String(r['graph'])} waiting more than ${String(waitingAfter)} ms (oldest run ${String(r['oldest_ms'])} ms old)`,
|
|
117
|
+
subject: r => ({ entity: 'step-graph', entityId: String(r['graph']) }),
|
|
118
|
+
context: r => ({ graph: String(r['graph']), count: Number(r['n']), oldestMs: Number(r['oldest_ms']) }),
|
|
119
|
+
});
|
|
120
|
+
const stalled = querySource({
|
|
121
|
+
...common(options),
|
|
122
|
+
name: 'step-run-stalled',
|
|
123
|
+
query: sql `
|
|
124
|
+
SELECT r.graph, count(*)::int AS n, (extract(epoch FROM clock_timestamp() - min(r.created_at)) * 1000)::bigint AS oldest_ms
|
|
125
|
+
FROM pipework_step_run r
|
|
126
|
+
WHERE r.status = 'running'
|
|
127
|
+
AND r.created_at < clock_timestamp() - ${runningAfter} * interval '1 millisecond'
|
|
128
|
+
AND NOT EXISTS (SELECT 1 FROM pipework_step_task k WHERE k.run_id = r.id AND k.status = 'waiting')
|
|
129
|
+
GROUP BY r.graph ORDER BY r.graph`,
|
|
130
|
+
id: r => String(r['graph']),
|
|
131
|
+
message: r => `${String(r['n'])} run(s) of step graph ${String(r['graph'])} still running after more than ${String(runningAfter)} ms with no person step waiting (oldest ${String(r['oldest_ms'])} ms)`,
|
|
132
|
+
subject: r => ({ entity: 'step-graph', entityId: String(r['graph']) }),
|
|
133
|
+
context: r => ({ graph: String(r['graph']), count: Number(r['n']), oldestMs: Number(r['oldest_ms']) }),
|
|
134
|
+
});
|
|
135
|
+
return composeSources('steps', [failed, unnotified, waiting, stalled]);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Raises `pipeline-failed:<pipeline name>` for pipeline runs that failed since the last tick (context `count`,
|
|
139
|
+
* `lastError`, `lastRunId`, `lastTenantId`), and `pipeline-waiting:<pipeline name>` while runs sit in `waiting` more than
|
|
140
|
+
* `waitingAfterMs` (default 1 h) after they started (the table keeps no time for when a run began to wait, so the age is
|
|
141
|
+
* `started_at`), and `pipeline-stalled:<pipeline name>` while runs are `running` more than `runningAfterMs` (default 1 h)
|
|
142
|
+
* after `started_at` (the process died mid-run). The failed condition clears by the handle's quiet period; the others
|
|
143
|
+
* resolve when the runs resume or finish. Needs the app's own `pipework_pipeline_runs` table (pipelines have no blueprint key; the app creates it).
|
|
144
|
+
*/
|
|
145
|
+
export function pipelinesSource(options = {}) {
|
|
146
|
+
const waitingAfter = positive(options.waitingAfterMs ?? HOUR, 'waitingAfterMs', 'alerts.source.pipelines');
|
|
147
|
+
const runningAfter = positive(options.runningAfterMs ?? HOUR, 'runningAfterMs', 'alerts.source.pipelines');
|
|
148
|
+
const failed = newRowsSource({
|
|
149
|
+
...common(options),
|
|
150
|
+
name: 'pipeline-failed',
|
|
151
|
+
table: 'pipework_pipeline_runs',
|
|
152
|
+
at: 'completed_at',
|
|
153
|
+
where: sql `t.status = 'failed'`,
|
|
154
|
+
groupBy: 'pipeline_name',
|
|
155
|
+
...(options.settleMs !== undefined ? { settleMs: options.settleMs } : {}),
|
|
156
|
+
message: g => `${String(g.count)} run(s) of pipeline ${g.id} failed; last error: ${text(errorText(g.last['error']))}`,
|
|
157
|
+
context: g => ({
|
|
158
|
+
pipeline: g.id,
|
|
159
|
+
count: g.count,
|
|
160
|
+
lastError: errorText(g.last['error']),
|
|
161
|
+
lastRunId: String(g.last['id']),
|
|
162
|
+
lastTenantId: String(g.last['tenant_id']),
|
|
163
|
+
}),
|
|
164
|
+
});
|
|
165
|
+
const waiting = querySource({
|
|
166
|
+
...common(options),
|
|
167
|
+
name: 'pipeline-waiting',
|
|
168
|
+
query: sql `
|
|
169
|
+
SELECT pipeline_name, count(*)::int AS n, (extract(epoch FROM clock_timestamp() - min(started_at)) * 1000)::bigint AS oldest_ms
|
|
170
|
+
FROM pipework_pipeline_runs
|
|
171
|
+
WHERE status = 'waiting' AND started_at < clock_timestamp() - ${waitingAfter} * interval '1 millisecond'
|
|
172
|
+
GROUP BY pipeline_name ORDER BY pipeline_name`,
|
|
173
|
+
id: r => String(r['pipeline_name']),
|
|
174
|
+
message: r => `${String(r['n'])} run(s) of pipeline ${String(r['pipeline_name'])} waiting more than ${String(waitingAfter)} ms (oldest ${String(r['oldest_ms'])} ms)`,
|
|
175
|
+
subject: r => ({ entity: 'pipeline', entityId: String(r['pipeline_name']) }),
|
|
176
|
+
context: r => ({ pipeline: String(r['pipeline_name']), count: Number(r['n']), oldestMs: Number(r['oldest_ms']) }),
|
|
177
|
+
});
|
|
178
|
+
const stalled = querySource({
|
|
179
|
+
...common(options),
|
|
180
|
+
name: 'pipeline-stalled',
|
|
181
|
+
query: sql `
|
|
182
|
+
SELECT pipeline_name, count(*)::int AS n, (extract(epoch FROM clock_timestamp() - min(started_at)) * 1000)::bigint AS oldest_ms
|
|
183
|
+
FROM pipework_pipeline_runs
|
|
184
|
+
WHERE status = 'running' AND started_at < clock_timestamp() - ${runningAfter} * interval '1 millisecond'
|
|
185
|
+
GROUP BY pipeline_name ORDER BY pipeline_name`,
|
|
186
|
+
id: r => String(r['pipeline_name']),
|
|
187
|
+
message: r => `${String(r['n'])} run(s) of pipeline ${String(r['pipeline_name'])} still running after more than ${String(runningAfter)} ms (oldest ${String(r['oldest_ms'])} ms)`,
|
|
188
|
+
subject: r => ({ entity: 'pipeline', entityId: String(r['pipeline_name']) }),
|
|
189
|
+
context: r => ({ pipeline: String(r['pipeline_name']), count: Number(r['n']), oldestMs: Number(r['oldest_ms']) }),
|
|
190
|
+
});
|
|
191
|
+
return composeSources('pipelines', [failed, waiting, stalled]);
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Raises `trace-expired:<entry kind>` (http, job, cron, test) for traces the expiry sweep force-closed since the last
|
|
195
|
+
* tick: the sweep stamps `status = 'expired'` and `closed_at`, so `closed_at` is the cursor. Also raises
|
|
196
|
+
* `trace-expiry-undrained:all` while traces still `open` have an `expires_at` more than `graceMs` (default 26 h: the maintenance job that
|
|
197
|
+
* runs the sweep is daily, so a trace legitimately stays open up to a day past its deadline) in the past, which means
|
|
198
|
+
* nothing is running the expiry job. The expired condition clears by the handle's quiet period; the
|
|
199
|
+
* undrained one resolves once the sweep runs. Needs the blueprint's `trace` block (the `trace` table).
|
|
200
|
+
*/
|
|
201
|
+
export function tracesSource(options = {}) {
|
|
202
|
+
const grace = positive(options.graceMs ?? TRACE_SWEEP_GRACE, 'graceMs', 'alerts.source.traces');
|
|
203
|
+
const expired = newRowsSource({
|
|
204
|
+
...common(options),
|
|
205
|
+
name: 'trace-expired',
|
|
206
|
+
table: 'trace',
|
|
207
|
+
at: 'closed_at',
|
|
208
|
+
where: sql `t.status = 'expired'`,
|
|
209
|
+
groupBy: 'entry_kind',
|
|
210
|
+
...(options.settleMs !== undefined ? { settleMs: options.settleMs } : {}),
|
|
211
|
+
message: g => `${String(g.count)} ${g.id} trace(s) force-closed after expiring; last trace ${String(g.last['id'])}`,
|
|
212
|
+
context: g => ({
|
|
213
|
+
entryKind: g.id,
|
|
214
|
+
count: g.count,
|
|
215
|
+
lastTraceId: String(g.last['id']),
|
|
216
|
+
lastTenantId: g.last['tenant_id'] === null || g.last['tenant_id'] === undefined ? null : String(g.last['tenant_id']),
|
|
217
|
+
}),
|
|
218
|
+
});
|
|
219
|
+
const undrained = querySource({
|
|
220
|
+
...common(options),
|
|
221
|
+
name: 'trace-expiry-undrained',
|
|
222
|
+
query: sql `
|
|
223
|
+
SELECT count(*)::int AS n, (extract(epoch FROM clock_timestamp() - min(expires_at)) * 1000)::bigint AS oldest_ms
|
|
224
|
+
FROM "trace"
|
|
225
|
+
WHERE status = 'open' AND expires_at <= clock_timestamp() - ${grace} * interval '1 millisecond'
|
|
226
|
+
HAVING count(*) > 0`,
|
|
227
|
+
id: () => 'all',
|
|
228
|
+
message: r => `${String(r['n'])} open trace(s) expired more than ${String(grace)} ms ago and not closed; is the trace expiry job scheduled?`,
|
|
229
|
+
context: r => ({ count: Number(r['n']), oldestMs: Number(r['oldest_ms']) }),
|
|
230
|
+
});
|
|
231
|
+
return composeSources('traces', [expired, undrained]);
|
|
232
|
+
}
|
|
233
|
+
//# sourceMappingURL=async-sources.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"async-sources.js","sourceRoot":"","sources":["../../../src/infra/alerts/async-sources.ts"],"names":[],"mappings":"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"}
|