qati-sdk 1.0.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 +78 -0
- package/README.md +564 -0
- package/dist/index.cjs +1021 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +2147 -0
- package/dist/index.d.ts +2147 -0
- package/dist/index.js +996 -0
- package/dist/index.js.map +1 -0
- package/package.json +54 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,996 @@
|
|
|
1
|
+
import { config } from 'dotenv';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import axios, { AxiosError } from 'axios';
|
|
4
|
+
|
|
5
|
+
// src/config.ts
|
|
6
|
+
|
|
7
|
+
// src/shared/common/exceptions.ts
|
|
8
|
+
var QatiSDKError = class extends Error {
|
|
9
|
+
constructor(message, options) {
|
|
10
|
+
super(message, options);
|
|
11
|
+
this.name = "QatiSDKError";
|
|
12
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
var QatiAPIError = class extends QatiSDKError {
|
|
16
|
+
detail;
|
|
17
|
+
constructor(detail) {
|
|
18
|
+
super(
|
|
19
|
+
`HTTP ${detail.status_code}: ${detail.message} (request_id=${detail.request_id})`
|
|
20
|
+
);
|
|
21
|
+
this.name = "QatiAPIError";
|
|
22
|
+
this.detail = detail;
|
|
23
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* @returns Same as `detail.status_code`.
|
|
27
|
+
*/
|
|
28
|
+
get status_code() {
|
|
29
|
+
return this.detail.status_code;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* @returns Same as `detail.request_id`.
|
|
33
|
+
*/
|
|
34
|
+
get request_id() {
|
|
35
|
+
return this.detail.request_id;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
var QatiAuthError = class extends QatiAPIError {
|
|
39
|
+
constructor(detail) {
|
|
40
|
+
super(detail);
|
|
41
|
+
this.name = "QatiAuthError";
|
|
42
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
var QatiNotFoundError = class extends QatiAPIError {
|
|
46
|
+
constructor(detail) {
|
|
47
|
+
super(detail);
|
|
48
|
+
this.name = "QatiNotFoundError";
|
|
49
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
var QatiRateLimitError = class extends QatiAPIError {
|
|
53
|
+
constructor(detail) {
|
|
54
|
+
super(detail);
|
|
55
|
+
this.name = "QatiRateLimitError";
|
|
56
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
var QatiServerError = class extends QatiAPIError {
|
|
60
|
+
constructor(detail) {
|
|
61
|
+
super(detail);
|
|
62
|
+
this.name = "QatiServerError";
|
|
63
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
var QatiConfigError = class extends QatiSDKError {
|
|
67
|
+
constructor(message, options) {
|
|
68
|
+
super(message, options);
|
|
69
|
+
this.name = "QatiConfigError";
|
|
70
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// src/config.ts
|
|
75
|
+
var EV_VARIABLES_NAMES = [
|
|
76
|
+
"QATI_TENANT_API_KEY",
|
|
77
|
+
"QATI_QUERY_API_BASE_URL",
|
|
78
|
+
"QATI_INGESTION_API_BASE_URL",
|
|
79
|
+
"QATI_TIMEOUT",
|
|
80
|
+
"QATI_MAX_RETRIES",
|
|
81
|
+
"QATI_INGESTION_BATCH_SIZE",
|
|
82
|
+
"QATI_INGESTION_FLUSH_INTERVAL_SECONDS",
|
|
83
|
+
"QATI_RETRY_BACKOFF_INITIAL_SECONDS",
|
|
84
|
+
"QATI_RETRY_BACKOFF_MAX_SECONDS",
|
|
85
|
+
"QATI_RETRY_JITTER_FRACTION",
|
|
86
|
+
"QATI_MAX_RETRIES"
|
|
87
|
+
];
|
|
88
|
+
var API_REGISTRY = {
|
|
89
|
+
query_api: "queryApiBaseUrl",
|
|
90
|
+
ingestion_api: "ingestionApiBaseUrl"
|
|
91
|
+
};
|
|
92
|
+
var QatiConfigBase = z.object({
|
|
93
|
+
tenantApiKey: z.string().min(1),
|
|
94
|
+
queryApiBaseUrl: z.string().url(),
|
|
95
|
+
ingestionApiBaseUrl: z.string().url(),
|
|
96
|
+
timeout: z.number().min(1).optional().default(30),
|
|
97
|
+
ingestionBatchSize: z.number().int().min(1).optional().default(100),
|
|
98
|
+
ingestionFlushIntervalSeconds: z.number().positive().optional().default(5),
|
|
99
|
+
// Retry fields
|
|
100
|
+
retryBackoffInitialSeconds: z.number().nonnegative().optional().default(1),
|
|
101
|
+
retryBackoffMaxSeconds: z.number().nonnegative().optional().default(30),
|
|
102
|
+
retryJitterFraction: z.number().min(0).max(1).optional().default(0.1),
|
|
103
|
+
maxRetries: z.number().int().min(1).max(10).optional().default(3)
|
|
104
|
+
});
|
|
105
|
+
var stripUndefined = (obj) => {
|
|
106
|
+
return Object.fromEntries(
|
|
107
|
+
Object.entries(obj).filter(([_, v]) => v !== void 0)
|
|
108
|
+
);
|
|
109
|
+
};
|
|
110
|
+
var readEnvOverrides = () => {
|
|
111
|
+
const e = process.env;
|
|
112
|
+
const sanitizedEnvs = EV_VARIABLES_NAMES.reduce(
|
|
113
|
+
(acc, ev) => {
|
|
114
|
+
acc[ev] = e[ev];
|
|
115
|
+
return acc;
|
|
116
|
+
},
|
|
117
|
+
{}
|
|
118
|
+
);
|
|
119
|
+
return sanitizedEnvs;
|
|
120
|
+
};
|
|
121
|
+
var resolveQatiConfig = (input) => {
|
|
122
|
+
config({ path: ".env" });
|
|
123
|
+
const merged = {
|
|
124
|
+
...readEnvOverrides(),
|
|
125
|
+
...stripUndefined(input ?? {})
|
|
126
|
+
};
|
|
127
|
+
const parsed = QatiConfigBase.safeParse(merged);
|
|
128
|
+
if (!parsed.success) {
|
|
129
|
+
const msg = parsed.error.flatten().fieldErrors;
|
|
130
|
+
throw new QatiConfigError(
|
|
131
|
+
`Invalid QATI SDK configuration: ${JSON.stringify(msg)}`
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
return parsed.data;
|
|
135
|
+
};
|
|
136
|
+
var parseQatiConfig = (input) => {
|
|
137
|
+
const parsed = QatiConfigBase.safeParse(input);
|
|
138
|
+
if (!parsed.success) {
|
|
139
|
+
throw new QatiConfigError(
|
|
140
|
+
`Invalid QATI SDK configuration: ${JSON.stringify(parsed.error.flatten().fieldErrors)}`
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
return parsed.data;
|
|
144
|
+
};
|
|
145
|
+
var baseUrlFor = (config, apiName) => {
|
|
146
|
+
if (!(apiName in API_REGISTRY))
|
|
147
|
+
throw new QatiConfigError(
|
|
148
|
+
`Unknown API "${apiName}". Registered APIs: ${Object.keys(API_REGISTRY).map((k) => `"${k}"`).join(", ")}.`
|
|
149
|
+
);
|
|
150
|
+
const field = API_REGISTRY[apiName];
|
|
151
|
+
const url = config[field];
|
|
152
|
+
if (typeof url !== "string")
|
|
153
|
+
throw new QatiConfigError(`Missing base URL field for API "${apiName}"`);
|
|
154
|
+
return url.replace(/\/$/, "");
|
|
155
|
+
};
|
|
156
|
+
var isRetryableStatusCode = (statusCode) => statusCode === 429 || statusCode >= 500;
|
|
157
|
+
var defaultHeaders = (config) => ({
|
|
158
|
+
"x-tenant-api-key": config.tenantApiKey,
|
|
159
|
+
"Content-Type": "application/json"
|
|
160
|
+
});
|
|
161
|
+
var resolveRequestHeaders = (config, headers = {}, extraHeaders = {}) => {
|
|
162
|
+
return { ...headers, ...defaultHeaders(config), ...extraHeaders };
|
|
163
|
+
};
|
|
164
|
+
var computeBackoffMs = (attemptNumber, config) => {
|
|
165
|
+
const {
|
|
166
|
+
retryBackoffInitialSeconds,
|
|
167
|
+
retryBackoffMaxSeconds,
|
|
168
|
+
retryJitterFraction
|
|
169
|
+
} = config;
|
|
170
|
+
const expBase = 2;
|
|
171
|
+
const rawDelay = retryBackoffInitialSeconds * expBase ** (attemptNumber - 1);
|
|
172
|
+
const base = Math.min(rawDelay, retryBackoffMaxSeconds);
|
|
173
|
+
const jitterFactor = 1 + (Math.random() * 2 * retryJitterFraction - retryJitterFraction);
|
|
174
|
+
const delayMs = Math.max(
|
|
175
|
+
0,
|
|
176
|
+
Math.min(base * jitterFactor, retryBackoffMaxSeconds)
|
|
177
|
+
);
|
|
178
|
+
return delayMs * 1e3;
|
|
179
|
+
};
|
|
180
|
+
var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
181
|
+
var isRetryableAxiosFailure = (err) => {
|
|
182
|
+
if (!axios.isAxiosError(err)) return false;
|
|
183
|
+
if (!err.response) return true;
|
|
184
|
+
return isRetryableStatusCode(err.response.status);
|
|
185
|
+
};
|
|
186
|
+
var validateInjectedApiNames = (instances) => {
|
|
187
|
+
if (!instances) return;
|
|
188
|
+
const unknownApiNames = Object.keys(instances).filter(
|
|
189
|
+
(name) => !(name in API_REGISTRY)
|
|
190
|
+
);
|
|
191
|
+
if (unknownApiNames?.length) {
|
|
192
|
+
const badApiNames = unknownApiNames.map((name) => `"${name}"`).join(", ");
|
|
193
|
+
throw new QatiConfigError(`Unknown API name(s): ${badApiNames}`);
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
var STATUS_ERROR_MAP = {
|
|
197
|
+
401: QatiAuthError,
|
|
198
|
+
403: QatiAuthError,
|
|
199
|
+
404: QatiNotFoundError,
|
|
200
|
+
429: QatiRateLimitError,
|
|
201
|
+
500: QatiServerError
|
|
202
|
+
};
|
|
203
|
+
var parseDetail = (response) => {
|
|
204
|
+
const { headers, status, statusText, data } = response;
|
|
205
|
+
const requestId = headers["x-request-id"] ?? headers["X-Request-Id"];
|
|
206
|
+
let message = statusText ?? "";
|
|
207
|
+
try {
|
|
208
|
+
const body = data;
|
|
209
|
+
if (typeof body === "object") {
|
|
210
|
+
const msg = body.detail || body.message;
|
|
211
|
+
message = typeof msg === "string" ? msg : JSON.stringify(msg);
|
|
212
|
+
}
|
|
213
|
+
} catch {
|
|
214
|
+
message = statusText;
|
|
215
|
+
}
|
|
216
|
+
return {
|
|
217
|
+
status_code: status,
|
|
218
|
+
message,
|
|
219
|
+
request_id: requestId
|
|
220
|
+
};
|
|
221
|
+
};
|
|
222
|
+
var qatiApiErrorFromRequestFailure = (err) => {
|
|
223
|
+
if (axios.isAxiosError(err)) {
|
|
224
|
+
const ax = err;
|
|
225
|
+
if (ax.response) {
|
|
226
|
+
const resp = ax.response;
|
|
227
|
+
const ErrorCls = STATUS_ERROR_MAP[resp.status] ?? (resp.status >= 500 ? QatiServerError : QatiAPIError);
|
|
228
|
+
return new ErrorCls(parseDetail(resp));
|
|
229
|
+
}
|
|
230
|
+
return new QatiAPIError({
|
|
231
|
+
status_code: 0,
|
|
232
|
+
message: ax.message || String(ax.code ?? "Axios request error"),
|
|
233
|
+
request_id: null
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
throw new TypeError(`Expected Axios error, got ${typeof err}`);
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
// src/http/http-client.ts
|
|
240
|
+
var HttpClient = class {
|
|
241
|
+
_config;
|
|
242
|
+
_httpClients;
|
|
243
|
+
constructor(config, axiosInstances = {}) {
|
|
244
|
+
validateInjectedApiNames(axiosInstances);
|
|
245
|
+
this._config = config;
|
|
246
|
+
this._httpClients = /* @__PURE__ */ new Map();
|
|
247
|
+
for (const apiName of Object.keys(API_REGISTRY)) {
|
|
248
|
+
const existingApiInstance = axiosInstances[apiName];
|
|
249
|
+
if (existingApiInstance) {
|
|
250
|
+
this._httpClients.set(apiName, existingApiInstance);
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
const apiInstance = this.createClient(apiName);
|
|
254
|
+
this._httpClients.set(apiName, apiInstance);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
createClient(apiName) {
|
|
258
|
+
const baseURL = baseUrlFor(this._config, apiName);
|
|
259
|
+
return axios.create({
|
|
260
|
+
baseURL,
|
|
261
|
+
timeout: this._config.timeout * 1e3,
|
|
262
|
+
headers: defaultHeaders(this._config)
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* @returns The same configuration used to build base URLs, timeouts, and default headers.
|
|
267
|
+
*/
|
|
268
|
+
get config() {
|
|
269
|
+
return this._config;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Returns the underlying `axios` instance for a logical API.
|
|
273
|
+
*
|
|
274
|
+
* @param apiName - `query_api` or `ingestion_api`.
|
|
275
|
+
* @returns Configured `AxiosInstance` for that API.
|
|
276
|
+
* @throws {@link QatiConfigError} If the internal client map has no entry for `apiName`.
|
|
277
|
+
*/
|
|
278
|
+
getClient(apiName) {
|
|
279
|
+
const instance = this._httpClients.get(apiName);
|
|
280
|
+
if (!instance)
|
|
281
|
+
throw new QatiConfigError(`Unknown HTTP client for API "${apiName}"`);
|
|
282
|
+
return instance;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Performs one HTTP request and returns the **unwrapped** `data` field from the QATI `{ data: T }` envelope.
|
|
286
|
+
*
|
|
287
|
+
* @typeParam T - Response payload type inside the API envelope.
|
|
288
|
+
* @param method - HTTP verb (e.g. `GET`, `POST`).
|
|
289
|
+
* @param url - Path relative to the API base URL (may include query string; prefer `options.params` for queries).
|
|
290
|
+
* @param options - `api_name` selects which base URL and client to use. Set `retry: true` to retry transient failures.
|
|
291
|
+
* @returns Parsed `T` from `response.data.data` (not the full `ApiResponse` wrapper).
|
|
292
|
+
* @throws `QatiAPIError` or subclasses (`QatiAuthError`, `QatiNotFoundError`, …) on HTTP failures; on transport exhaustion when `retry` is `true`, after optional dead-letter callback.
|
|
293
|
+
*
|
|
294
|
+
* When `options.retry` is `true`, retries `429`, `5xx`, and transport errors up to `maxRetries` from config;
|
|
295
|
+
* other `4xx` are not retried. When `retry` is `false` or omitted, a single attempt is made.
|
|
296
|
+
*/
|
|
297
|
+
async request(method, url, options) {
|
|
298
|
+
const instance = this.getClient(options.api_name);
|
|
299
|
+
const resolvedHeaders = resolveRequestHeaders(
|
|
300
|
+
this._config,
|
|
301
|
+
options.headers,
|
|
302
|
+
options.extraHeaders
|
|
303
|
+
);
|
|
304
|
+
const req = {
|
|
305
|
+
method,
|
|
306
|
+
url,
|
|
307
|
+
params: options.params ?? void 0,
|
|
308
|
+
headers: resolvedHeaders,
|
|
309
|
+
data: options.data
|
|
310
|
+
};
|
|
311
|
+
if (!options.retry) {
|
|
312
|
+
const { data } = await instance.request(req);
|
|
313
|
+
return data.data;
|
|
314
|
+
}
|
|
315
|
+
const response = await this.retryRequest(
|
|
316
|
+
() => instance.request(req),
|
|
317
|
+
options
|
|
318
|
+
);
|
|
319
|
+
return response.data.data;
|
|
320
|
+
}
|
|
321
|
+
async retryRequest(requestFn, options) {
|
|
322
|
+
for (let attempt = 0; attempt <= this._config.maxRetries; attempt++) {
|
|
323
|
+
try {
|
|
324
|
+
const response = await requestFn();
|
|
325
|
+
if (!isRetryableStatusCode(response.status)) return response;
|
|
326
|
+
if (attempt >= this._config.maxRetries) {
|
|
327
|
+
throw new AxiosError(
|
|
328
|
+
`Retryable HTTP ${response.status}`,
|
|
329
|
+
String(response.status),
|
|
330
|
+
response.config,
|
|
331
|
+
void 0,
|
|
332
|
+
response
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
await sleep(computeBackoffMs(attempt + 1, this._config));
|
|
336
|
+
} catch (err) {
|
|
337
|
+
const shouldRetry = isRetryableAxiosFailure(err) && attempt < this._config.maxRetries;
|
|
338
|
+
if (shouldRetry) {
|
|
339
|
+
await sleep(computeBackoffMs(attempt + 1, this._config));
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
const error = qatiApiErrorFromRequestFailure(err);
|
|
343
|
+
options.deadLetterCallback?.(options.data, error);
|
|
344
|
+
throw error;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
throw new QatiConfigError("Unreachable retry exhaustion");
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
// src/shared/utils/date.utils.ts
|
|
352
|
+
function dateToIso8601(value) {
|
|
353
|
+
if (!value) return;
|
|
354
|
+
if (value instanceof Date) return value.toISOString();
|
|
355
|
+
return value;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// src/resources/advisory-resource.ts
|
|
359
|
+
var AdvisoryResource = class {
|
|
360
|
+
constructor(_http) {
|
|
361
|
+
this._http = _http;
|
|
362
|
+
}
|
|
363
|
+
_http;
|
|
364
|
+
apiName = "query_api";
|
|
365
|
+
/**
|
|
366
|
+
* Lists persisted advisories for the tenant (`GET /v1/advisories`).
|
|
367
|
+
*
|
|
368
|
+
* @param entityKey - Scoped entity key: uppercase type, colon, opaque id (e.g. `USER:20`). Must match server expectations for entity keys.
|
|
369
|
+
* @param advisoryType - Filter by advisory `type` (see {@link AdvisoryType}).
|
|
370
|
+
* @param severity - Filter by `severity` tier (see {@link AdvisorySeverity}).
|
|
371
|
+
* @param options - Optional time range and pagination ({@link ListAdvisoriesOptions}).
|
|
372
|
+
* @returns: {@link AdvisoryListResponse} including `total_count`, `limit`, and `offset` echo from the API.
|
|
373
|
+
* ```
|
|
374
|
+
*/
|
|
375
|
+
async listAdvisories(entityKey, advisoryType, severity, options) {
|
|
376
|
+
return this._http.request("GET", "/v1/advisories", {
|
|
377
|
+
api_name: this.apiName,
|
|
378
|
+
params: {
|
|
379
|
+
severity,
|
|
380
|
+
entity_key: entityKey,
|
|
381
|
+
advisory_type: advisoryType,
|
|
382
|
+
since: options?.since ? dateToIso8601(options.since) : void 0,
|
|
383
|
+
until: options?.until ? dateToIso8601(options.until) : void 0,
|
|
384
|
+
limit: options?.limit,
|
|
385
|
+
offset: options?.offset
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
// src/shared/utils/mutext.utils.ts
|
|
392
|
+
var createMutex = () => {
|
|
393
|
+
let tail = Promise.resolve();
|
|
394
|
+
return async (fn) => {
|
|
395
|
+
const result = tail.then(() => fn());
|
|
396
|
+
tail = result.then(
|
|
397
|
+
() => {
|
|
398
|
+
},
|
|
399
|
+
() => {
|
|
400
|
+
}
|
|
401
|
+
);
|
|
402
|
+
return result;
|
|
403
|
+
};
|
|
404
|
+
};
|
|
405
|
+
var BatchBufferOptionsSchema = z.object({
|
|
406
|
+
batchSize: z.number().int().min(1).default(100),
|
|
407
|
+
flushIntervalSeconds: z.number().min(0).default(5)
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
// src/shared/buffering/auto-flush-batch-buffer.ts
|
|
411
|
+
var AutoFlushBatchBuffer = class {
|
|
412
|
+
/**
|
|
413
|
+
* Serializes buffer/timer mutations so enqueue, flush, and timer fire never overlap.
|
|
414
|
+
*/
|
|
415
|
+
runExclusive = createMutex();
|
|
416
|
+
queue = [];
|
|
417
|
+
flushBatch;
|
|
418
|
+
batchSize;
|
|
419
|
+
flushIntervalMs;
|
|
420
|
+
pendingTimer = null;
|
|
421
|
+
timerSequence = 0;
|
|
422
|
+
constructor(flushBatch, options) {
|
|
423
|
+
this.flushBatch = flushBatch;
|
|
424
|
+
const validatedOptions = BatchBufferOptionsSchema.parse(options);
|
|
425
|
+
this.batchSize = validatedOptions.batchSize;
|
|
426
|
+
this.flushIntervalMs = validatedOptions.flushIntervalSeconds * 1e3;
|
|
427
|
+
}
|
|
428
|
+
async enqueue(item) {
|
|
429
|
+
let batchToFlush = [];
|
|
430
|
+
await this.runExclusive(async () => {
|
|
431
|
+
this.queue.push(item);
|
|
432
|
+
if (this.queue.length >= this.batchSize) {
|
|
433
|
+
this.cancelTimer();
|
|
434
|
+
batchToFlush = this.getItemsToSend();
|
|
435
|
+
}
|
|
436
|
+
if (this.queue.length === 1) this.scheduleTimer();
|
|
437
|
+
});
|
|
438
|
+
if (batchToFlush.length)
|
|
439
|
+
await this.executeFlush(batchToFlush).catch(() => {
|
|
440
|
+
});
|
|
441
|
+
return "queued";
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Flush all buffered items immediately.
|
|
445
|
+
* @returns The return value of {@link flushBatch}, or `{}` if the buffer was empty.
|
|
446
|
+
*/
|
|
447
|
+
async flush() {
|
|
448
|
+
let batch = [];
|
|
449
|
+
await this.runExclusive(async () => {
|
|
450
|
+
this.cancelTimer();
|
|
451
|
+
batch = this.getItemsToSend();
|
|
452
|
+
});
|
|
453
|
+
if (!batch.length) return {};
|
|
454
|
+
return this.executeFlush(batch);
|
|
455
|
+
}
|
|
456
|
+
/**
|
|
457
|
+
* Best-effort flush for shutdown; logs and drops items on failure.
|
|
458
|
+
*/
|
|
459
|
+
async shutdown() {
|
|
460
|
+
let batch = [];
|
|
461
|
+
await this.runExclusive(async () => {
|
|
462
|
+
this.cancelTimer();
|
|
463
|
+
batch = this.getItemsToSend();
|
|
464
|
+
});
|
|
465
|
+
if (!batch.length) return;
|
|
466
|
+
await this.executeFlush(batch).catch(() => {
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* The number of items currently in the buffer.
|
|
471
|
+
*/
|
|
472
|
+
get pendingCount() {
|
|
473
|
+
return this.queue.length;
|
|
474
|
+
}
|
|
475
|
+
getItemsToSend() {
|
|
476
|
+
if (!this.queue.length) return [];
|
|
477
|
+
return this.queue.splice(0, this.queue.length);
|
|
478
|
+
}
|
|
479
|
+
cancelTimer() {
|
|
480
|
+
if (!this.pendingTimer) return;
|
|
481
|
+
clearTimeout(this.pendingTimer.handleTimer);
|
|
482
|
+
this.pendingTimer = null;
|
|
483
|
+
}
|
|
484
|
+
scheduleTimer() {
|
|
485
|
+
this.cancelTimer();
|
|
486
|
+
const seq = ++this.timerSequence;
|
|
487
|
+
const handleTimer = setTimeout(
|
|
488
|
+
() => this.timerFire(seq),
|
|
489
|
+
this.flushIntervalMs
|
|
490
|
+
);
|
|
491
|
+
this.pendingTimer = { sequence: seq, handleTimer };
|
|
492
|
+
}
|
|
493
|
+
async timerFire(seq) {
|
|
494
|
+
let batch = [];
|
|
495
|
+
await this.runExclusive(async () => {
|
|
496
|
+
if (this.pendingTimer?.sequence !== seq) return;
|
|
497
|
+
this.pendingTimer = null;
|
|
498
|
+
batch = this.getItemsToSend();
|
|
499
|
+
});
|
|
500
|
+
if (!batch.length) return;
|
|
501
|
+
await this.executeFlush(batch).catch(() => {
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
async executeFlush(batch) {
|
|
505
|
+
const result = await this.flushBatch(batch);
|
|
506
|
+
return result ?? {};
|
|
507
|
+
}
|
|
508
|
+
};
|
|
509
|
+
|
|
510
|
+
// src/resources/event-resource.ts
|
|
511
|
+
var EventResource = class {
|
|
512
|
+
_http;
|
|
513
|
+
_buffer;
|
|
514
|
+
apiName = "ingestion_api";
|
|
515
|
+
_deadLetterCallback;
|
|
516
|
+
/**
|
|
517
|
+
* @param http - Shared HTTP client (supplies config defaults and axios instance for `ingestion_api`).
|
|
518
|
+
* @param options - Optional buffer sizing overrides ({@link EventResourceOptions}).
|
|
519
|
+
*/
|
|
520
|
+
constructor(http, options = {}) {
|
|
521
|
+
this._http = http;
|
|
522
|
+
this._buffer = new AutoFlushBatchBuffer((batch) => this.postBatch(batch), {
|
|
523
|
+
batchSize: options.batchSize ?? http.config.ingestionBatchSize,
|
|
524
|
+
flushIntervalSeconds: options.flushIntervalSeconds ?? http.config.ingestionFlushIntervalSeconds
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Registers a hook invoked after retries are exhausted for a failed batch POST so you can land failures on a DLQ or metric pipeline. Must stay non-throwing — swallow errors inside your handler.
|
|
529
|
+
*/
|
|
530
|
+
onIngestionFailure(callback) {
|
|
531
|
+
this._deadLetterCallback = callback;
|
|
532
|
+
return this;
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Accepts one normalized event into the in-memory buffer and returns immediately with **`queued`** — HTTP happens on flush timers or explicit `flush`, not inline with `enqueue`.
|
|
536
|
+
*/
|
|
537
|
+
async enqueue(event) {
|
|
538
|
+
return await this._buffer.enqueue(event);
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Sends whatever is buffered **now** as one ingestion batch (or resolves immediately with an empty-shaped result when nothing was pending).
|
|
542
|
+
*/
|
|
543
|
+
async flush() {
|
|
544
|
+
return this._buffer.flush();
|
|
545
|
+
}
|
|
546
|
+
/**
|
|
547
|
+
* Best-effort final flush then tears down timers — typically reached via `Client.close()` during graceful shutdown so late telemetry is not dropped on SIGTERM.
|
|
548
|
+
*/
|
|
549
|
+
async shutdown() {
|
|
550
|
+
await this._buffer.shutdown();
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Live count of rows waiting in the in-memory buffer (not yet acknowledged by the ingestion API).
|
|
554
|
+
*/
|
|
555
|
+
get pendingCount() {
|
|
556
|
+
return this._buffer.pendingCount;
|
|
557
|
+
}
|
|
558
|
+
async postBatch(batch) {
|
|
559
|
+
return this._http.request("POST", "/v1/events:batch", {
|
|
560
|
+
api_name: this.apiName,
|
|
561
|
+
data: { events: batch },
|
|
562
|
+
retry: true,
|
|
563
|
+
deadLetterCallback: this._deadLetterCallback
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
// src/resources/explain-resource.ts
|
|
569
|
+
var ExplainResource = class {
|
|
570
|
+
constructor(_http) {
|
|
571
|
+
this._http = _http;
|
|
572
|
+
}
|
|
573
|
+
_http;
|
|
574
|
+
apiName = "query_api";
|
|
575
|
+
/**
|
|
576
|
+
* Fetches composite explain for one entity (`GET /v1/explain/{entity_key}`).
|
|
577
|
+
*
|
|
578
|
+
* @param entityKey - URL-encoded path segment: uppercase entity type, colon, id (e.g. `USER:user-123`). Must not contain raw `/` (caller passes logical key; SDK encodes for HTTP).
|
|
579
|
+
* @param atTimestamp - Optional “as of” time (`Date` or ISO-8601 string). Omitted means server default (typically “now”).
|
|
580
|
+
* @param topDominantNeighbors - Optional cap on dominant neighbor rows returned.
|
|
581
|
+
* @param topEvents - Optional cap on top contributing events returned.
|
|
582
|
+
* @returns {@link ExplainResponse} (closure score, risk tier, top events, neighbours, active advisories).
|
|
583
|
+
* @example
|
|
584
|
+
* ```ts
|
|
585
|
+
* const x = await client.explain.get('USER:alice', new Date(), 5, 10);
|
|
586
|
+
* ```
|
|
587
|
+
*/
|
|
588
|
+
async get(entityKey, atTimestamp, topDominantNeighbors, topEvents) {
|
|
589
|
+
return await this._http.request(
|
|
590
|
+
"GET",
|
|
591
|
+
`/v1/explain/${encodeURIComponent(entityKey)}`,
|
|
592
|
+
{
|
|
593
|
+
api_name: this.apiName,
|
|
594
|
+
params: {
|
|
595
|
+
at_timestamp: dateToIso8601(atTimestamp),
|
|
596
|
+
top_dominant_neighbors: topDominantNeighbors,
|
|
597
|
+
top_events: topEvents
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
// src/resources/tenant-resource.ts
|
|
605
|
+
var TenantResource = class {
|
|
606
|
+
constructor(_http) {
|
|
607
|
+
this._http = _http;
|
|
608
|
+
}
|
|
609
|
+
_http;
|
|
610
|
+
apiName = "query_api";
|
|
611
|
+
/**
|
|
612
|
+
* Confirms whether the **`x-tenant-api-key`** presented by this client is accepted by the platform and returns the opaqu
|
|
613
|
+
* e **tenant partition id** bound to that key.
|
|
614
|
+
* Use at process startup so misconfigured keys fail fast before trust-state or explain traffic.
|
|
615
|
+
*
|
|
616
|
+
* @returns: {@link VerifyCredentialsResponse}
|
|
617
|
+
*/
|
|
618
|
+
async verifyCredentials() {
|
|
619
|
+
return this._http.request(
|
|
620
|
+
"GET",
|
|
621
|
+
"/v1/tenants/verify-credentials",
|
|
622
|
+
{ api_name: this.apiName }
|
|
623
|
+
);
|
|
624
|
+
}
|
|
625
|
+
};
|
|
626
|
+
|
|
627
|
+
// src/resources/trust-state-resource.ts
|
|
628
|
+
var TrustStateResource = class {
|
|
629
|
+
constructor(_http) {
|
|
630
|
+
this._http = _http;
|
|
631
|
+
}
|
|
632
|
+
_http;
|
|
633
|
+
apiName = "query_api";
|
|
634
|
+
/**
|
|
635
|
+
* Fetches the latest trust state for one entity.
|
|
636
|
+
*
|
|
637
|
+
* @param entityType - Lowercase entity class: `user`, `device`, `account`, `model`, `session`, or `service` (see {@link EntityTypeLowerCase}).
|
|
638
|
+
* @param entityId - Id for the entity(Required)
|
|
639
|
+
* @param options - Optional contributor limits/window.
|
|
640
|
+
* @returns {@link TrustState} including `current_closure_score`, `risk_tier`, and `top_contributors`.
|
|
641
|
+
*/
|
|
642
|
+
async getTrustState(entityType, entityId, options = {}) {
|
|
643
|
+
return await this._http.request(
|
|
644
|
+
"GET",
|
|
645
|
+
`/v1/entities/${entityType}/${encodeURIComponent(entityId)}/trust-state`,
|
|
646
|
+
{
|
|
647
|
+
api_name: this.apiName,
|
|
648
|
+
params: {
|
|
649
|
+
top_contributors_limit: options.topContributorsLimit ?? 5,
|
|
650
|
+
top_contributors_window_minutes: options.topContributorsWindowMinutes
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
/**
|
|
656
|
+
* Fetches trust states for many entities in one request.
|
|
657
|
+
*
|
|
658
|
+
* @param entityType - Lowercase entity class: `user`, `device`, `account`, `model`, `session`, or `service` (see {@link EntityTypeLowerCase}).
|
|
659
|
+
* @param entityIds - Distinct ids to query. Prefer length ≤ 100 or the server may reject the request.
|
|
660
|
+
* @param options - Optional contributor limits/window.
|
|
661
|
+
* @returns Array of {@link TrustState} in server-defined order; empty array when `entityIds` is empty.
|
|
662
|
+
*/
|
|
663
|
+
async getTrustStates(entityType, entityIds, options = {}) {
|
|
664
|
+
if (!entityIds.length) return [];
|
|
665
|
+
return await this._http.request("GET", "/v1/trust-states", {
|
|
666
|
+
api_name: this.apiName,
|
|
667
|
+
params: {
|
|
668
|
+
entity_type: entityType,
|
|
669
|
+
entity_ids: entityIds.join(","),
|
|
670
|
+
top_contributors_limit: options.topContributorsLimit ?? 5,
|
|
671
|
+
top_contributors_window_minutes: options.topContributorsWindowMinutes
|
|
672
|
+
}
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
};
|
|
676
|
+
|
|
677
|
+
// src/client/client.ts
|
|
678
|
+
var Client = class extends HttpClient {
|
|
679
|
+
_tenant;
|
|
680
|
+
_trustState;
|
|
681
|
+
_advisory;
|
|
682
|
+
_explain;
|
|
683
|
+
_events;
|
|
684
|
+
/**
|
|
685
|
+
* @param config - Resolved SDK configuration (typically `session.config`).
|
|
686
|
+
* @param axiosInstances - Optional per-API `axios` instances; see {@link Session#createClient}.
|
|
687
|
+
* @param eventResourceOptions - Optional overrides for buffered ingestion (`onDeadLetter`, batch sizing).
|
|
688
|
+
*/
|
|
689
|
+
constructor(config, axiosInstances) {
|
|
690
|
+
super(config, axiosInstances);
|
|
691
|
+
this._tenant = new TenantResource(this);
|
|
692
|
+
this._trustState = new TrustStateResource(this);
|
|
693
|
+
this._advisory = new AdvisoryResource(this);
|
|
694
|
+
this._explain = new ExplainResource(this);
|
|
695
|
+
this._events = new EventResource(this);
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Tenant API surface (credentials verification).
|
|
699
|
+
*
|
|
700
|
+
* @returns {@link TenantResource} bound to this client’s Query API base URL and tenant key.
|
|
701
|
+
*/
|
|
702
|
+
get tenant() {
|
|
703
|
+
return this._tenant;
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Trust scores and contributors for entities.
|
|
707
|
+
*
|
|
708
|
+
* @returns {@link TrustStateResource} for the Query API.
|
|
709
|
+
*/
|
|
710
|
+
get trustState() {
|
|
711
|
+
return this._trustState;
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* List persisted advisories for an entity.
|
|
715
|
+
*
|
|
716
|
+
* @returns {@link AdvisoryResource} for the Query API.
|
|
717
|
+
*/
|
|
718
|
+
get advisory() {
|
|
719
|
+
return this._advisory;
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Composite explain / attribution for an entity key.
|
|
723
|
+
*
|
|
724
|
+
* @returns {@link ExplainResource} for the Query API.
|
|
725
|
+
*/
|
|
726
|
+
get explain() {
|
|
727
|
+
return this._explain;
|
|
728
|
+
}
|
|
729
|
+
/**
|
|
730
|
+
* Buffered ingestion of `RawEventRequest` payloads to the ingestion API (`POST /v1/events:batch`).
|
|
731
|
+
*
|
|
732
|
+
* @returns {@link EventResource} for the Query API.
|
|
733
|
+
*/
|
|
734
|
+
get events() {
|
|
735
|
+
return this._events;
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* Flushes the event ingestion buffer (best-effort), then shuts down the batch scheduler.
|
|
739
|
+
* Call this on process shutdown so queued events are not lost.
|
|
740
|
+
*
|
|
741
|
+
* @returns Resolves when {@link EventResource#shutdown} completes.
|
|
742
|
+
*/
|
|
743
|
+
async close() {
|
|
744
|
+
await this._events.shutdown();
|
|
745
|
+
}
|
|
746
|
+
};
|
|
747
|
+
|
|
748
|
+
// src/session.ts
|
|
749
|
+
var Session = class {
|
|
750
|
+
_config;
|
|
751
|
+
constructor(config) {
|
|
752
|
+
this._config = resolveQatiConfig(config);
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* Resolved, validated configuration.
|
|
756
|
+
*/
|
|
757
|
+
get config() {
|
|
758
|
+
return this._config;
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* Creates an API {@link Client} using this session’s resolved config.
|
|
762
|
+
*
|
|
763
|
+
* @param httpClients - Optional injected `axios` instances keyed by `query_api` / `ingestion_api` (testing, proxies, interceptors).
|
|
764
|
+
* @returns A new {@link Client} (not a singleton).
|
|
765
|
+
* @example
|
|
766
|
+
* ```typescript
|
|
767
|
+
* const session = new Session();
|
|
768
|
+
* const client = session.createClient();
|
|
769
|
+
* const explained = await client.explain.get('USER:user-123');
|
|
770
|
+
* console.log(explained.closure_score);
|
|
771
|
+
* ```
|
|
772
|
+
*/
|
|
773
|
+
createClient(httpClients) {
|
|
774
|
+
return new Client(this._config, httpClients ?? {});
|
|
775
|
+
}
|
|
776
|
+
};
|
|
777
|
+
|
|
778
|
+
// src/shared/types/signal-type.ts
|
|
779
|
+
var SIGNAL_TYPES = [
|
|
780
|
+
"TRANSACTION",
|
|
781
|
+
"AUTH",
|
|
782
|
+
"BEHAVIOR",
|
|
783
|
+
"SYSTEM_TELEMETRY",
|
|
784
|
+
"MODEL_OUTPUT",
|
|
785
|
+
"ANOMALY_FLAG",
|
|
786
|
+
"NETWORK_EVENT"
|
|
787
|
+
];
|
|
788
|
+
|
|
789
|
+
// src/shared/types/provenance-mode.ts
|
|
790
|
+
var PROVENANCE_MODES = [
|
|
791
|
+
"DETERMINISTIC",
|
|
792
|
+
"EXTERNAL_RNG",
|
|
793
|
+
"ATTESTED"
|
|
794
|
+
];
|
|
795
|
+
|
|
796
|
+
// src/v1/schemas/provenance.ts
|
|
797
|
+
var ProvenanceSchema = z.object({
|
|
798
|
+
mode: z.enum(PROVENANCE_MODES).optional(),
|
|
799
|
+
source_id: z.string().optional(),
|
|
800
|
+
epoch_counter: z.number().int().nonnegative().optional(),
|
|
801
|
+
health_summary: z.union([z.string(), z.record(z.unknown())]).optional()
|
|
802
|
+
});
|
|
803
|
+
var IntegritySchema = z.object({
|
|
804
|
+
hash: z.string().optional(),
|
|
805
|
+
signature: z.string().optional()
|
|
806
|
+
});
|
|
807
|
+
var EventPrincipalSchema = z.object({
|
|
808
|
+
user_id: z.string().min(1).optional(),
|
|
809
|
+
account_id: z.string().min(1).optional(),
|
|
810
|
+
device_id: z.string().min(1).optional(),
|
|
811
|
+
session_id: z.string().min(1).optional(),
|
|
812
|
+
model_id: z.string().min(1).optional(),
|
|
813
|
+
service_id: z.string().min(1).optional()
|
|
814
|
+
});
|
|
815
|
+
|
|
816
|
+
// src/v1/schemas/raw-event.ts
|
|
817
|
+
var BaseEventSchema = z.object({
|
|
818
|
+
tenant_id: z.string().describe("Tenant partition key (required)."),
|
|
819
|
+
signal_version: z.string().describe("Schema version (e.g. v1)."),
|
|
820
|
+
signal_type: z.enum(SIGNAL_TYPES).describe("Type of signal."),
|
|
821
|
+
signal_payload: z.record(z.any()).describe("Payload validated per signal_type."),
|
|
822
|
+
principal: EventPrincipalSchema,
|
|
823
|
+
timestamp: z.string().datetime().default((/* @__PURE__ */ new Date()).toISOString()).optional().describe("Event / provenance record time when provided by the client."),
|
|
824
|
+
confidence_hint: z.number().max(1).min(0).optional().describe("Scalar in [0,1]; how trustworthy this event is."),
|
|
825
|
+
provenance: ProvenanceSchema.optional().describe(
|
|
826
|
+
"Optional provenance (mode, source_id, epoch_counter, health_summary)."
|
|
827
|
+
),
|
|
828
|
+
integrity: IntegritySchema.optional()
|
|
829
|
+
});
|
|
830
|
+
var RawEventRequestSchema = z.object({
|
|
831
|
+
tenant_id: z.string().describe("Tenant partition key (required)."),
|
|
832
|
+
payload: z.record(z.any()).describe("JSON payload (stored as JSONB in DB).")
|
|
833
|
+
});
|
|
834
|
+
|
|
835
|
+
// src/v1/schemas/signal-payload.ts
|
|
836
|
+
var TransactionSignalPayloadSchema = z.object({
|
|
837
|
+
amount: z.number().nonnegative().default(0),
|
|
838
|
+
amount_minor: z.number().int().nonnegative().nullable().optional(),
|
|
839
|
+
amount_usd: z.number().nonnegative().nullable().optional(),
|
|
840
|
+
currency: z.string().nullable().optional(),
|
|
841
|
+
merchant_category: z.string().nullable().optional(),
|
|
842
|
+
merchant_id: z.string().nullable().optional(),
|
|
843
|
+
channel: z.string().nullable().optional(),
|
|
844
|
+
country: z.string().nullable().optional(),
|
|
845
|
+
velocity: z.number().nonnegative().nullable().optional(),
|
|
846
|
+
external_anomaly_score: z.number().nullable().optional(),
|
|
847
|
+
geo_distance: z.number().nonnegative().nullable().optional(),
|
|
848
|
+
geo_distance_km: z.number().nonnegative().nullable().optional(),
|
|
849
|
+
records_accessed: z.number().int().nonnegative().nullable().optional(),
|
|
850
|
+
baseline_records_accessed: z.number().int().nonnegative().nullable().optional(),
|
|
851
|
+
sensitivity_level: z.string().nullable().optional(),
|
|
852
|
+
export_count: z.number().int().nonnegative().nullable().optional(),
|
|
853
|
+
bulk_export: z.boolean().optional().default(false),
|
|
854
|
+
contains_phi: z.boolean().optional().default(false),
|
|
855
|
+
control_command: z.string().nullable().optional(),
|
|
856
|
+
authorized: z.boolean().nullable().optional(),
|
|
857
|
+
safety_critical: z.boolean().optional().default(false)
|
|
858
|
+
});
|
|
859
|
+
var AuthSignalPayloadSchema = z.object({
|
|
860
|
+
result: z.string().nullable().optional(),
|
|
861
|
+
auth_method: z.string().nullable().optional(),
|
|
862
|
+
mfa_used: z.boolean().nullable().optional(),
|
|
863
|
+
mfa_bypassed: z.boolean().optional().default(false),
|
|
864
|
+
failed_attempts: z.number().int().nonnegative().optional().default(0),
|
|
865
|
+
ip: z.string().nullable().optional(),
|
|
866
|
+
country: z.string().nullable().optional(),
|
|
867
|
+
user_agent: z.string().nullable().optional(),
|
|
868
|
+
unusual_location: z.boolean().optional().default(false),
|
|
869
|
+
after_hours_login: z.boolean().optional().default(false)
|
|
870
|
+
});
|
|
871
|
+
var ModelOutputSignalPayloadSchema = z.object({
|
|
872
|
+
missing_citations_rate: z.number().min(0).max(1).nullable().optional(),
|
|
873
|
+
citation_rate: z.number().min(0).max(1).nullable().optional(),
|
|
874
|
+
expected_citation_rate: z.number().min(0).max(1).nullable().optional(),
|
|
875
|
+
policy_violations: z.number().int().nonnegative().optional().default(0),
|
|
876
|
+
policy_violation_rate: z.number().min(0).max(1).nullable().optional(),
|
|
877
|
+
tool_call_inconsistency: z.number().min(0).max(1).optional().default(0),
|
|
878
|
+
tool_inconsistency_rate: z.number().min(0).max(1).nullable().optional(),
|
|
879
|
+
tool_miss_rate: z.number().min(0).max(1).nullable().optional(),
|
|
880
|
+
eval_window_n: z.number().int().min(1).nullable().optional()
|
|
881
|
+
});
|
|
882
|
+
var SystemTelemetrySignalPayloadSchema = z.object({
|
|
883
|
+
metric_name: z.string().nullable().optional(),
|
|
884
|
+
value: z.number().nullable().optional(),
|
|
885
|
+
baseline: z.number().nullable().optional(),
|
|
886
|
+
window_seconds: z.number().int().min(1).nullable().optional(),
|
|
887
|
+
error_rate: z.number().min(0).max(1).optional().default(0),
|
|
888
|
+
baseline_error_rate: z.number().min(0).max(1).nullable().optional(),
|
|
889
|
+
unusual_auth_rate: z.number().min(0).max(1).optional().default(0),
|
|
890
|
+
firmware_hash_changed: z.boolean().optional().default(false),
|
|
891
|
+
expected_hash_match: z.boolean().nullable().optional(),
|
|
892
|
+
sensor_deviation_score: z.number().min(0).max(1).nullable().optional()
|
|
893
|
+
});
|
|
894
|
+
var AnomalyFlagSignalPayloadSchema = z.object({
|
|
895
|
+
severity: z.number().min(0).max(1)
|
|
896
|
+
});
|
|
897
|
+
var BehaviorSignalPayloadSchema = z.object({
|
|
898
|
+
deviation_score: z.number().min(0).max(1)
|
|
899
|
+
});
|
|
900
|
+
var NetworkSignalPayloadSchema = z.object({
|
|
901
|
+
ip: z.string().nullable().optional(),
|
|
902
|
+
asn: z.string().nullable().optional(),
|
|
903
|
+
reputation_score: z.number().min(0).max(1).nullable().optional(),
|
|
904
|
+
threat_score: z.number().min(0).max(1).nullable().optional(),
|
|
905
|
+
is_datacenter: z.boolean().nullable().optional(),
|
|
906
|
+
is_untrusted_segment: z.boolean().optional().default(false),
|
|
907
|
+
asn_reputation: z.number().min(0).max(1).nullable().optional()
|
|
908
|
+
});
|
|
909
|
+
BaseEventSchema.extend({
|
|
910
|
+
signal_payload: TransactionSignalPayloadSchema
|
|
911
|
+
});
|
|
912
|
+
BaseEventSchema.extend({
|
|
913
|
+
signal_payload: AnomalyFlagSignalPayloadSchema
|
|
914
|
+
});
|
|
915
|
+
BaseEventSchema.extend({
|
|
916
|
+
signal_payload: AuthSignalPayloadSchema
|
|
917
|
+
});
|
|
918
|
+
BaseEventSchema.extend({
|
|
919
|
+
signal_payload: BehaviorSignalPayloadSchema
|
|
920
|
+
});
|
|
921
|
+
BaseEventSchema.extend({
|
|
922
|
+
signal_payload: NetworkSignalPayloadSchema
|
|
923
|
+
});
|
|
924
|
+
BaseEventSchema.extend({
|
|
925
|
+
signal_payload: ModelOutputSignalPayloadSchema
|
|
926
|
+
});
|
|
927
|
+
BaseEventSchema.extend({
|
|
928
|
+
signal_payload: SystemTelemetrySignalPayloadSchema
|
|
929
|
+
});
|
|
930
|
+
|
|
931
|
+
// src/v1/builders/event-builder.ts
|
|
932
|
+
var buildRawEventPayload = (event, signalPayload, signalType) => {
|
|
933
|
+
const rawEventPayload = {
|
|
934
|
+
timestamp: event.timestamp,
|
|
935
|
+
provenance: event.provenance,
|
|
936
|
+
principal: event.principal,
|
|
937
|
+
confidence_hint: event.confidence_hint,
|
|
938
|
+
tenant_id: event.tenant_id,
|
|
939
|
+
signal_version: "v1",
|
|
940
|
+
signal_type: signalType,
|
|
941
|
+
signal_payload: signalPayload,
|
|
942
|
+
integrity: event.integrity
|
|
943
|
+
};
|
|
944
|
+
return BaseEventSchema.parse(rawEventPayload);
|
|
945
|
+
};
|
|
946
|
+
var buildRawEventRequest = (event, signalPayload, signalType) => {
|
|
947
|
+
const rawEventPayload = buildRawEventPayload(
|
|
948
|
+
event,
|
|
949
|
+
signalPayload,
|
|
950
|
+
signalType
|
|
951
|
+
);
|
|
952
|
+
return {
|
|
953
|
+
tenant_id: event.tenant_id,
|
|
954
|
+
payload: rawEventPayload
|
|
955
|
+
};
|
|
956
|
+
};
|
|
957
|
+
var createAuthEvent = (event) => {
|
|
958
|
+
const signalPayload = AuthSignalPayloadSchema.parse(event.signal_payload);
|
|
959
|
+
return buildRawEventRequest(event, signalPayload, "AUTH");
|
|
960
|
+
};
|
|
961
|
+
function createTransactionEvent(event) {
|
|
962
|
+
const signalPayload = TransactionSignalPayloadSchema.parse(
|
|
963
|
+
event.signal_payload
|
|
964
|
+
);
|
|
965
|
+
return buildRawEventRequest(event, signalPayload, "TRANSACTION");
|
|
966
|
+
}
|
|
967
|
+
var createAnomalyFlagEvent = (event) => {
|
|
968
|
+
const signalPayload = AnomalyFlagSignalPayloadSchema.parse(
|
|
969
|
+
event.signal_payload
|
|
970
|
+
);
|
|
971
|
+
return buildRawEventRequest(event, signalPayload, "ANOMALY_FLAG");
|
|
972
|
+
};
|
|
973
|
+
var createBehaviorEvent = (event) => {
|
|
974
|
+
const signalPayload = BehaviorSignalPayloadSchema.parse(event.signal_payload);
|
|
975
|
+
return buildRawEventRequest(event, signalPayload, "BEHAVIOR");
|
|
976
|
+
};
|
|
977
|
+
var createNetworkEvent = (event) => {
|
|
978
|
+
const signalPayload = NetworkSignalPayloadSchema.parse(event.signal_payload);
|
|
979
|
+
return buildRawEventRequest(event, signalPayload, "NETWORK_EVENT");
|
|
980
|
+
};
|
|
981
|
+
var createModelOutputEvent = (event) => {
|
|
982
|
+
const signalPayload = ModelOutputSignalPayloadSchema.parse(
|
|
983
|
+
event.signal_payload
|
|
984
|
+
);
|
|
985
|
+
return buildRawEventRequest(event, signalPayload, "MODEL_OUTPUT");
|
|
986
|
+
};
|
|
987
|
+
var createSystemTelemetryEvent = (event) => {
|
|
988
|
+
const signalPayload = SystemTelemetrySignalPayloadSchema.parse(
|
|
989
|
+
event.signal_payload
|
|
990
|
+
);
|
|
991
|
+
return buildRawEventRequest(event, signalPayload, "SYSTEM_TELEMETRY");
|
|
992
|
+
};
|
|
993
|
+
|
|
994
|
+
export { API_REGISTRY, QatiAPIError, QatiAuthError, QatiConfigError, QatiNotFoundError, QatiRateLimitError, QatiSDKError, QatiServerError, RawEventRequestSchema, Session, baseUrlFor, createAnomalyFlagEvent, createAuthEvent, createBehaviorEvent, createModelOutputEvent, createNetworkEvent, createSystemTelemetryEvent, createTransactionEvent, parseQatiConfig, resolveQatiConfig };
|
|
995
|
+
//# sourceMappingURL=index.js.map
|
|
996
|
+
//# sourceMappingURL=index.js.map
|