@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,402 @@
|
|
|
1
|
+
// ─── ComplianceCheck resource ───────────────────────────────────────────────
|
|
2
|
+
//
|
|
3
|
+
// Wraps the compliance-check surface (session 15):
|
|
4
|
+
//
|
|
5
|
+
// - GET /api/v1/compliance-check?systemId=<UUID>
|
|
6
|
+
// - GET /api/v1/compliance-check?orgName=<string>
|
|
7
|
+
//
|
|
8
|
+
// Third non-decisions resource on `@attestry/sdk`. Sibling to
|
|
9
|
+
// `IncidentsResource`, `DecisionsResource`, `ChatResource`,
|
|
10
|
+
// `AuditLogResource`, `RegulatoryChangesResource`. Single public
|
|
11
|
+
// method today (`check`); the resource class exists as the landing
|
|
12
|
+
// pad for future compliance-check methods if/when the kernel adds
|
|
13
|
+
// them (resource-class-per-kernel-resource convention, carry-forward
|
|
14
|
+
// invariant candidate #43).
|
|
15
|
+
//
|
|
16
|
+
// Multi-permission UNION auth scope: the kernel route gates on
|
|
17
|
+
// `requireApiKeyWithPermission(request, READ_SYSTEMS, READ_ASSESSMENTS)`
|
|
18
|
+
// which is OR semantics — `permissions.ts:53-55` uses `Array.some()`,
|
|
19
|
+
// NOT `.every()`. A key with EITHER permission (or `ADMIN`, or empty
|
|
20
|
+
// permissions for backwards-compat) succeeds. **HTTP 401** for
|
|
21
|
+
// no/invalid API key (the `requireApiKey` branch fires first inside
|
|
22
|
+
// `requireApiKeyWithPermission`); **HTTP 403** for an authenticated
|
|
23
|
+
// key that has NEITHER required permission. Pin BOTH branches
|
|
24
|
+
// separately. **Different from `auditLog.export`** which returns 401
|
|
25
|
+
// for both unauth and insufficient-permission (ADMIN-only convention;
|
|
26
|
+
// carry-forward invariant #42 governs ADMIN routes specifically).
|
|
27
|
+
// First SDK route to exercise the multi-arg form of
|
|
28
|
+
// `requireApiKeyWithPermission` — invariant candidate #45.
|
|
29
|
+
//
|
|
30
|
+
// XOR input mode (the **first** non-obvious gotcha): exactly one of
|
|
31
|
+
// `systemId` OR `orgName` must be provided. The kernel is NOT strictly
|
|
32
|
+
// XOR — when both are provided, kernel uses `systemId` and silently
|
|
33
|
+
// ignores `orgName` (route.ts:80-87). The SDK is **stricter** than the
|
|
34
|
+
// kernel — synchronously rejects "both provided" with a clear
|
|
35
|
+
// `TypeError`. This protects consumers from silent shadow-of-orgName
|
|
36
|
+
// bugs that the kernel quietly enables. Invariant candidate #46.
|
|
37
|
+
//
|
|
38
|
+
// Asymmetric cross-org error codes (the **second** non-obvious
|
|
39
|
+
// gotcha): cross-org systemId returns **404** (kernel collapses to
|
|
40
|
+
// "System not found", route.ts:76 — mirror of decisions.retrieve),
|
|
41
|
+
// while cross-org orgName returns **403** ("Access denied",
|
|
42
|
+
// route.ts:95). Pin BOTH branches and document the asymmetry in
|
|
43
|
+
// JSDoc + README. Invariant candidate #47.
|
|
44
|
+
//
|
|
45
|
+
// Silent `.limit(100)` on orgName path (the **third** non-obvious
|
|
46
|
+
// gotcha): route.ts:107 hardcodes `.limit(100)` on the org-systems
|
|
47
|
+
// query. Orgs with more than 100 systems see a truncated set with NO
|
|
48
|
+
// indicator — no `total` field, no `hasMore` cursor. The SDK does NOT
|
|
49
|
+
// mask this — JSDoc + README call it out as a kernel surface gap.
|
|
50
|
+
// Faithful-courier policy.
|
|
51
|
+
//
|
|
52
|
+
// Sync JSON request/response: reuses `client._request` and the
|
|
53
|
+
// existing `{success:true, data}` envelope-unwrap (carry-forward
|
|
54
|
+
// invariant #9). NO new SDK primitive needed — smaller blast radius
|
|
55
|
+
// than `auditLog.export`. Returns
|
|
56
|
+
// `Promise<ComplianceCheckResponse>`.
|
|
57
|
+
import { AttestryError } from "../errors.js";
|
|
58
|
+
import { readInputField } from "./safe-input-read.js";
|
|
59
|
+
// Module-load snapshot of `Object.hasOwn` — defends against a
|
|
60
|
+
// late-loading hostile/buggy npm dependency that overrides the global
|
|
61
|
+
// (e.g., `Object.hasOwn = () => true`). Without the snapshot, the H4
|
|
62
|
+
// prototype-pollution defense uses whatever Object.hasOwn the
|
|
63
|
+
// dependency replaced it with at request time. Snapshotting at module
|
|
64
|
+
// load captures the original implementation BEFORE most consumer
|
|
65
|
+
// code has a chance to monkey-patch.
|
|
66
|
+
//
|
|
67
|
+
// Caveat: this is partial. If the hostile dependency is imported
|
|
68
|
+
// BEFORE @attestry/sdk in the consumer's load graph, the snapshot
|
|
69
|
+
// captures the bad version. Consumers ordering imports
|
|
70
|
+
// SDK-then-untrusted-deps benefit; the reverse ordering does not.
|
|
71
|
+
// Combined with `Object.hasOwn` itself being immune to
|
|
72
|
+
// `obj.hasOwnProperty = ...` overrides (per MDN), this gives a
|
|
73
|
+
// layered defense — perfect protection isn't possible at the SDK
|
|
74
|
+
// layer.
|
|
75
|
+
//
|
|
76
|
+
// Hostile review session 15 finding (LOW #2).
|
|
77
|
+
const objectHasOwn = Object.hasOwn;
|
|
78
|
+
/**
|
|
79
|
+
* ComplianceCheck resource — sibling to `IncidentsResource`,
|
|
80
|
+
* `DecisionsResource`, `ChatResource`, `AuditLogResource`,
|
|
81
|
+
* `RegulatoryChangesResource`. Today wraps a single endpoint
|
|
82
|
+
* (`check`); the class is the landing pad for future compliance-check
|
|
83
|
+
* methods if the kernel adds them (resource-class-per-kernel-resource
|
|
84
|
+
* convention, invariant candidate #43).
|
|
85
|
+
*/
|
|
86
|
+
export class ComplianceCheckResource {
|
|
87
|
+
client;
|
|
88
|
+
constructor(client) {
|
|
89
|
+
this.client = client;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Return a per-system compliance summary for either a single system
|
|
93
|
+
* (by UUID) or every system in an org (by org name, capped at 100).
|
|
94
|
+
* Returns `Promise<ComplianceCheckResponse>` — sync JSON, no
|
|
95
|
+
* pagination, no streaming.
|
|
96
|
+
*
|
|
97
|
+
* **Input mode — XOR with kernel quirk** (read carefully): exactly
|
|
98
|
+
* one of `systemId` OR `orgName` must be provided. The kernel is
|
|
99
|
+
* NOT strictly XOR — when both are provided, kernel silently picks
|
|
100
|
+
* `systemId` and ignores `orgName`. The SDK is **stricter** than
|
|
101
|
+
* the kernel and synchronously throws `TypeError` when both are
|
|
102
|
+
* provided. This is a deliberate D3 deviation: the kernel's
|
|
103
|
+
* silent-pick is a quirk that future maintenance could change at
|
|
104
|
+
* any time, and surfacing the conflict at the SDK boundary makes
|
|
105
|
+
* consumer code stable across kernel revisions.
|
|
106
|
+
*
|
|
107
|
+
* **Multi-permission UNION auth scope**: kernel uses
|
|
108
|
+
* `requireApiKeyWithPermission(req, READ_SYSTEMS, READ_ASSESSMENTS)`
|
|
109
|
+
* which is OR semantics (`Array.some()` at
|
|
110
|
+
* `permissions.ts:53-55`). A key with EITHER permission (or `ADMIN`,
|
|
111
|
+
* or null/empty permissions for backwards-compat) succeeds.
|
|
112
|
+
* **HTTP 401** for no/invalid API key, **HTTP 403** for an
|
|
113
|
+
* authenticated key that has NEITHER required permission. Pin
|
|
114
|
+
* BOTH branches separately. **Distinct from `auditLog.export`** in
|
|
115
|
+
* the auth MODEL — that route is ADMIN-only dual-auth — but NOT in
|
|
116
|
+
* the status surface: `auditLog.export` also returns 401 vs 403
|
|
117
|
+
* distinctly (corrected session-22 hostile review #2; the prior
|
|
118
|
+
* "ADMIN-only 401-for-both" framing of invariant #42 was wrong).
|
|
119
|
+
* First SDK route to exercise multi-arg
|
|
120
|
+
* `requireApiKeyWithPermission` — invariant candidate #45.
|
|
121
|
+
*
|
|
122
|
+
* **Asymmetric cross-org error codes** (read carefully):
|
|
123
|
+
* cross-org `systemId` returns **404** (kernel collapses to
|
|
124
|
+
* "System not found" at route.ts:76 — mirror of
|
|
125
|
+
* `decisions.retrieve`); cross-org `orgName` returns **403**
|
|
126
|
+
* ("Access denied" at route.ts:95). Consumers writing defensive
|
|
127
|
+
* error-handling logic must distinguish: a 404 on systemId path
|
|
128
|
+
* may be "not your org" OR "genuine missing UUID"; a 403 on
|
|
129
|
+
* orgName path is unambiguously "the org exists but you don't
|
|
130
|
+
* own it". Invariant candidate #47.
|
|
131
|
+
*
|
|
132
|
+
* **Silent `.limit(100)` on orgName path** (read carefully): if
|
|
133
|
+
* the org has more than 100 systems, the response is silently
|
|
134
|
+
* truncated to the first 100 — NO `total` field, NO `hasMore`
|
|
135
|
+
* cursor, NO warning. The SDK does NOT mask this (faithful
|
|
136
|
+
* courier — kernel decided 100 is enough). Consumers managing
|
|
137
|
+
* >100-system orgs should switch to systemId-per-row.
|
|
138
|
+
*
|
|
139
|
+
* **`compliant` field — implicit threshold of 70**:
|
|
140
|
+
* `compliant === activeAttestations > 0 && (overallScore === null || overallScore >= 70)`.
|
|
141
|
+
* Documented in detail on `ComplianceCheckResult.compliant` —
|
|
142
|
+
* consumers wanting a different bar can apply it post-hoc via
|
|
143
|
+
* the `score` field.
|
|
144
|
+
*
|
|
145
|
+
* Errors (kernel firing precedence: rate-limit → auth → input
|
|
146
|
+
* validation, so a request with multiple problems surfaces only the
|
|
147
|
+
* highest-precedence one. Hostile-review LOW #8):
|
|
148
|
+
* - `AttestryAPIError` (status 429) — rate limit FIRES FIRST
|
|
149
|
+
* (auto-retried by default — invariant #18; per-IP rate-limit
|
|
150
|
+
* key `v1-compliance-check:${ip}`). A flooded IP gets 429 even
|
|
151
|
+
* for unauthenticated or malformed requests.
|
|
152
|
+
* - `AttestryAPIError` (status 401) — no API key OR invalid key
|
|
153
|
+
* (the `requireApiKey` branch). Fires AFTER rate-limit but
|
|
154
|
+
* BEFORE input validation.
|
|
155
|
+
* - `AttestryAPIError` (status 403) — authenticated key has
|
|
156
|
+
* NEITHER `READ_SYSTEMS` nor `READ_ASSESSMENTS` (the
|
|
157
|
+
* permission-check branch); OR cross-org orgName ("Access
|
|
158
|
+
* denied"). Distinguish via the response body's `error`
|
|
159
|
+
* message.
|
|
160
|
+
* - `AttestryAPIError` (status 400) — invalid systemId UUID
|
|
161
|
+
* format (kernel's `isValidUuid` rejection). Fires AFTER auth.
|
|
162
|
+
* The SDK does NOT pre-validate UUID format (D2: kernel is the
|
|
163
|
+
* authority). The "neither systemId nor orgName" 400 is
|
|
164
|
+
* UNREACHABLE through the SDK — pre-rejected as `TypeError`.
|
|
165
|
+
* - `AttestryAPIError` (status 404) — systemId not found OR
|
|
166
|
+
* systemId belongs to a different org (kernel collapses
|
|
167
|
+
* cross-org systemId to 404); OR orgName not found.
|
|
168
|
+
* - `AttestryAPIError` (status 500) — internal kernel error
|
|
169
|
+
* (scrubbed message via `internalErrorResponse`).
|
|
170
|
+
* - `AttestryError` ("request aborted by caller") — caller-supplied
|
|
171
|
+
* `options.signal` fired (pre-aborted or mid-flight).
|
|
172
|
+
* - `AttestryError` (P2 hardening) — kernel response failed
|
|
173
|
+
* SDK-side shape validation (not an object, missing `systems`
|
|
174
|
+
* array, missing `checkedAt` string).
|
|
175
|
+
* - `AttestryAPIError` (P3 hardening) — kernel response had a
|
|
176
|
+
* wrong Content-Type (transport-level guard before body parsing).
|
|
177
|
+
* - `TypeError` (synchronous, no fetch issued) — input failed
|
|
178
|
+
* SDK-side validation (null / array / non-object input,
|
|
179
|
+
* neither systemId nor orgName provided, both provided, empty
|
|
180
|
+
* string, non-string, lone surrogates).
|
|
181
|
+
*
|
|
182
|
+
* **Notably ABSENT**:
|
|
183
|
+
* - **No 422** — no Zod schema; no closed enums in input.
|
|
184
|
+
* - **No 413** — no body size limit (no body — GET).
|
|
185
|
+
* - **No 402** — read-only, doesn't count against decisionsPerMonth quota.
|
|
186
|
+
*
|
|
187
|
+
* **SDK-side validation** (synchronous `TypeError`, no fetch
|
|
188
|
+
* issued):
|
|
189
|
+
* - `input` itself: required; must be a non-null, non-array
|
|
190
|
+
* object.
|
|
191
|
+
* - `input.systemId` XOR `input.orgName`: exactly one must be
|
|
192
|
+
* provided. Both = `TypeError` (stricter than kernel — D3).
|
|
193
|
+
* Neither = `TypeError`.
|
|
194
|
+
* - `input.systemId` (when provided): non-empty string.
|
|
195
|
+
* Lone-surrogate guard via `assertEncodableQueryString`
|
|
196
|
+
* (carry-forward invariant #32). UUID format NOT pre-validated
|
|
197
|
+
* (D2 — kernel is the authority).
|
|
198
|
+
* - `input.orgName` (when provided): non-empty string.
|
|
199
|
+
* Lone-surrogate guard.
|
|
200
|
+
*
|
|
201
|
+
* **Response-shape validation** (P2 hardening):
|
|
202
|
+
* - Rejects with `AttestryError` if the kernel response isn't a
|
|
203
|
+
* non-null, non-array object.
|
|
204
|
+
* - Rejects if `response.systems` isn't an array.
|
|
205
|
+
* - Rejects if `response.checkedAt` isn't a string.
|
|
206
|
+
* - Per-row shape (the 7-field `ComplianceCheckResult`) is
|
|
207
|
+
* faithful-courier — NOT validated (P4 candidate).
|
|
208
|
+
*
|
|
209
|
+
* **Transport-shape validation** (P3 hardening):
|
|
210
|
+
* - Rejects with `AttestryAPIError` if the kernel responds with
|
|
211
|
+
* a non-`application/json` Content-Type — protects against
|
|
212
|
+
* proxy-injected HTML 200 pages parsing into junk consumer
|
|
213
|
+
* state.
|
|
214
|
+
*
|
|
215
|
+
* @example Compliance check by system UUID
|
|
216
|
+
* ```ts
|
|
217
|
+
* const result = await client.complianceCheck.check({
|
|
218
|
+
* systemId: "11111111-1111-1111-1111-111111111111",
|
|
219
|
+
* });
|
|
220
|
+
* for (const system of result.systems) {
|
|
221
|
+
* console.log(system.systemName, system.compliant, system.score);
|
|
222
|
+
* }
|
|
223
|
+
* ```
|
|
224
|
+
*
|
|
225
|
+
* @example Compliance check by org name (capped at 100 systems)
|
|
226
|
+
* ```ts
|
|
227
|
+
* const result = await client.complianceCheck.check({
|
|
228
|
+
* orgName: "Acme Corp",
|
|
229
|
+
* });
|
|
230
|
+
* console.log(`${result.systems.length} systems checked at ${result.checkedAt}`);
|
|
231
|
+
* ```
|
|
232
|
+
*/
|
|
233
|
+
check(input, options) {
|
|
234
|
+
// Top-level shape — input is REQUIRED (unlike auditLog.export /
|
|
235
|
+
// regulatoryChanges.list which accept undefined). typeof null ===
|
|
236
|
+
// "object" and typeof [] === "object", so guard both explicitly.
|
|
237
|
+
if (input === null ||
|
|
238
|
+
typeof input !== "object" ||
|
|
239
|
+
Array.isArray(input)) {
|
|
240
|
+
throw new TypeError("complianceCheck.check: `input` must be a non-null object with `systemId` or `orgName`");
|
|
241
|
+
}
|
|
242
|
+
// XOR check — snapshot each field's value EXACTLY ONCE up front,
|
|
243
|
+
// then operate only on the locals downstream. Three motivations:
|
|
244
|
+
// 1. **Prototype-pollution defense (hostile round H4)**:
|
|
245
|
+
// `Object.prototype.systemId = "evil-uuid"` (set somewhere
|
|
246
|
+
// else in the consumer's process) DOES NOT trick the SDK
|
|
247
|
+
// into thinking systemId was provided when the user passed
|
|
248
|
+
// `{orgName: "Acme"}`. `Object.hasOwn` only checks own
|
|
249
|
+
// properties (ES2022, Node 16.9+ — well below the SDK's
|
|
250
|
+
// Node 18 floor). Use the module-load snapshot
|
|
251
|
+
// (`objectHasOwn`) so a late-loading dep that overrides the
|
|
252
|
+
// global doesn't defeat the defense. Hostile-review LOW #2.
|
|
253
|
+
// 2. **TOCTOU defense (hostile-review LOW #1)**: a Proxy or
|
|
254
|
+
// getter-defining input could yield DIFFERENT values across
|
|
255
|
+
// multiple reads of the same field — the SDK would validate
|
|
256
|
+
// one value and send another to the wire. Snapshotting once
|
|
257
|
+
// collapses validate-then-send to a single read per field;
|
|
258
|
+
// the validated value is provably the value sent.
|
|
259
|
+
// 3. An explicit `{systemId: "uuid", orgName: undefined}` (which
|
|
260
|
+
// TS permits via the `orgName?: never` branch) is treated as
|
|
261
|
+
// systemId-only — `objectHasOwn` says "yes, orgName is an
|
|
262
|
+
// own property" but the value is `undefined`, so the
|
|
263
|
+
// undefined-check filters it out.
|
|
264
|
+
//
|
|
265
|
+
// Each input field is read exactly once via the indexer below;
|
|
266
|
+
// every subsequent operation uses the local snapshot.
|
|
267
|
+
const systemIdRaw = objectHasOwn(input, "systemId")
|
|
268
|
+
? readInputField(input, "systemId", "complianceCheck.check")
|
|
269
|
+
: undefined;
|
|
270
|
+
const orgNameRaw = objectHasOwn(input, "orgName")
|
|
271
|
+
? readInputField(input, "orgName", "complianceCheck.check")
|
|
272
|
+
: undefined;
|
|
273
|
+
const hasSystemId = systemIdRaw !== undefined;
|
|
274
|
+
const hasOrgName = orgNameRaw !== undefined;
|
|
275
|
+
if (!hasSystemId && !hasOrgName) {
|
|
276
|
+
throw new TypeError("complianceCheck.check: must provide exactly one of `systemId` or `orgName`");
|
|
277
|
+
}
|
|
278
|
+
if (hasSystemId && hasOrgName) {
|
|
279
|
+
// **SDK is STRICTER than the kernel**. The kernel silently
|
|
280
|
+
// picks systemId when both are provided (route.ts:80-87).
|
|
281
|
+
// The SDK rejects to prevent shadow-of-orgName bugs that
|
|
282
|
+
// could otherwise pass kernel-side and produce confusing
|
|
283
|
+
// results (consumer expects orgName to take effect; gets
|
|
284
|
+
// systemId-shaped response). D3 / invariant candidate #46.
|
|
285
|
+
throw new TypeError("complianceCheck.check: provide either `systemId` or `orgName`, not both " +
|
|
286
|
+
"(kernel silently ignores `orgName` when both are present; SDK rejects " +
|
|
287
|
+
"synchronously to prevent shadow-of-orgName bugs)");
|
|
288
|
+
}
|
|
289
|
+
let validatedSystemId;
|
|
290
|
+
let validatedOrgName;
|
|
291
|
+
if (hasSystemId) {
|
|
292
|
+
if (typeof systemIdRaw !== "string" || systemIdRaw.length === 0) {
|
|
293
|
+
throw new TypeError("complianceCheck.check: `systemId` must be a non-empty string when provided");
|
|
294
|
+
}
|
|
295
|
+
// UUID format NOT pre-validated (D2 — kernel's `isValidUuid`
|
|
296
|
+
// is the authority). Lone-surrogate guard (#32) catches the
|
|
297
|
+
// URIError defect class.
|
|
298
|
+
assertEncodableQueryString(systemIdRaw, "systemId", "complianceCheck.check");
|
|
299
|
+
validatedSystemId = systemIdRaw;
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
if (typeof orgNameRaw !== "string" || orgNameRaw.length === 0) {
|
|
303
|
+
throw new TypeError("complianceCheck.check: `orgName` must be a non-empty string when provided");
|
|
304
|
+
}
|
|
305
|
+
assertEncodableQueryString(orgNameRaw, "orgName", "complianceCheck.check");
|
|
306
|
+
validatedOrgName = orgNameRaw;
|
|
307
|
+
}
|
|
308
|
+
return this.client
|
|
309
|
+
._request({
|
|
310
|
+
method: "GET",
|
|
311
|
+
path: "/api/v1/compliance-check",
|
|
312
|
+
query: {
|
|
313
|
+
systemId: validatedSystemId,
|
|
314
|
+
orgName: validatedOrgName,
|
|
315
|
+
},
|
|
316
|
+
options,
|
|
317
|
+
})
|
|
318
|
+
.then((result) => {
|
|
319
|
+
// P2 hardening: validate the kernel returned an object with
|
|
320
|
+
// the expected top-level shape. The route emits
|
|
321
|
+
// `successResponse({systems, checkedAt})` where systems is
|
|
322
|
+
// always an array (initialized as `[]` and populated via
|
|
323
|
+
// `.push`) and checkedAt is always a string
|
|
324
|
+
// (`new Date().toISOString()`). A kernel-side regression that
|
|
325
|
+
// breaks any of these invariants would let a malformed shape
|
|
326
|
+
// reach consumers, who would crash on `result.systems.length`
|
|
327
|
+
// or similar with a confusing TypeError. Catch at the SDK
|
|
328
|
+
// boundary with a clear AttestryError.
|
|
329
|
+
if (result === null ||
|
|
330
|
+
typeof result !== "object" ||
|
|
331
|
+
Array.isArray(result)) {
|
|
332
|
+
throw new AttestryError(`complianceCheck.check: expected an object response from the kernel (got ${describeType(result)})`);
|
|
333
|
+
}
|
|
334
|
+
const obj = result;
|
|
335
|
+
if (!Array.isArray(obj.systems)) {
|
|
336
|
+
throw new AttestryError(`complianceCheck.check: expected response.systems to be an array (got ${describeType(obj.systems)})`);
|
|
337
|
+
}
|
|
338
|
+
if (typeof obj.checkedAt !== "string") {
|
|
339
|
+
throw new AttestryError(`complianceCheck.check: expected response.checkedAt to be a string (got ${describeType(obj.checkedAt)})`);
|
|
340
|
+
}
|
|
341
|
+
return result;
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Synchronously verify a query-string value is encodable via
|
|
347
|
+
* `encodeURIComponent`. Mirrors the helper in `decisions.ts`,
|
|
348
|
+
* `audit-log.ts`, and `regulatory-changes.ts` (carry-forward invariant
|
|
349
|
+
* #32 — URIError defect-class is uniformly handled).
|
|
350
|
+
*
|
|
351
|
+
* Duplicated rather than shared because cross-resource imports between
|
|
352
|
+
* leaf-resource modules would create graph-cycle hazards. A future
|
|
353
|
+
* SDK refactor may extract validation helpers to a shared module
|
|
354
|
+
* (e.g., `src/validate.ts`) when a fifth caller shows up; for now the
|
|
355
|
+
* duplication is intentional and documented.
|
|
356
|
+
*/
|
|
357
|
+
function assertEncodableQueryString(value, fieldName, methodName) {
|
|
358
|
+
try {
|
|
359
|
+
encodeURIComponent(value);
|
|
360
|
+
}
|
|
361
|
+
catch (err) {
|
|
362
|
+
throw new TypeError(`${methodName}: \`${fieldName}\` contains invalid UTF-16 sequences (${
|
|
363
|
+
// encodeURIComponent always throws URIError (an Error
|
|
364
|
+
// subclass), so the String(err) branch is unreachable.
|
|
365
|
+
// Defense-in-depth marker for the v8 coverage tool.
|
|
366
|
+
/* v8 ignore next */
|
|
367
|
+
err instanceof Error ? err.message : String(err)})`, { cause: err });
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Human-readable type description for response-shape error messages.
|
|
372
|
+
* Distinguishes `null` and `array` from generic `object`.
|
|
373
|
+
*
|
|
374
|
+
* Duplicated in `decisions.ts`, `incidents.ts`, and
|
|
375
|
+
* `regulatory-changes.ts` per project pattern (small helper,
|
|
376
|
+
* leaf-resource modules, no shared module yet).
|
|
377
|
+
*
|
|
378
|
+
* In complianceCheck.check, `describeType` is invoked from THREE
|
|
379
|
+
* call sites:
|
|
380
|
+
* 1. Top-level "expected an object" error — value is null OR a
|
|
381
|
+
* non-object scalar OR an array (the negation of "non-null
|
|
382
|
+
* non-array object"). All three branches are reachable here.
|
|
383
|
+
* 2. "expected response.systems to be an array" — value is the
|
|
384
|
+
* `systems` field, which can be anything but `Array`. All
|
|
385
|
+
* branches reachable.
|
|
386
|
+
* 3. "expected response.checkedAt to be a string" — value is the
|
|
387
|
+
* `checkedAt` field, which can be anything but `string`. All
|
|
388
|
+
* branches reachable.
|
|
389
|
+
*
|
|
390
|
+
* Unlike regulatoryChanges.list (which only invokes describeType
|
|
391
|
+
* from the "not an array" path, making the array branch
|
|
392
|
+
* structurally unreachable in that call site), every branch here
|
|
393
|
+
* IS reachable. No v8-ignore-next marker needed on any branch.
|
|
394
|
+
*/
|
|
395
|
+
function describeType(value) {
|
|
396
|
+
if (value === null)
|
|
397
|
+
return "null";
|
|
398
|
+
if (Array.isArray(value))
|
|
399
|
+
return "array";
|
|
400
|
+
return typeof value;
|
|
401
|
+
}
|
|
402
|
+
//# sourceMappingURL=compliance-check.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compliance-check.js","sourceRoot":"","sources":["../../src/resources/compliance-check.ts"],"names":[],"mappings":"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"}
|