@attestry/sdk 0.6.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/LICENSE +190 -0
- package/README.md +1269 -0
- package/dist/client.d.ts +58 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +74 -0
- package/dist/client.js.map +1 -0
- package/dist/constants.d.ts +7 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +43 -0
- package/dist/constants.js.map +1 -0
- package/dist/errors.d.ts +16 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +41 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/lines-parser.d.ts +50 -0
- package/dist/lines-parser.d.ts.map +1 -0
- package/dist/lines-parser.js +211 -0
- package/dist/lines-parser.js.map +1 -0
- package/dist/ndjson-parser.d.ts +57 -0
- package/dist/ndjson-parser.d.ts.map +1 -0
- package/dist/ndjson-parser.js +245 -0
- package/dist/ndjson-parser.js.map +1 -0
- package/dist/resources/abac-policies.d.ts +1034 -0
- package/dist/resources/abac-policies.d.ts.map +1 -0
- package/dist/resources/abac-policies.js +1519 -0
- package/dist/resources/abac-policies.js.map +1 -0
- package/dist/resources/audit-log.d.ts +588 -0
- package/dist/resources/audit-log.d.ts.map +1 -0
- package/dist/resources/audit-log.js +629 -0
- package/dist/resources/audit-log.js.map +1 -0
- package/dist/resources/batch.d.ts +845 -0
- package/dist/resources/batch.d.ts.map +1 -0
- package/dist/resources/batch.js +1074 -0
- package/dist/resources/batch.js.map +1 -0
- package/dist/resources/chat.d.ts +151 -0
- package/dist/resources/chat.d.ts.map +1 -0
- package/dist/resources/chat.js +124 -0
- package/dist/resources/chat.js.map +1 -0
- package/dist/resources/check.d.ts +348 -0
- package/dist/resources/check.d.ts.map +1 -0
- package/dist/resources/check.js +543 -0
- package/dist/resources/check.js.map +1 -0
- package/dist/resources/compliance-check.d.ts +330 -0
- package/dist/resources/compliance-check.d.ts.map +1 -0
- package/dist/resources/compliance-check.js +402 -0
- package/dist/resources/compliance-check.js.map +1 -0
- package/dist/resources/decisions.d.ts +1208 -0
- package/dist/resources/decisions.d.ts.map +1 -0
- package/dist/resources/decisions.js +1362 -0
- package/dist/resources/decisions.js.map +1 -0
- package/dist/resources/evidence-pack.d.ts +1080 -0
- package/dist/resources/evidence-pack.d.ts.map +1 -0
- package/dist/resources/evidence-pack.js +1789 -0
- package/dist/resources/evidence-pack.js.map +1 -0
- package/dist/resources/gate.d.ts +613 -0
- package/dist/resources/gate.d.ts.map +1 -0
- package/dist/resources/gate.js +737 -0
- package/dist/resources/gate.js.map +1 -0
- package/dist/resources/incidents.d.ts +136 -0
- package/dist/resources/incidents.d.ts.map +1 -0
- package/dist/resources/incidents.js +229 -0
- package/dist/resources/incidents.js.map +1 -0
- package/dist/resources/regulatory-changes.d.ts +307 -0
- package/dist/resources/regulatory-changes.d.ts.map +1 -0
- package/dist/resources/regulatory-changes.js +365 -0
- package/dist/resources/regulatory-changes.js.map +1 -0
- package/dist/resources/safe-input-read.d.ts +21 -0
- package/dist/resources/safe-input-read.d.ts.map +1 -0
- package/dist/resources/safe-input-read.js +57 -0
- package/dist/resources/safe-input-read.js.map +1 -0
- package/dist/resources/ship-gate.d.ts +475 -0
- package/dist/resources/ship-gate.d.ts.map +1 -0
- package/dist/resources/ship-gate.js +727 -0
- package/dist/resources/ship-gate.js.map +1 -0
- package/dist/resources/vision.d.ts +540 -0
- package/dist/resources/vision.d.ts.map +1 -0
- package/dist/resources/vision.js +1036 -0
- package/dist/resources/vision.js.map +1 -0
- package/dist/retry.d.ts +103 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +224 -0
- package/dist/retry.js.map +1 -0
- package/dist/sse-parser.d.ts +64 -0
- package/dist/sse-parser.d.ts.map +1 -0
- package/dist/sse-parser.js +271 -0
- package/dist/sse-parser.js.map +1 -0
- package/dist/transport.d.ts +142 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +455 -0
- package/dist/transport.js.map +1 -0
- package/dist/types.d.ts +61 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +44 -0
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import type { AttestryClient } from "../client.js";
|
|
2
|
+
import type { RequestOptions } from "../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Public closed-enum of supported severity values. Mirrors the kernel's
|
|
5
|
+
* route-local `validSeverities` const at
|
|
6
|
+
* `src/app/api/v1/regulatory-changes/route.ts:39`. Drift-pinned in
|
|
7
|
+
* `src/lib/incidents/__tests__/sdk-drift.test.ts`.
|
|
8
|
+
*
|
|
9
|
+
* Forward-compat: when a future severity value is added kernel-side,
|
|
10
|
+
* bump the SDK minor version and extend this array. The kernel returns
|
|
11
|
+
* 400 for any value outside this set; the SDK pre-rejects invalid
|
|
12
|
+
* values synchronously as `TypeError` (build-round D5 — closed-enum
|
|
13
|
+
* input validates at the SDK boundary so the failure is faster +
|
|
14
|
+
* clearer than waiting for the server's 400).
|
|
15
|
+
*/
|
|
16
|
+
export declare const REGULATORY_CHANGE_SEVERITIES: readonly ["critical", "high", "medium", "low"];
|
|
17
|
+
export type RegulatoryChangeSeverity = (typeof REGULATORY_CHANGE_SEVERITIES)[number];
|
|
18
|
+
/**
|
|
19
|
+
* Public closed-enum of supported status values. Mirrors the kernel's
|
|
20
|
+
* route-local `VALID_STATUSES` const at
|
|
21
|
+
* `src/app/api/v1/regulatory-changes/route.ts:62`. Drift-pinned in
|
|
22
|
+
* `src/lib/incidents/__tests__/sdk-drift.test.ts`.
|
|
23
|
+
*
|
|
24
|
+
* Same forward-compat trade-off as `REGULATORY_CHANGE_SEVERITIES`
|
|
25
|
+
* (build-round D5).
|
|
26
|
+
*/
|
|
27
|
+
export declare const REGULATORY_CHANGE_STATUSES: readonly ["new", "reviewed", "actioned", "dismissed"];
|
|
28
|
+
export type RegulatoryChangeStatus = (typeof REGULATORY_CHANGE_STATUSES)[number];
|
|
29
|
+
/**
|
|
30
|
+
* Wire shape for a single regulatory-change row. Source-of-truth at
|
|
31
|
+
* kernel `src/lib/db/schema.ts:1082-1111` (`regulatoryChanges` pgTable).
|
|
32
|
+
*
|
|
33
|
+
* The route returns raw Drizzle rows (no `rowToWireJson` mapper today —
|
|
34
|
+
* unlike `auditLog.export`'s stable wire shape). Drizzle serializes
|
|
35
|
+
* timestamp columns as ISO-8601 strings via `JSON.stringify(Date)`.
|
|
36
|
+
* jsonb columns (`affectedRequirements`, `aiAnalysis`,
|
|
37
|
+
* `statusTransitions`) are typed as `unknown` — the schema comment hints
|
|
38
|
+
* at concrete shapes (`string[]`, Reggie analysis, `[{status, date,
|
|
39
|
+
* source}]`) but they're not enforced server-side. Consumers parse via
|
|
40
|
+
* their own validators (build-round D3).
|
|
41
|
+
*
|
|
42
|
+
* `severity` and `status` are typed as `string` (NOT typed unions) —
|
|
43
|
+
* forward-compat for kernel-side enum additions that haven't landed in
|
|
44
|
+
* the SDK yet. The write-side `RegulatoryChangesListInput.severity` /
|
|
45
|
+
* `.status` use the literal-union for IDE auto-completion. Same
|
|
46
|
+
* asymmetry as `humanOversightState`/`policyOutcome` on
|
|
47
|
+
* `decisions.ingest` (build-round D2).
|
|
48
|
+
*
|
|
49
|
+
* **Field stability**: this wire shape IS the Drizzle row shape today.
|
|
50
|
+
* A future row-to-wire mapper (parallel to `rowToWireJson`) would
|
|
51
|
+
* stabilize it; until then a kernel-side column rename ripples directly
|
|
52
|
+
* to the SDK. The drift pin in `sdk-drift.test.ts` trip-wires that
|
|
53
|
+
* change at the kernel-source level.
|
|
54
|
+
*/
|
|
55
|
+
export interface RegulatoryChange {
|
|
56
|
+
/** UUID. */
|
|
57
|
+
id: string;
|
|
58
|
+
/** Framework code: `"EU_AI_ACT"` / `"COLORADO_AI_ACT"` / etc. — open string. */
|
|
59
|
+
framework: string;
|
|
60
|
+
title: string;
|
|
61
|
+
description: string | null;
|
|
62
|
+
/**
|
|
63
|
+
* `'amendment' | 'clarification' | 'enforcement' | 'guidance'` per
|
|
64
|
+
* the schema comment. Not enforced server-side — typed as `string`
|
|
65
|
+
* for forward-compat.
|
|
66
|
+
*/
|
|
67
|
+
changeType: string;
|
|
68
|
+
/**
|
|
69
|
+
* `'critical' | 'high' | 'medium' | 'low'` (closed enum, see
|
|
70
|
+
* `REGULATORY_CHANGE_SEVERITIES`). Typed as `string` for forward-compat
|
|
71
|
+
* (build-round D2).
|
|
72
|
+
*/
|
|
73
|
+
severity: string;
|
|
74
|
+
/** ISO-8601, nullable. */
|
|
75
|
+
effectiveDate: string | null;
|
|
76
|
+
/**
|
|
77
|
+
* jsonb — typed as `string[]` in the schema comment, but defensively
|
|
78
|
+
* `unknown` at the SDK boundary (build-round D3).
|
|
79
|
+
*/
|
|
80
|
+
affectedRequirements: unknown;
|
|
81
|
+
sourceUrl: string | null;
|
|
82
|
+
/** ISO-8601, nullable; kernel sorts the response DESC by this column. */
|
|
83
|
+
publishedAt: string | null;
|
|
84
|
+
/**
|
|
85
|
+
* `'eur_lex' | 'federal_register' | 'uk_legislation' | 'colorado_leg'
|
|
86
|
+
* | 'nist_gov' | 'rss_custom'` per schema comment. Open string —
|
|
87
|
+
* source pipeline IDs.
|
|
88
|
+
*/
|
|
89
|
+
sourceId: string | null;
|
|
90
|
+
/** External document ID from the source system. */
|
|
91
|
+
sourceReferenceId: string | null;
|
|
92
|
+
/** ISO-8601, nullable; kernel records when the row was scraped. */
|
|
93
|
+
ingestedAt: string | null;
|
|
94
|
+
/** E.g., `"European Commission"`, `"US Congress"`. */
|
|
95
|
+
authorityPublisher: string | null;
|
|
96
|
+
/**
|
|
97
|
+
* jsonb — Reggie's cached analysis result. Defensively `unknown`
|
|
98
|
+
* (build-round D3).
|
|
99
|
+
*/
|
|
100
|
+
aiAnalysis: unknown;
|
|
101
|
+
/** ISO-8601, nullable; kernel records when alerts were sent. */
|
|
102
|
+
notifiedAt: string | null;
|
|
103
|
+
/**
|
|
104
|
+
* `'draft' | 'introduced' | 'committee' | 'passed_one_chamber' |
|
|
105
|
+
* 'passed_both' | 'signed' | 'enacted' | 'vetoed' | 'withdrawn'` per
|
|
106
|
+
* schema comment. Open string — bill lifecycle state.
|
|
107
|
+
*/
|
|
108
|
+
billStatus: string | null;
|
|
109
|
+
/**
|
|
110
|
+
* jsonb — array of `{status, date, source}` per schema comment.
|
|
111
|
+
* Defensively `unknown` (build-round D3).
|
|
112
|
+
*/
|
|
113
|
+
statusTransitions: unknown;
|
|
114
|
+
/**
|
|
115
|
+
* `'new' | 'reviewed' | 'actioned' | 'dismissed'` (closed enum, see
|
|
116
|
+
* `REGULATORY_CHANGE_STATUSES`). Defaults `"new"` server-side. Typed
|
|
117
|
+
* as `string` for forward-compat (build-round D2).
|
|
118
|
+
*/
|
|
119
|
+
status: string;
|
|
120
|
+
/** `'high' | 'medium' | 'low'` per schema comment; defaults `"medium"` server-side. */
|
|
121
|
+
relevance: string | null;
|
|
122
|
+
/** ISO-8601, NOT NULL (server `defaultNow()`). */
|
|
123
|
+
createdAt: string;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Input shape for `regulatoryChanges.list(input?)`. All fields
|
|
127
|
+
* optional; a bare `regulatoryChanges.list()` call returns the most
|
|
128
|
+
* recent 200 non-dismissed regulatory-change rows (kernel default —
|
|
129
|
+
* see "Default-excludes-dismissed" in JSDoc on `list()`).
|
|
130
|
+
*
|
|
131
|
+
* Filters are AND-combined server-side. Date range filters apply to
|
|
132
|
+
* `publishedAt` (kernel `gte` / `lte`).
|
|
133
|
+
*/
|
|
134
|
+
export interface RegulatoryChangesListInput {
|
|
135
|
+
/**
|
|
136
|
+
* Open string — kernel forwards verbatim to a DB filter
|
|
137
|
+
* (`WHERE framework = ?`). The SDK does NOT pre-validate (forward-
|
|
138
|
+
* compat: new framework codes added kernel-side don't require an
|
|
139
|
+
* SDK bump). URIError defense via `assertEncodableQueryString`
|
|
140
|
+
* (carry-forward invariant #32).
|
|
141
|
+
*/
|
|
142
|
+
framework?: string;
|
|
143
|
+
/**
|
|
144
|
+
* Closed enum. Pre-validated against
|
|
145
|
+
* `REGULATORY_CHANGE_SEVERITIES`; SDK throws `TypeError`
|
|
146
|
+
* synchronously for unknown values (build-round D5; carry-forward
|
|
147
|
+
* invariant #41). Drift-pinned.
|
|
148
|
+
*/
|
|
149
|
+
severity?: RegulatoryChangeSeverity;
|
|
150
|
+
/**
|
|
151
|
+
* Closed enum. Pre-validated against
|
|
152
|
+
* `REGULATORY_CHANGE_STATUSES`; SDK throws `TypeError` synchronously
|
|
153
|
+
* for unknown values (build-round D5).
|
|
154
|
+
*
|
|
155
|
+
* **Default-excludes-dismissed**: when omitted, the kernel filters
|
|
156
|
+
* dismissed rows OUT (`WHERE status != 'dismissed'`). To include
|
|
157
|
+
* dismissed rows, pass `status: "dismissed"` (returns ONLY
|
|
158
|
+
* dismissed) or one of `"new"` / `"reviewed"` / `"actioned"` for an
|
|
159
|
+
* exact match. There is currently NO way to retrieve "everything
|
|
160
|
+
* including dismissed" via this endpoint.
|
|
161
|
+
*/
|
|
162
|
+
status?: RegulatoryChangeStatus;
|
|
163
|
+
/**
|
|
164
|
+
* Date-string lower bound on `publishedAt`. Kernel parses via
|
|
165
|
+
* `new Date(value)` and returns 400 with
|
|
166
|
+
* `error: "Invalid 'from' date format"` if `isNaN(parsed.getTime())`.
|
|
167
|
+
* The SDK does NOT pre-validate ISO-8601 format (build-round D6 —
|
|
168
|
+
* kernel's `new Date(...)` is lenient: `"May 7 2026"`, `"2026/05/07"`,
|
|
169
|
+
* and `"2026-05-07T00:00:00Z"` all parse; pre-validating to strict
|
|
170
|
+
* ISO would reject valid kernel inputs).
|
|
171
|
+
*/
|
|
172
|
+
from?: string;
|
|
173
|
+
/** Same semantics as `from`. */
|
|
174
|
+
to?: string;
|
|
175
|
+
/**
|
|
176
|
+
* Page size, 1..200; default 200 server-side. SDK pre-rejects
|
|
177
|
+
* `NaN` / `Infinity` / `<= 0` / non-integer as `TypeError`
|
|
178
|
+
* synchronously. Values > 200 are forwarded verbatim — server
|
|
179
|
+
* returns 400 (`"Invalid limit. Must be between 1 and 200."`).
|
|
180
|
+
* Build-round D4: kernel's MAX_LIMIT is the authority; pre-capping
|
|
181
|
+
* SDK-side would silently mask future kernel raises.
|
|
182
|
+
*/
|
|
183
|
+
limit?: number;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* RegulatoryChanges resource — sibling to `IncidentsResource`,
|
|
187
|
+
* `DecisionsResource`, `ChatResource`, `AuditLogResource`. Today wraps
|
|
188
|
+
* a single endpoint (`list`); the class is the landing pad for future
|
|
189
|
+
* regulatory-changes methods (mark-as-read, mark-as-actioned,
|
|
190
|
+
* subscribe-to-framework, etc.).
|
|
191
|
+
*/
|
|
192
|
+
export declare class RegulatoryChangesResource {
|
|
193
|
+
private readonly client;
|
|
194
|
+
constructor(client: AttestryClient);
|
|
195
|
+
/**
|
|
196
|
+
* List regulatory-change rows filtered by framework / severity /
|
|
197
|
+
* status / date range. Returns all matching rows up to `limit`
|
|
198
|
+
* (default 200 server-side, max 200). Rows arrive DESC by
|
|
199
|
+
* `publishedAt`.
|
|
200
|
+
*
|
|
201
|
+
* **Default-excludes-dismissed** (the non-obvious gotcha — read
|
|
202
|
+
* carefully): when `status` is OMITTED from the input, the kernel
|
|
203
|
+
* filters dismissed rows OUT (`WHERE status != 'dismissed'`). To
|
|
204
|
+
* retrieve dismissed rows, pass `status: "dismissed"` (returns ONLY
|
|
205
|
+
* dismissed). There is currently NO way to retrieve "everything
|
|
206
|
+
* including dismissed" via this endpoint — the kernel route hardcodes
|
|
207
|
+
* the exclusion at `route.ts:78-79`.
|
|
208
|
+
*
|
|
209
|
+
* **READ_SYSTEMS auth scope**: returns HTTP 401 for no/invalid API
|
|
210
|
+
* key, HTTP **403** for an authenticated key that lacks the
|
|
211
|
+
* READ_SYSTEMS permission. `auditLog.export` (ADMIN-only dual-auth)
|
|
212
|
+
* surfaces the SAME 401-vs-403 split — the auth models differ, the
|
|
213
|
+
* status surface does not (corrected session-22 hostile review #2).
|
|
214
|
+
* Consumers must distinguish 401 (re-authenticate) from 403 (request
|
|
215
|
+
* a different API key) at the call site.
|
|
216
|
+
*
|
|
217
|
+
* **Sync JSON list**: returns `Promise<RegulatoryChange[]>`. No
|
|
218
|
+
* pagination cursor — caller adjusts `limit` or filters to narrow the
|
|
219
|
+
* result set. An empty match returns `[]` (no 404).
|
|
220
|
+
*
|
|
221
|
+
* Errors:
|
|
222
|
+
* - `AttestryAPIError` (status 400) — invalid `from`/`to` date
|
|
223
|
+
* format (kernel's `new Date(...)` returned `NaN`), or
|
|
224
|
+
* `limit > 200` / `limit < 1` (server-side range check). Closed-
|
|
225
|
+
* enum 400s (`severity` / `status` not in enum) are
|
|
226
|
+
* UNREACHABLE through the SDK — pre-rejected as `TypeError`.
|
|
227
|
+
* - `AttestryAPIError` (status 401) — no API key OR invalid key
|
|
228
|
+
* (the `requireApiKey` branch).
|
|
229
|
+
* - `AttestryAPIError` (status 403) — authenticated key lacks
|
|
230
|
+
* `READ_SYSTEMS` permission (the `requireApiKeyWithPermission`
|
|
231
|
+
* branch).
|
|
232
|
+
* - `AttestryAPIError` (status 429) — rate limit (auto-retried by
|
|
233
|
+
* default — invariant #18; per-IP rate-limit key
|
|
234
|
+
* `v1-reg-changes:${ip}`).
|
|
235
|
+
* - `AttestryAPIError` (status 500) — internal kernel error (scrubbed
|
|
236
|
+
* message via `internalErrorResponse`).
|
|
237
|
+
* - `AttestryError` ("request aborted by caller") — caller-supplied
|
|
238
|
+
* `options.signal` fired (pre-aborted or mid-flight).
|
|
239
|
+
* - `TypeError` (synchronous, no fetch issued) — input failed
|
|
240
|
+
* SDK-side validation.
|
|
241
|
+
*
|
|
242
|
+
* **Notably ABSENT**:
|
|
243
|
+
* - **No 404** — empty filter set returns 200 with `data: []`. Do
|
|
244
|
+
* NOT special-case 404 in error handling.
|
|
245
|
+
* - **No 422** — closed enums return 400 (kernel uses inline
|
|
246
|
+
* string-includes parsing, not Zod for these query params).
|
|
247
|
+
* - **No 413** — limit is enforced by `parseInt` + range check.
|
|
248
|
+
*
|
|
249
|
+
* **SDK-side validation** (synchronous `TypeError`, no fetch
|
|
250
|
+
* issued):
|
|
251
|
+
* - `input` itself: optional; when provided, must be a non-null,
|
|
252
|
+
* non-array object.
|
|
253
|
+
* - `input.framework`: optional; non-empty string when provided.
|
|
254
|
+
* Lone-surrogate guard via `assertEncodableQueryString`
|
|
255
|
+
* (carry-forward invariant #32). NOT pre-validated as a known
|
|
256
|
+
* framework code (forward-compat).
|
|
257
|
+
* - `input.severity`: optional; one of `"critical"`/`"high"`/
|
|
258
|
+
* `"medium"`/`"low"` when provided. Pre-validated against the
|
|
259
|
+
* closed enum (`REGULATORY_CHANGE_SEVERITIES`).
|
|
260
|
+
* - `input.status`: optional; one of `"new"`/`"reviewed"`/
|
|
261
|
+
* `"actioned"`/`"dismissed"` when provided. Pre-validated against
|
|
262
|
+
* the closed enum (`REGULATORY_CHANGE_STATUSES`).
|
|
263
|
+
* - `input.from` / `input.to`: optional; non-empty string when
|
|
264
|
+
* provided. Lone-surrogate guard. **NOT pre-validated as ISO-8601**
|
|
265
|
+
* (build-round D6 — kernel's `new Date(...)` is lenient).
|
|
266
|
+
* - `input.limit`: optional; positive finite integer when provided.
|
|
267
|
+
* `NaN` / `Infinity` / `<= 0` / non-integer rejected. Values
|
|
268
|
+
* `> 200` forwarded verbatim (build-round D4).
|
|
269
|
+
*
|
|
270
|
+
* **Response-shape validation** (P2 hardening):
|
|
271
|
+
* - Rejects with `AttestryError` if the kernel response isn't an
|
|
272
|
+
* array. Per-row shape (the 21-field `RegulatoryChange`) is
|
|
273
|
+
* faithful-courier — NOT validated (P4 candidate).
|
|
274
|
+
*
|
|
275
|
+
* **Transport-shape validation** (P3 hardening):
|
|
276
|
+
* - Rejects with `AttestryAPIError` if the kernel responds with a
|
|
277
|
+
* non-`application/json` Content-Type — protects against
|
|
278
|
+
* proxy-injected HTML 200 pages parsing into junk consumer state.
|
|
279
|
+
*
|
|
280
|
+
* @example List the most recent 200 non-dismissed regulatory updates
|
|
281
|
+
* ```ts
|
|
282
|
+
* const changes = await client.regulatoryChanges.list();
|
|
283
|
+
* for (const change of changes) {
|
|
284
|
+
* console.log(change.framework, change.severity, change.title);
|
|
285
|
+
* }
|
|
286
|
+
* ```
|
|
287
|
+
*
|
|
288
|
+
* @example Filter to critical EU AI Act updates from last week
|
|
289
|
+
* ```ts
|
|
290
|
+
* const changes = await client.regulatoryChanges.list({
|
|
291
|
+
* framework: "EU_AI_ACT",
|
|
292
|
+
* severity: "critical",
|
|
293
|
+
* from: "2026-04-30T00:00:00Z",
|
|
294
|
+
* limit: 50,
|
|
295
|
+
* });
|
|
296
|
+
* ```
|
|
297
|
+
*
|
|
298
|
+
* @example Retrieve only dismissed rows (default omits them)
|
|
299
|
+
* ```ts
|
|
300
|
+
* const dismissed = await client.regulatoryChanges.list({
|
|
301
|
+
* status: "dismissed",
|
|
302
|
+
* });
|
|
303
|
+
* ```
|
|
304
|
+
*/
|
|
305
|
+
list(input?: RegulatoryChangesListInput, options?: RequestOptions): Promise<RegulatoryChange[]>;
|
|
306
|
+
}
|
|
307
|
+
//# sourceMappingURL=regulatory-changes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"regulatory-changes.d.ts","sourceRoot":"","sources":["../../src/resources/regulatory-changes.ts"],"names":[],"mappings":"AAsCA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAGlD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,4BAA4B,gDAK9B,CAAC;AAEZ,MAAM,MAAM,wBAAwB,GAClC,CAAC,OAAO,4BAA4B,CAAC,CAAC,MAAM,CAAC,CAAC;AAEhD;;;;;;;;GAQG;AACH,eAAO,MAAM,0BAA0B,uDAK5B,CAAC;AAEZ,MAAM,MAAM,sBAAsB,GAChC,CAAC,OAAO,0BAA0B,CAAC,CAAC,MAAM,CAAC,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,WAAW,gBAAgB;IAC/B,YAAY;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,gFAAgF;IAChF,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B;;;;OAIG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB,0BAA0B;IAC1B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B;;;OAGG;IACH,oBAAoB,EAAE,OAAO,CAAC;IAC9B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,yEAAyE;IACzE,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B;;;;OAIG;IACH,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,mDAAmD;IACnD,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,mEAAmE;IACnE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,sDAAsD;IACtD,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC;;;OAGG;IACH,UAAU,EAAE,OAAO,CAAC;IACpB,gEAAgE;IAChE,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B;;;;OAIG;IACH,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B;;;OAGG;IACH,iBAAiB,EAAE,OAAO,CAAC;IAC3B;;;;OAIG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,uFAAuF;IACvF,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,kDAAkD;IAClD,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,0BAA0B;IACzC;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,wBAAwB,CAAC;IACpC;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,EAAE,sBAAsB,CAAC;IAChC;;;;;;;;OAQG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gCAAgC;IAChC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ;;;;;;;OAOG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;GAMG;AACH,qBAAa,yBAAyB;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,cAAc;IAEnD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6GG;IACH,IAAI,CACF,KAAK,CAAC,EAAE,0BAA0B,EAClC,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,gBAAgB,EAAE,CAAC;CAwK/B"}
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
// ─── RegulatoryChanges resource ─────────────────────────────────────────────
|
|
2
|
+
//
|
|
3
|
+
// Wraps the regulatory-changes feed surface (Prompt R12):
|
|
4
|
+
//
|
|
5
|
+
// - GET /api/v1/regulatory-changes sync JSON list of regulatory updates
|
|
6
|
+
//
|
|
7
|
+
// Second non-decisions resource on `@attestry/sdk`. Sibling to
|
|
8
|
+
// `IncidentsResource`, `DecisionsResource`, `ChatResource`,
|
|
9
|
+
// `AuditLogResource`. Single public method today (`list`); the resource
|
|
10
|
+
// class exists as the landing pad for future regulatory-changes methods
|
|
11
|
+
// if/when the kernel adds them.
|
|
12
|
+
//
|
|
13
|
+
// READ_SYSTEMS auth scope: the kernel route gates on
|
|
14
|
+
// `apiKeyPermissions:[READ_SYSTEMS]`. Returns **HTTP 401** for no/invalid
|
|
15
|
+
// API key (via `requireApiKey` first), and **HTTP 403** for an authenticated
|
|
16
|
+
// key that lacks the READ_SYSTEMS permission. `auditLog.export` (ADMIN-only
|
|
17
|
+
// dual-auth) surfaces the SAME 401-vs-403 split — the auth MODELS differ
|
|
18
|
+
// (single READ_SYSTEMS permission vs ADMIN-only dual-auth) but the status
|
|
19
|
+
// surface does not (corrected session-22 hostile review #2; the prior
|
|
20
|
+
// "auditLog.export returns 401 for both" framing of invariant #42 was
|
|
21
|
+
// wrong). Consumers must pin both 401 and 403 branches separately.
|
|
22
|
+
//
|
|
23
|
+
// Sync JSON list response: reuses `client._request` and the existing
|
|
24
|
+
// `{success:true, data}` envelope-unwrap (carry-forward invariant #9).
|
|
25
|
+
// NO new SDK primitive needed — smaller blast radius than `auditLog.export`.
|
|
26
|
+
// Returns `Promise<RegulatoryChange[]>`.
|
|
27
|
+
//
|
|
28
|
+
// **Default-excludes-dismissed semantics** (the non-obvious gotcha):
|
|
29
|
+
// - `status` omitted → kernel filters dismissed rows OUT
|
|
30
|
+
// (`WHERE status != 'dismissed'`)
|
|
31
|
+
// - `status: "dismissed"` → only dismissed rows
|
|
32
|
+
// - `status: "new"|"reviewed"|"actioned"` → only that exact status
|
|
33
|
+
// There is currently NO way to retrieve "everything including dismissed"
|
|
34
|
+
// via this endpoint. Documented prominently in JSDoc on `list()` and
|
|
35
|
+
// `RegulatoryChangesListInput.status`. Pinned via URL test
|
|
36
|
+
// (omitted-status URL omits the param entirely; explicit-`"dismissed"`
|
|
37
|
+
// URL contains `status=dismissed`).
|
|
38
|
+
import { AttestryError } from "../errors.js";
|
|
39
|
+
import { readInputField } from "./safe-input-read.js";
|
|
40
|
+
/**
|
|
41
|
+
* Public closed-enum of supported severity values. Mirrors the kernel's
|
|
42
|
+
* route-local `validSeverities` const at
|
|
43
|
+
* `src/app/api/v1/regulatory-changes/route.ts:39`. Drift-pinned in
|
|
44
|
+
* `src/lib/incidents/__tests__/sdk-drift.test.ts`.
|
|
45
|
+
*
|
|
46
|
+
* Forward-compat: when a future severity value is added kernel-side,
|
|
47
|
+
* bump the SDK minor version and extend this array. The kernel returns
|
|
48
|
+
* 400 for any value outside this set; the SDK pre-rejects invalid
|
|
49
|
+
* values synchronously as `TypeError` (build-round D5 — closed-enum
|
|
50
|
+
* input validates at the SDK boundary so the failure is faster +
|
|
51
|
+
* clearer than waiting for the server's 400).
|
|
52
|
+
*/
|
|
53
|
+
export const REGULATORY_CHANGE_SEVERITIES = Object.freeze([
|
|
54
|
+
"critical",
|
|
55
|
+
"high",
|
|
56
|
+
"medium",
|
|
57
|
+
"low",
|
|
58
|
+
]);
|
|
59
|
+
/**
|
|
60
|
+
* Public closed-enum of supported status values. Mirrors the kernel's
|
|
61
|
+
* route-local `VALID_STATUSES` const at
|
|
62
|
+
* `src/app/api/v1/regulatory-changes/route.ts:62`. Drift-pinned in
|
|
63
|
+
* `src/lib/incidents/__tests__/sdk-drift.test.ts`.
|
|
64
|
+
*
|
|
65
|
+
* Same forward-compat trade-off as `REGULATORY_CHANGE_SEVERITIES`
|
|
66
|
+
* (build-round D5).
|
|
67
|
+
*/
|
|
68
|
+
export const REGULATORY_CHANGE_STATUSES = Object.freeze([
|
|
69
|
+
"new",
|
|
70
|
+
"reviewed",
|
|
71
|
+
"actioned",
|
|
72
|
+
"dismissed",
|
|
73
|
+
]);
|
|
74
|
+
/**
|
|
75
|
+
* RegulatoryChanges resource — sibling to `IncidentsResource`,
|
|
76
|
+
* `DecisionsResource`, `ChatResource`, `AuditLogResource`. Today wraps
|
|
77
|
+
* a single endpoint (`list`); the class is the landing pad for future
|
|
78
|
+
* regulatory-changes methods (mark-as-read, mark-as-actioned,
|
|
79
|
+
* subscribe-to-framework, etc.).
|
|
80
|
+
*/
|
|
81
|
+
export class RegulatoryChangesResource {
|
|
82
|
+
client;
|
|
83
|
+
constructor(client) {
|
|
84
|
+
this.client = client;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* List regulatory-change rows filtered by framework / severity /
|
|
88
|
+
* status / date range. Returns all matching rows up to `limit`
|
|
89
|
+
* (default 200 server-side, max 200). Rows arrive DESC by
|
|
90
|
+
* `publishedAt`.
|
|
91
|
+
*
|
|
92
|
+
* **Default-excludes-dismissed** (the non-obvious gotcha — read
|
|
93
|
+
* carefully): when `status` is OMITTED from the input, the kernel
|
|
94
|
+
* filters dismissed rows OUT (`WHERE status != 'dismissed'`). To
|
|
95
|
+
* retrieve dismissed rows, pass `status: "dismissed"` (returns ONLY
|
|
96
|
+
* dismissed). There is currently NO way to retrieve "everything
|
|
97
|
+
* including dismissed" via this endpoint — the kernel route hardcodes
|
|
98
|
+
* the exclusion at `route.ts:78-79`.
|
|
99
|
+
*
|
|
100
|
+
* **READ_SYSTEMS auth scope**: returns HTTP 401 for no/invalid API
|
|
101
|
+
* key, HTTP **403** for an authenticated key that lacks the
|
|
102
|
+
* READ_SYSTEMS permission. `auditLog.export` (ADMIN-only dual-auth)
|
|
103
|
+
* surfaces the SAME 401-vs-403 split — the auth models differ, the
|
|
104
|
+
* status surface does not (corrected session-22 hostile review #2).
|
|
105
|
+
* Consumers must distinguish 401 (re-authenticate) from 403 (request
|
|
106
|
+
* a different API key) at the call site.
|
|
107
|
+
*
|
|
108
|
+
* **Sync JSON list**: returns `Promise<RegulatoryChange[]>`. No
|
|
109
|
+
* pagination cursor — caller adjusts `limit` or filters to narrow the
|
|
110
|
+
* result set. An empty match returns `[]` (no 404).
|
|
111
|
+
*
|
|
112
|
+
* Errors:
|
|
113
|
+
* - `AttestryAPIError` (status 400) — invalid `from`/`to` date
|
|
114
|
+
* format (kernel's `new Date(...)` returned `NaN`), or
|
|
115
|
+
* `limit > 200` / `limit < 1` (server-side range check). Closed-
|
|
116
|
+
* enum 400s (`severity` / `status` not in enum) are
|
|
117
|
+
* UNREACHABLE through the SDK — pre-rejected as `TypeError`.
|
|
118
|
+
* - `AttestryAPIError` (status 401) — no API key OR invalid key
|
|
119
|
+
* (the `requireApiKey` branch).
|
|
120
|
+
* - `AttestryAPIError` (status 403) — authenticated key lacks
|
|
121
|
+
* `READ_SYSTEMS` permission (the `requireApiKeyWithPermission`
|
|
122
|
+
* branch).
|
|
123
|
+
* - `AttestryAPIError` (status 429) — rate limit (auto-retried by
|
|
124
|
+
* default — invariant #18; per-IP rate-limit key
|
|
125
|
+
* `v1-reg-changes:${ip}`).
|
|
126
|
+
* - `AttestryAPIError` (status 500) — internal kernel error (scrubbed
|
|
127
|
+
* message via `internalErrorResponse`).
|
|
128
|
+
* - `AttestryError` ("request aborted by caller") — caller-supplied
|
|
129
|
+
* `options.signal` fired (pre-aborted or mid-flight).
|
|
130
|
+
* - `TypeError` (synchronous, no fetch issued) — input failed
|
|
131
|
+
* SDK-side validation.
|
|
132
|
+
*
|
|
133
|
+
* **Notably ABSENT**:
|
|
134
|
+
* - **No 404** — empty filter set returns 200 with `data: []`. Do
|
|
135
|
+
* NOT special-case 404 in error handling.
|
|
136
|
+
* - **No 422** — closed enums return 400 (kernel uses inline
|
|
137
|
+
* string-includes parsing, not Zod for these query params).
|
|
138
|
+
* - **No 413** — limit is enforced by `parseInt` + range check.
|
|
139
|
+
*
|
|
140
|
+
* **SDK-side validation** (synchronous `TypeError`, no fetch
|
|
141
|
+
* issued):
|
|
142
|
+
* - `input` itself: optional; when provided, must be a non-null,
|
|
143
|
+
* non-array object.
|
|
144
|
+
* - `input.framework`: optional; non-empty string when provided.
|
|
145
|
+
* Lone-surrogate guard via `assertEncodableQueryString`
|
|
146
|
+
* (carry-forward invariant #32). NOT pre-validated as a known
|
|
147
|
+
* framework code (forward-compat).
|
|
148
|
+
* - `input.severity`: optional; one of `"critical"`/`"high"`/
|
|
149
|
+
* `"medium"`/`"low"` when provided. Pre-validated against the
|
|
150
|
+
* closed enum (`REGULATORY_CHANGE_SEVERITIES`).
|
|
151
|
+
* - `input.status`: optional; one of `"new"`/`"reviewed"`/
|
|
152
|
+
* `"actioned"`/`"dismissed"` when provided. Pre-validated against
|
|
153
|
+
* the closed enum (`REGULATORY_CHANGE_STATUSES`).
|
|
154
|
+
* - `input.from` / `input.to`: optional; non-empty string when
|
|
155
|
+
* provided. Lone-surrogate guard. **NOT pre-validated as ISO-8601**
|
|
156
|
+
* (build-round D6 — kernel's `new Date(...)` is lenient).
|
|
157
|
+
* - `input.limit`: optional; positive finite integer when provided.
|
|
158
|
+
* `NaN` / `Infinity` / `<= 0` / non-integer rejected. Values
|
|
159
|
+
* `> 200` forwarded verbatim (build-round D4).
|
|
160
|
+
*
|
|
161
|
+
* **Response-shape validation** (P2 hardening):
|
|
162
|
+
* - Rejects with `AttestryError` if the kernel response isn't an
|
|
163
|
+
* array. Per-row shape (the 21-field `RegulatoryChange`) is
|
|
164
|
+
* faithful-courier — NOT validated (P4 candidate).
|
|
165
|
+
*
|
|
166
|
+
* **Transport-shape validation** (P3 hardening):
|
|
167
|
+
* - Rejects with `AttestryAPIError` if the kernel responds with a
|
|
168
|
+
* non-`application/json` Content-Type — protects against
|
|
169
|
+
* proxy-injected HTML 200 pages parsing into junk consumer state.
|
|
170
|
+
*
|
|
171
|
+
* @example List the most recent 200 non-dismissed regulatory updates
|
|
172
|
+
* ```ts
|
|
173
|
+
* const changes = await client.regulatoryChanges.list();
|
|
174
|
+
* for (const change of changes) {
|
|
175
|
+
* console.log(change.framework, change.severity, change.title);
|
|
176
|
+
* }
|
|
177
|
+
* ```
|
|
178
|
+
*
|
|
179
|
+
* @example Filter to critical EU AI Act updates from last week
|
|
180
|
+
* ```ts
|
|
181
|
+
* const changes = await client.regulatoryChanges.list({
|
|
182
|
+
* framework: "EU_AI_ACT",
|
|
183
|
+
* severity: "critical",
|
|
184
|
+
* from: "2026-04-30T00:00:00Z",
|
|
185
|
+
* limit: 50,
|
|
186
|
+
* });
|
|
187
|
+
* ```
|
|
188
|
+
*
|
|
189
|
+
* @example Retrieve only dismissed rows (default omits them)
|
|
190
|
+
* ```ts
|
|
191
|
+
* const dismissed = await client.regulatoryChanges.list({
|
|
192
|
+
* status: "dismissed",
|
|
193
|
+
* });
|
|
194
|
+
* ```
|
|
195
|
+
*/
|
|
196
|
+
list(input, options) {
|
|
197
|
+
// Top-level shape — when provided, must be a non-null, non-array
|
|
198
|
+
// object. typeof null === "object" and typeof [] === "object", so
|
|
199
|
+
// guard both explicitly. Like auditLog.export, input is OPTIONAL —
|
|
200
|
+
// `()` and `(undefined)` are both valid.
|
|
201
|
+
//
|
|
202
|
+
// The six query fields are snapshotted into locals (declared here
|
|
203
|
+
// so they stay visible for the query construction below — `input`
|
|
204
|
+
// is optional, and when omitted the locals stay `undefined`). Each
|
|
205
|
+
// read goes through `readInputField`, which converts a throwing
|
|
206
|
+
// accessor's exception into the documented synchronous `TypeError`
|
|
207
|
+
// input contract (session-22 hostile review #1 — the SDK-wide
|
|
208
|
+
// MEDIUM-1 getter-throws fix). The `as` cast re-asserts only what
|
|
209
|
+
// the consumer's own `RegulatoryChangesListInput` type claims.
|
|
210
|
+
let framework;
|
|
211
|
+
let severity;
|
|
212
|
+
let status;
|
|
213
|
+
let from;
|
|
214
|
+
let to;
|
|
215
|
+
let limit;
|
|
216
|
+
if (input !== undefined) {
|
|
217
|
+
if (input === null ||
|
|
218
|
+
typeof input !== "object" ||
|
|
219
|
+
Array.isArray(input)) {
|
|
220
|
+
throw new TypeError("regulatoryChanges.list: `input` must be an object when provided");
|
|
221
|
+
}
|
|
222
|
+
framework = readInputField(input, "framework", "regulatoryChanges.list");
|
|
223
|
+
severity = readInputField(input, "severity", "regulatoryChanges.list");
|
|
224
|
+
status = readInputField(input, "status", "regulatoryChanges.list");
|
|
225
|
+
from = readInputField(input, "from", "regulatoryChanges.list");
|
|
226
|
+
to = readInputField(input, "to", "regulatoryChanges.list");
|
|
227
|
+
limit = readInputField(input, "limit", "regulatoryChanges.list");
|
|
228
|
+
// framework: open string. Non-empty when provided. Lone-surrogate
|
|
229
|
+
// guard (#32). NOT pre-validated as a known code.
|
|
230
|
+
if (framework !== undefined) {
|
|
231
|
+
if (typeof framework !== "string" || framework.length === 0) {
|
|
232
|
+
throw new TypeError("regulatoryChanges.list: `framework` must be a non-empty string when provided");
|
|
233
|
+
}
|
|
234
|
+
assertEncodableQueryString(framework, "framework", "regulatoryChanges.list");
|
|
235
|
+
}
|
|
236
|
+
// severity: closed enum. Pre-reject invalid values synchronously
|
|
237
|
+
// (#41 / build-round D5).
|
|
238
|
+
if (severity !== undefined) {
|
|
239
|
+
if (typeof severity !== "string" ||
|
|
240
|
+
!REGULATORY_CHANGE_SEVERITIES.includes(severity)) {
|
|
241
|
+
throw new TypeError(`regulatoryChanges.list: \`severity\` must be one of ${REGULATORY_CHANGE_SEVERITIES.join(", ")} when provided`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
// status: closed enum. Same treatment as severity.
|
|
245
|
+
if (status !== undefined) {
|
|
246
|
+
if (typeof status !== "string" ||
|
|
247
|
+
!REGULATORY_CHANGE_STATUSES.includes(status)) {
|
|
248
|
+
throw new TypeError(`regulatoryChanges.list: \`status\` must be one of ${REGULATORY_CHANGE_STATUSES.join(", ")} when provided`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
// from / to: open date strings. Non-empty when provided. NOT
|
|
252
|
+
// pre-validated as ISO-8601 (build-round D6 — kernel's
|
|
253
|
+
// `new Date(...)` is lenient). Lone-surrogate guard (#32).
|
|
254
|
+
if (from !== undefined) {
|
|
255
|
+
if (typeof from !== "string" || from.length === 0) {
|
|
256
|
+
throw new TypeError("regulatoryChanges.list: `from` must be a non-empty string when provided");
|
|
257
|
+
}
|
|
258
|
+
assertEncodableQueryString(from, "from", "regulatoryChanges.list");
|
|
259
|
+
}
|
|
260
|
+
if (to !== undefined) {
|
|
261
|
+
if (typeof to !== "string" || to.length === 0) {
|
|
262
|
+
throw new TypeError("regulatoryChanges.list: `to` must be a non-empty string when provided");
|
|
263
|
+
}
|
|
264
|
+
assertEncodableQueryString(to, "to", "regulatoryChanges.list");
|
|
265
|
+
}
|
|
266
|
+
// limit: positive finite integer. NaN / Infinity / fractional /
|
|
267
|
+
// <= 0 rejected. Stricter than kernel's 400 (build-round D4 —
|
|
268
|
+
// fail-loud-and-synchronous; mirrors auditLog.export's limit
|
|
269
|
+
// policy).
|
|
270
|
+
if (limit !== undefined) {
|
|
271
|
+
if (typeof limit !== "number" ||
|
|
272
|
+
!Number.isInteger(limit) ||
|
|
273
|
+
limit <= 0) {
|
|
274
|
+
throw new TypeError("regulatoryChanges.list: `limit` must be a positive integer when provided");
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
return this.client
|
|
279
|
+
._request({
|
|
280
|
+
method: "GET",
|
|
281
|
+
path: "/api/v1/regulatory-changes",
|
|
282
|
+
query: {
|
|
283
|
+
framework,
|
|
284
|
+
severity,
|
|
285
|
+
status,
|
|
286
|
+
from,
|
|
287
|
+
to,
|
|
288
|
+
limit,
|
|
289
|
+
},
|
|
290
|
+
options,
|
|
291
|
+
})
|
|
292
|
+
.then((result) => {
|
|
293
|
+
// P2 hardening: validate the kernel returned an array. The
|
|
294
|
+
// route emits `successResponse(changes)` where `changes` comes
|
|
295
|
+
// from Drizzle's `db.select()...limit(N)` which always returns
|
|
296
|
+
// Array — but a kernel-side regression to scalar/null/undefined
|
|
297
|
+
// would let `null as RegulatoryChange[]` reach consumers, who
|
|
298
|
+
// would crash on `out.length` with a confusing TypeError.
|
|
299
|
+
// Catch it at the SDK boundary with a clear AttestryError.
|
|
300
|
+
// NOTE: G6 documented behavior (wrong content-type with
|
|
301
|
+
// unparseable body resolves to null) is changed in P3 — see
|
|
302
|
+
// the P3 audit-prompt for the transport-level content-type
|
|
303
|
+
// guard that subsumes this null-case.
|
|
304
|
+
if (!Array.isArray(result)) {
|
|
305
|
+
throw new AttestryError(`regulatoryChanges.list: expected an array response from the kernel (got ${describeType(result)})`);
|
|
306
|
+
}
|
|
307
|
+
return result;
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Synchronously verify a query-string value is encodable via
|
|
313
|
+
* `encodeURIComponent`. Mirrors the helper in `decisions.ts` and
|
|
314
|
+
* `audit-log.ts` (carry-forward invariant #32 — URIError defect-class
|
|
315
|
+
* is uniformly handled).
|
|
316
|
+
*
|
|
317
|
+
* Duplicated rather than shared because cross-resource imports between
|
|
318
|
+
* `regulatory-changes.ts`, `audit-log.ts`, and `decisions.ts` would
|
|
319
|
+
* create graph-cycle hazards — all three want to remain leaf-resource
|
|
320
|
+
* modules. A future SDK refactor may extract validation helpers to a
|
|
321
|
+
* shared module (e.g., `src/validate.ts`) when a fourth caller shows
|
|
322
|
+
* up; for now the duplication is intentional and documented.
|
|
323
|
+
*/
|
|
324
|
+
function assertEncodableQueryString(value, fieldName, methodName) {
|
|
325
|
+
try {
|
|
326
|
+
encodeURIComponent(value);
|
|
327
|
+
}
|
|
328
|
+
catch (err) {
|
|
329
|
+
throw new TypeError(`${methodName}: \`${fieldName}\` contains invalid UTF-16 sequences (${
|
|
330
|
+
// encodeURIComponent always throws URIError (an Error subclass),
|
|
331
|
+
// so the String(err) branch is unreachable. Defense-in-depth
|
|
332
|
+
// marker for the v8 coverage tool.
|
|
333
|
+
/* v8 ignore next */
|
|
334
|
+
err instanceof Error ? err.message : String(err)})`, { cause: err });
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Human-readable type description for response-shape error messages.
|
|
339
|
+
* Distinguishes `null` and `array` from generic `object`.
|
|
340
|
+
*
|
|
341
|
+
* Duplicated in `decisions.ts` and `incidents.ts` per project pattern
|
|
342
|
+
* (small helper, leaf-resource modules, no shared module yet).
|
|
343
|
+
*
|
|
344
|
+
* In regulatoryChanges.list, the validator's outer check is
|
|
345
|
+
* `!Array.isArray(result)` — describeType is only invoked when the
|
|
346
|
+
* result is NOT an array. The Array.isArray branch below is therefore
|
|
347
|
+
* structurally unreachable in this file's call site (kept for helper
|
|
348
|
+
* symmetry with the sibling files where the branch IS reachable —
|
|
349
|
+
* decisions.ts and incidents.ts both call describeType from
|
|
350
|
+
* "expected object, got X" contexts where X may be an array).
|
|
351
|
+
*/
|
|
352
|
+
function describeType(value) {
|
|
353
|
+
if (value === null)
|
|
354
|
+
return "null";
|
|
355
|
+
// The validator above guarantees `value` is not an array when this
|
|
356
|
+
// helper is invoked from regulatoryChanges.list. Branch retained
|
|
357
|
+
// for helper symmetry with decisions.ts / incidents.ts (where it
|
|
358
|
+
// IS reachable) and as defense-in-depth if the validator's outer
|
|
359
|
+
// check is ever changed. Coverage marker:
|
|
360
|
+
/* v8 ignore next */
|
|
361
|
+
if (Array.isArray(value))
|
|
362
|
+
return "array";
|
|
363
|
+
return typeof value;
|
|
364
|
+
}
|
|
365
|
+
//# sourceMappingURL=regulatory-changes.js.map
|