@blamejs/core 0.7.106 → 0.8.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.
Files changed (51) hide show
  1. package/CHANGELOG.md +19 -1
  2. package/NOTICE +17 -1
  3. package/README.md +4 -3
  4. package/index.js +16 -0
  5. package/lib/asyncapi-bindings.js +160 -0
  6. package/lib/asyncapi-traits.js +143 -0
  7. package/lib/asyncapi.js +531 -0
  8. package/lib/audit.js +6 -0
  9. package/lib/auth/acr-vocabulary.js +265 -0
  10. package/lib/auth/auth-time-tracker.js +111 -0
  11. package/lib/auth/elevation-grant.js +306 -0
  12. package/lib/auth/sd-jwt-vc-disclosure.js +95 -0
  13. package/lib/auth/sd-jwt-vc-holder.js +203 -0
  14. package/lib/auth/sd-jwt-vc-issuer.js +197 -0
  15. package/lib/auth/sd-jwt-vc.js +526 -0
  16. package/lib/auth/step-up-policy.js +335 -0
  17. package/lib/auth/step-up.js +445 -0
  18. package/lib/compliance-ai-act-logging.js +186 -0
  19. package/lib/compliance-ai-act-prohibited.js +205 -0
  20. package/lib/compliance-ai-act-risk.js +189 -0
  21. package/lib/compliance-ai-act-transparency.js +200 -0
  22. package/lib/compliance-ai-act.js +558 -0
  23. package/lib/compliance.js +2 -0
  24. package/lib/crypto.js +32 -0
  25. package/lib/flag-cache.js +136 -0
  26. package/lib/flag-evaluation-context.js +135 -0
  27. package/lib/flag-providers.js +279 -0
  28. package/lib/flag-targeting.js +210 -0
  29. package/lib/flag.js +284 -0
  30. package/lib/inbox.js +367 -0
  31. package/lib/mail-arc-sign.js +372 -0
  32. package/lib/mail-auth.js +2 -0
  33. package/lib/middleware/ai-act-disclosure.js +166 -0
  34. package/lib/middleware/asyncapi-serve.js +136 -0
  35. package/lib/middleware/flag-context.js +76 -0
  36. package/lib/middleware/index.js +15 -0
  37. package/lib/middleware/openapi-serve.js +143 -0
  38. package/lib/middleware/require-step-up.js +186 -0
  39. package/lib/openapi-paths-builder.js +248 -0
  40. package/lib/openapi-schema-walk.js +192 -0
  41. package/lib/openapi-security.js +169 -0
  42. package/lib/openapi-yaml.js +154 -0
  43. package/lib/openapi.js +443 -0
  44. package/lib/pqc-software.js +195 -0
  45. package/lib/vault/index.js +3 -0
  46. package/lib/vault-aad.js +259 -0
  47. package/lib/vendor/MANIFEST.json +29 -0
  48. package/lib/vendor/noble-post-quantum.cjs +18 -0
  49. package/lib/ws-client.js +829 -0
  50. package/package.json +1 -1
  51. package/sbom.cyclonedx.json +6 -6
@@ -0,0 +1,531 @@
1
+ "use strict";
2
+ /**
3
+ * b.asyncapi — AsyncAPI 3.0 schema-document builder.
4
+ *
5
+ * AsyncAPI is the event-driven sibling to OpenAPI. Operators describe
6
+ * pubsub / websocket / kafka / mqtt surfaces as a single document the
7
+ * framework can serve at /asyncapi.json (or /asyncapi.yaml) for
8
+ * downstream tooling.
9
+ *
10
+ * var aapi = b.asyncapi.create({
11
+ * info: { title: "Acme Events API", version: "1.0.0" },
12
+ * servers: {
13
+ * production: { host: "broker.acme.example.com:9092",
14
+ * protocol: "kafka",
15
+ * description: "Kafka broker" },
16
+ * },
17
+ * });
18
+ *
19
+ * aapi.channel("orders.created", {
20
+ * address: "orders.created",
21
+ * messages: {
22
+ * OrderCreated: {
23
+ * payload: { type: "object", properties: { id: { type: "string" } }, required: ["id"] },
24
+ * contentType: "application/json",
25
+ * },
26
+ * },
27
+ * bindings: { kafka: b.asyncapi.bindings.kafka({ topic: "orders.created", partitions: 4 }) },
28
+ * });
29
+ *
30
+ * aapi.operation("publishOrderCreated", {
31
+ * action: "send",
32
+ * channel: "orders.created",
33
+ * summary: "Publish an order-created event",
34
+ * });
35
+ *
36
+ * var doc = aapi.toJson(); // AsyncAPI 3.0 JSON document
37
+ * var yaml = aapi.toYaml(); // YAML serialisation
38
+ *
39
+ * The builder is FRAMEWORK-FACING: it produces a valid AsyncAPI 3.0
40
+ * document, but the operator's hand-written contract is the source of
41
+ * truth — it does NOT auto-walk b.pubsub topics or b.websocketChannels
42
+ * subscriptions (operators frequently want a smaller / different
43
+ * surface published than what is in-process).
44
+ *
45
+ * Public surface (b.asyncapi.*):
46
+ *
47
+ * .create({ info, servers, defaultContentType, security, externalDocs })
48
+ * -> builder
49
+ *
50
+ * builder.channel(channelId, opts) // register channel
51
+ * builder.operation(operationId, opts) // register operation
52
+ * builder.schema(name, schemaSpec) // reusable schema
53
+ * builder.message(name, messageSpec) // reusable message
54
+ * builder.security.add(name, scheme)
55
+ * builder.tag({ name, description })
56
+ * builder.toJson() / toJsonString() / toYaml()
57
+ *
58
+ * b.asyncapi.bindings.{websockets, kafka, amqp, mqtt, http}
59
+ * -> typed binding builders.
60
+ */
61
+
62
+ var validateOpts = require("./validate-opts");
63
+ var lazyRequire = require("./lazy-require");
64
+ var schemaWalk = require("./openapi-schema-walk");
65
+ var openapiSecurity = require("./openapi-security");
66
+ var openapiYaml = require("./openapi-yaml");
67
+ var bindingsMod = require("./asyncapi-bindings");
68
+ var traitsMod = require("./asyncapi-traits");
69
+ var { defineClass } = require("./framework-error");
70
+ var AsyncApiError = defineClass("AsyncApiError", { alwaysPermanent: true });
71
+
72
+ var audit = lazyRequire(function () { return require("./audit"); });
73
+
74
+ var ASYNCAPI_VERSION = "3.0.0";
75
+
76
+ var VALID_OPERATION_ACTIONS = ["send", "receive"];
77
+
78
+ function create(opts) {
79
+ opts = opts || {};
80
+ validateOpts(opts, [
81
+ "info", "servers", "defaultContentType", "security",
82
+ "externalDocs", "tags", "id",
83
+ ], "asyncapi.create");
84
+ if (!opts.info || typeof opts.info !== "object") {
85
+ throw new AsyncApiError("asyncapi/bad-info",
86
+ "create: info object is required (title + version)");
87
+ }
88
+ validateOpts.requireNonEmptyString(opts.info.title,
89
+ "create: info.title", AsyncApiError, "asyncapi/bad-info");
90
+ validateOpts.requireNonEmptyString(opts.info.version,
91
+ "create: info.version", AsyncApiError, "asyncapi/bad-info");
92
+
93
+ var info = Object.assign({}, opts.info);
94
+ var servers = _validateServers(opts.servers);
95
+ var channels = {};
96
+ var operations = {};
97
+ var components = {
98
+ schemas: {},
99
+ messages: {},
100
+ parameters: {},
101
+ securitySchemes: {},
102
+ serverVariables: {},
103
+ correlationIds: {},
104
+ operationTraits: {},
105
+ messageTraits: {},
106
+ replies: {},
107
+ replyAddresses: {},
108
+ };
109
+ var docTags = Array.isArray(opts.tags) ? opts.tags.slice() : [];
110
+ var docSecurity = Array.isArray(opts.security) ? opts.security.slice() : [];
111
+ var defaultContentType = (typeof opts.defaultContentType === "string" && opts.defaultContentType.length > 0)
112
+ ? opts.defaultContentType : "application/json";
113
+ var externalDocs = opts.externalDocs || null;
114
+ var docId = opts.id || null;
115
+
116
+ function _addChannel(channelId, channelOpts) {
117
+ validateOpts.requireNonEmptyString(channelId, "channel: channelId",
118
+ AsyncApiError, "asyncapi/bad-channel");
119
+ if (Object.prototype.hasOwnProperty.call(channels, channelId)) {
120
+ throw new AsyncApiError("asyncapi/duplicate-channel",
121
+ "channel: " + channelId + " already registered");
122
+ }
123
+ channelOpts = channelOpts || {};
124
+ validateOpts(channelOpts, [
125
+ "address", "messages", "title", "summary", "description",
126
+ "servers", "parameters", "tags", "bindings", "externalDocs",
127
+ ], "channel");
128
+ var ch = {};
129
+ if (channelOpts.address) ch.address = channelOpts.address;
130
+ if (channelOpts.title) ch.title = channelOpts.title;
131
+ if (channelOpts.summary) ch.summary = channelOpts.summary;
132
+ if (channelOpts.description) ch.description = channelOpts.description;
133
+ if (Array.isArray(channelOpts.servers)) ch.servers = channelOpts.servers.slice();
134
+ if (channelOpts.bindings) ch.bindings = channelOpts.bindings;
135
+ if (channelOpts.externalDocs) ch.externalDocs = channelOpts.externalDocs;
136
+ if (Array.isArray(channelOpts.tags)) ch.tags = channelOpts.tags.slice();
137
+ if (channelOpts.parameters && typeof channelOpts.parameters === "object") {
138
+ ch.parameters = {};
139
+ for (var pn in channelOpts.parameters) {
140
+ if (!Object.prototype.hasOwnProperty.call(channelOpts.parameters, pn)) continue;
141
+ ch.parameters[pn] = channelOpts.parameters[pn];
142
+ }
143
+ }
144
+ if (channelOpts.messages && typeof channelOpts.messages === "object") {
145
+ ch.messages = {};
146
+ for (var mn in channelOpts.messages) {
147
+ if (!Object.prototype.hasOwnProperty.call(channelOpts.messages, mn)) continue;
148
+ ch.messages[mn] = _normaliseMessage(channelOpts.messages[mn],
149
+ "channel " + channelId + ".messages." + mn);
150
+ }
151
+ }
152
+ channels[channelId] = ch;
153
+ return ch;
154
+ }
155
+
156
+ function _addOperation(operationId, opOpts) {
157
+ validateOpts.requireNonEmptyString(operationId, "operation: operationId",
158
+ AsyncApiError, "asyncapi/bad-operation");
159
+ if (Object.prototype.hasOwnProperty.call(operations, operationId)) {
160
+ throw new AsyncApiError("asyncapi/duplicate-operation",
161
+ "operation: " + operationId + " already registered");
162
+ }
163
+ opOpts = opOpts || {};
164
+ validateOpts(opOpts, [
165
+ "action", "channel", "messages", "summary", "description",
166
+ "tags", "bindings", "security", "externalDocs", "reply",
167
+ ], "operation");
168
+ if (VALID_OPERATION_ACTIONS.indexOf(opOpts.action) === -1) {
169
+ throw new AsyncApiError("asyncapi/bad-operation",
170
+ "operation: action must be 'send' or 'receive' - got " +
171
+ JSON.stringify(opOpts.action));
172
+ }
173
+ validateOpts.requireNonEmptyString(opOpts.channel, "operation: channel",
174
+ AsyncApiError, "asyncapi/bad-operation");
175
+ if (!Object.prototype.hasOwnProperty.call(channels, opOpts.channel)) {
176
+ throw new AsyncApiError("asyncapi/dangling-channel",
177
+ "operation " + operationId + ": channel " + JSON.stringify(opOpts.channel) +
178
+ " is not registered (declare it via builder.channel() first)");
179
+ }
180
+ var op = {
181
+ action: opOpts.action,
182
+ channel: { "$ref": "#/channels/" + opOpts.channel },
183
+ };
184
+ if (opOpts.summary) op.summary = opOpts.summary;
185
+ if (opOpts.description) op.description = opOpts.description;
186
+ if (Array.isArray(opOpts.tags)) op.tags = opOpts.tags.slice();
187
+ if (opOpts.bindings) op.bindings = opOpts.bindings;
188
+ if (Array.isArray(opOpts.security)) op.security = opOpts.security.slice();
189
+ if (opOpts.externalDocs) op.externalDocs = opOpts.externalDocs;
190
+ if (opOpts.reply) op.reply = opOpts.reply;
191
+ if (Array.isArray(opOpts.messages) && opOpts.messages.length > 0) {
192
+ op.messages = opOpts.messages.map(function (m, idx) {
193
+ if (typeof m === "string") {
194
+ return { "$ref": "#/channels/" + opOpts.channel + "/messages/" + m };
195
+ }
196
+ if (m && typeof m === "object" && typeof m["$ref"] === "string") {
197
+ return { "$ref": m["$ref"] };
198
+ }
199
+ throw new AsyncApiError("asyncapi/bad-operation",
200
+ "operation " + operationId + ".messages[" + idx +
201
+ "]: must be a message name string or an object with $ref");
202
+ });
203
+ }
204
+ operations[operationId] = op;
205
+ return op;
206
+ }
207
+
208
+ function _normaliseMessage(input, label) {
209
+ if (!input || typeof input !== "object") {
210
+ throw new AsyncApiError("asyncapi/bad-message",
211
+ label + ": message must be an object");
212
+ }
213
+ var m = {};
214
+ if (input.name) m.name = input.name;
215
+ if (input.title) m.title = input.title;
216
+ if (input.summary) m.summary = input.summary;
217
+ if (input.description) m.description = input.description;
218
+ if (input.contentType) m.contentType = input.contentType;
219
+ if (input.headers != null) m.headers = schemaWalk.walk(input.headers);
220
+ if (input.payload != null) m.payload = schemaWalk.walk(input.payload);
221
+ if (input.correlationId) m.correlationId = input.correlationId;
222
+ if (input.bindings) m.bindings = input.bindings;
223
+ if (input.tags) m.tags = input.tags;
224
+ if (Array.isArray(input.examples)) m.examples = input.examples.slice();
225
+ if (input.traits) m.traits = input.traits;
226
+ if (input.externalDocs) m.externalDocs = input.externalDocs;
227
+ return m;
228
+ }
229
+
230
+ return {
231
+ info: info,
232
+ asyncapi: ASYNCAPI_VERSION,
233
+
234
+ channel: function (id, channelOpts) { _addChannel(id, channelOpts); return this; },
235
+ operation: function (id, opOpts) { _addOperation(id, opOpts); return this; },
236
+
237
+ schema: function (name, schemaSpec) {
238
+ validateOpts.requireNonEmptyString(name, "schema: name",
239
+ AsyncApiError, "asyncapi/bad-component");
240
+ if (Object.prototype.hasOwnProperty.call(components.schemas, name)) {
241
+ throw new AsyncApiError("asyncapi/duplicate-component",
242
+ "schema: components.schemas." + name + " already registered");
243
+ }
244
+ components.schemas[name] = schemaWalk.walk(schemaSpec);
245
+ return this;
246
+ },
247
+
248
+ message: function (name, messageSpec) {
249
+ validateOpts.requireNonEmptyString(name, "message: name",
250
+ AsyncApiError, "asyncapi/bad-component");
251
+ components.messages[name] = _normaliseMessage(messageSpec, "message: " + name);
252
+ return this;
253
+ },
254
+
255
+ parameter: function (name, paramSpec) {
256
+ validateOpts.requireNonEmptyString(name, "parameter: name",
257
+ AsyncApiError, "asyncapi/bad-component");
258
+ components.parameters[name] = paramSpec;
259
+ return this;
260
+ },
261
+
262
+ correlationId: function (name, idSpec) {
263
+ validateOpts.requireNonEmptyString(name, "correlationId: name",
264
+ AsyncApiError, "asyncapi/bad-component");
265
+ if (!idSpec || typeof idSpec !== "object" || typeof idSpec.location !== "string") {
266
+ throw new AsyncApiError("asyncapi/bad-correlation-id",
267
+ "correlationId: spec must be { location: 'runtime expression', description? }");
268
+ }
269
+ components.correlationIds[name] = idSpec;
270
+ return this;
271
+ },
272
+
273
+ security: {
274
+ add: function (name, scheme) {
275
+ validateOpts.requireNonEmptyString(name, "security.add: name",
276
+ AsyncApiError, "asyncapi/bad-security");
277
+ if (!scheme || typeof scheme !== "object" || typeof scheme.type !== "string") {
278
+ throw new AsyncApiError("asyncapi/bad-security",
279
+ "security.add: scheme must be a securityScheme object with a type");
280
+ }
281
+ components.securitySchemes[name] = scheme;
282
+ return this;
283
+ },
284
+ require: function (requirement) {
285
+ if (!requirement || typeof requirement !== "object") {
286
+ throw new AsyncApiError("asyncapi/bad-security",
287
+ "security.require: requirement must be an object like { schemeName: ['scope'] }");
288
+ }
289
+ docSecurity.push(requirement);
290
+ return this;
291
+ },
292
+ },
293
+
294
+ tag: function (tagSpec) {
295
+ if (!tagSpec || typeof tagSpec !== "object" ||
296
+ typeof tagSpec.name !== "string" || tagSpec.name.length === 0) {
297
+ throw new AsyncApiError("asyncapi/bad-tag",
298
+ "tag: tagSpec.name is required");
299
+ }
300
+ docTags.push(tagSpec);
301
+ return this;
302
+ },
303
+
304
+ server: function (serverId, serverSpec) {
305
+ validateOpts.requireNonEmptyString(serverId, "server: serverId",
306
+ AsyncApiError, "asyncapi/bad-server");
307
+ _validateServerEntry(serverSpec, "server " + serverId);
308
+ servers[serverId] = Object.assign({}, serverSpec);
309
+ return this;
310
+ },
311
+
312
+ toJson: function () {
313
+ var doc = {
314
+ asyncapi: ASYNCAPI_VERSION,
315
+ info: info,
316
+ };
317
+ if (docId) doc.id = docId;
318
+ if (defaultContentType) doc.defaultContentType = defaultContentType;
319
+ if (Object.keys(servers).length > 0) doc.servers = servers;
320
+
321
+ doc.channels = channels;
322
+ doc.operations = operations;
323
+
324
+ var anyComponent = false;
325
+ var componentsOut = {};
326
+ var keys = ["schemas", "messages", "parameters", "securitySchemes",
327
+ "serverVariables", "correlationIds", "operationTraits",
328
+ "messageTraits", "replies", "replyAddresses"];
329
+ for (var k = 0; k < keys.length; k += 1) {
330
+ var key = keys[k];
331
+ if (Object.keys(components[key]).length > 0) {
332
+ componentsOut[key] = components[key];
333
+ anyComponent = true;
334
+ }
335
+ }
336
+ if (anyComponent) doc.components = componentsOut;
337
+
338
+ // Validate doc-level security references.
339
+ for (var r = 0; r < docSecurity.length; r += 1) {
340
+ for (var schemeName in docSecurity[r]) {
341
+ if (!Object.prototype.hasOwnProperty.call(docSecurity[r], schemeName)) continue;
342
+ if (!components.securitySchemes[schemeName]) {
343
+ throw new AsyncApiError("asyncapi/dangling-security",
344
+ "toJson: doc-level security references undefined scheme " +
345
+ JSON.stringify(schemeName));
346
+ }
347
+ }
348
+ }
349
+ // Operation-level security
350
+ for (var opId in operations) {
351
+ if (!Object.prototype.hasOwnProperty.call(operations, opId)) continue;
352
+ var opSec = operations[opId].security;
353
+ if (Array.isArray(opSec)) {
354
+ for (var os = 0; os < opSec.length; os += 1) {
355
+ for (var sn in opSec[os]) {
356
+ if (!Object.prototype.hasOwnProperty.call(opSec[os], sn)) continue;
357
+ if (!components.securitySchemes[sn]) {
358
+ throw new AsyncApiError("asyncapi/dangling-security",
359
+ "toJson: operation " + opId + " references undefined security scheme " +
360
+ JSON.stringify(sn));
361
+ }
362
+ }
363
+ }
364
+ }
365
+ }
366
+
367
+ if (docSecurity.length > 0) doc.security = docSecurity.slice();
368
+ if (docTags.length > 0) doc.tags = docTags.slice();
369
+ if (externalDocs) doc.externalDocs = externalDocs;
370
+
371
+ try {
372
+ audit().safeEmit({
373
+ action: "asyncapi.document.built",
374
+ outcome: "success",
375
+ actor: null,
376
+ metadata: {
377
+ title: info.title,
378
+ version: info.version,
379
+ channelCount: Object.keys(channels).length,
380
+ operationCount: Object.keys(operations).length,
381
+ schemaCount: Object.keys(components.schemas).length,
382
+ messageCount: Object.keys(components.messages).length,
383
+ },
384
+ });
385
+ } catch (_e) { /* drop-silent */ }
386
+
387
+ return doc;
388
+ },
389
+
390
+ toJsonString: function (indent) {
391
+ return JSON.stringify(this.toJson(), null, indent || 2);
392
+ },
393
+
394
+ toYaml: function () {
395
+ return openapiYaml.toYaml(this.toJson());
396
+ },
397
+ };
398
+ }
399
+
400
+ function _validateServers(input) {
401
+ if (input == null) return {};
402
+ if (typeof input !== "object" || Array.isArray(input)) {
403
+ throw new AsyncApiError("asyncapi/bad-server",
404
+ "create: servers must be a map { serverId: spec, ... }");
405
+ }
406
+ var out = {};
407
+ for (var key in input) {
408
+ if (!Object.prototype.hasOwnProperty.call(input, key)) continue;
409
+ _validateServerEntry(input[key], "create: servers." + key);
410
+ out[key] = Object.assign({}, input[key]);
411
+ }
412
+ return out;
413
+ }
414
+
415
+ function _validateServerEntry(entry, label) {
416
+ if (!entry || typeof entry !== "object") {
417
+ throw new AsyncApiError("asyncapi/bad-server",
418
+ label + ": server must be an object");
419
+ }
420
+ validateOpts.requireNonEmptyString(entry.host,
421
+ label + ": host", AsyncApiError, "asyncapi/bad-server");
422
+ validateOpts.requireNonEmptyString(entry.protocol,
423
+ label + ": protocol", AsyncApiError, "asyncapi/bad-server");
424
+ }
425
+
426
+ // Parse + validate an external AsyncAPI 3.0 document. Like openapi.parse:
427
+ // returns `{ doc, errors[], valid }`. Throws on invalid JSON.
428
+ function parse(jsonStringOrObject) {
429
+ var doc;
430
+ if (typeof jsonStringOrObject === "string") {
431
+ try { doc = JSON.parse(jsonStringOrObject); } // allow:bare-json-parse — operator-supplied AsyncAPI doc; size-bounded by caller
432
+ catch (e) {
433
+ throw new AsyncApiError("asyncapi/bad-json",
434
+ "asyncapi.parse: invalid JSON — " + e.message);
435
+ }
436
+ } else if (jsonStringOrObject != null && typeof jsonStringOrObject === "object") {
437
+ doc = jsonStringOrObject;
438
+ } else {
439
+ throw new AsyncApiError("asyncapi/bad-input",
440
+ "asyncapi.parse: input must be a JSON string or a plain object");
441
+ }
442
+ var errors = [];
443
+ if (typeof doc.asyncapi !== "string") {
444
+ errors.push("missing or non-string `asyncapi` version field (must be 3.0.x)");
445
+ } else if (doc.asyncapi.indexOf("3.0") !== 0) {
446
+ errors.push("`asyncapi` version must be 3.0.x — got " + JSON.stringify(doc.asyncapi));
447
+ }
448
+ if (!doc.info || typeof doc.info !== "object") {
449
+ errors.push("missing or non-object `info`");
450
+ } else {
451
+ if (typeof doc.info.title !== "string" || doc.info.title.length === 0) {
452
+ errors.push("info.title must be a non-empty string");
453
+ }
454
+ if (typeof doc.info.version !== "string" || doc.info.version.length === 0) {
455
+ errors.push("info.version must be a non-empty string");
456
+ }
457
+ }
458
+ // Channels must be an object when present.
459
+ if (doc.channels != null && typeof doc.channels !== "object") {
460
+ errors.push("`channels` must be an object when present");
461
+ }
462
+ // Operations must reference declared channels.
463
+ var channels = doc.channels || {};
464
+ if (doc.operations && typeof doc.operations === "object") {
465
+ for (var opId in doc.operations) {
466
+ if (!Object.prototype.hasOwnProperty.call(doc.operations, opId)) continue;
467
+ var op = doc.operations[opId];
468
+ if (!op || typeof op !== "object") {
469
+ errors.push("operations." + opId + ": must be an object");
470
+ continue;
471
+ }
472
+ if (op.action !== "send" && op.action !== "receive") {
473
+ errors.push("operations." + opId + ".action must be 'send' or 'receive' — got " +
474
+ JSON.stringify(op.action));
475
+ }
476
+ var channelRef = op.channel;
477
+ if (channelRef && typeof channelRef === "object" && typeof channelRef["$ref"] === "string") {
478
+ var refMatch = channelRef["$ref"].match(/^#\/channels\/(.+)$/);
479
+ if (refMatch && !Object.prototype.hasOwnProperty.call(channels, refMatch[1])) {
480
+ errors.push("operations." + opId + ".channel: $ref " +
481
+ JSON.stringify(channelRef["$ref"]) +
482
+ " does not resolve to a declared channel");
483
+ }
484
+ } else {
485
+ errors.push("operations." + opId + ".channel: must be a $ref to #/channels/<id>");
486
+ }
487
+ }
488
+ }
489
+ // Server entries must carry host + protocol when present.
490
+ if (doc.servers && typeof doc.servers === "object") {
491
+ for (var sid in doc.servers) {
492
+ if (!Object.prototype.hasOwnProperty.call(doc.servers, sid)) continue;
493
+ var entry = doc.servers[sid];
494
+ if (!entry || typeof entry !== "object") {
495
+ errors.push("servers." + sid + ": must be an object");
496
+ continue;
497
+ }
498
+ if (typeof entry.host !== "string" || entry.host.length === 0) {
499
+ errors.push("servers." + sid + ".host must be a non-empty string");
500
+ }
501
+ if (typeof entry.protocol !== "string" || entry.protocol.length === 0) {
502
+ errors.push("servers." + sid + ".protocol must be a non-empty string");
503
+ }
504
+ }
505
+ }
506
+ // Dangling doc-level security references.
507
+ var schemes = (doc.components && doc.components.securitySchemes) || {};
508
+ if (Array.isArray(doc.security)) {
509
+ for (var s = 0; s < doc.security.length; s += 1) {
510
+ for (var sn in doc.security[s]) {
511
+ if (!Object.prototype.hasOwnProperty.call(doc.security[s], sn)) continue;
512
+ if (!schemes[sn]) {
513
+ errors.push("doc-level security references undefined scheme " + JSON.stringify(sn));
514
+ }
515
+ }
516
+ }
517
+ }
518
+ return { doc: doc, errors: errors, valid: errors.length === 0 };
519
+ }
520
+
521
+ module.exports = {
522
+ create: create,
523
+ parse: parse,
524
+ bindings: bindingsMod,
525
+ traits: traitsMod,
526
+ schemaWalk: schemaWalk.walk,
527
+ security: openapiSecurity,
528
+ toYaml: openapiYaml.toYaml,
529
+ VERSION: ASYNCAPI_VERSION,
530
+ AsyncApiError: AsyncApiError,
531
+ };
package/lib/audit.js CHANGED
@@ -216,6 +216,12 @@ var FRAMEWORK_NAMESPACES = [
216
216
  "network", // b.middleware.networkAllowlist (network.gate.denied)
217
217
  "notify", // b.notify
218
218
  "objectstore", // b.objectStore.bucketOps (objectstore.bucket.* / objectstore.object.*)
219
+ "openapi", // b.openapi (openapi.document.built / openapi.document.served)
220
+ "asyncapi", // b.asyncapi (asyncapi.document.built)
221
+ "vault", // b.vault.aad (vault.aad.sealed / vault.aad.unseal_failed)
222
+ "wsclient", // b.wsClient (wsclient.connected / closed / error)
223
+ "inbox", // b.inbox (inbox.received / handled / handle_failed / swept)
224
+ "flag", // b.flag (flag.evaluated / flag.evaluation.error / flag.cache.bust)
219
225
  "permissions", // b.permissions
220
226
  "restore", // b.restore
221
227
  "retention", // b.retention (retention.rule.declared / sweep.started / row.processed / sweep.completed / sweep.failed)