@blamejs/core 0.9.24 → 0.9.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +885 -871
- package/index.js +32 -1
- package/lib/agent-audit.js +45 -0
- package/lib/agent-event-bus.js +336 -0
- package/lib/agent-idempotency.js +2 -8
- package/lib/agent-orchestrator.js +2 -8
- package/lib/agent-posture-chain.js +208 -0
- package/lib/agent-saga.js +191 -0
- package/lib/agent-snapshot.js +346 -0
- package/lib/agent-stream.js +2 -8
- package/lib/agent-tenant.js +308 -0
- package/lib/agent-trace.js +218 -0
- package/lib/guard-all.js +1 -0
- package/lib/guard-dsn.js +379 -0
- package/lib/guard-envelope.js +294 -0
- package/lib/guard-event-bus-payload.js +217 -0
- package/lib/guard-event-bus-topic.js +150 -0
- package/lib/guard-posture-chain.js +201 -0
- package/lib/guard-saga-config.js +157 -0
- package/lib/guard-smtp-command.js +484 -0
- package/lib/guard-snapshot-envelope.js +168 -0
- package/lib/guard-tenant-id.js +138 -0
- package/lib/guard-trace-context.js +172 -0
- package/lib/ip-utils.js +102 -0
- package/lib/mail-auth.js +4 -35
- package/lib/mail-greylist.js +448 -0
- package/lib/mail-helo.js +473 -0
- package/lib/mail-rbl.js +392 -0
- package/lib/mail.js +2 -1
- package/lib/network-dns-resolver.js +500 -0
- package/lib/network.js +1 -0
- package/lib/redis-client.js +2 -1
- package/lib/safe-dns.js +665 -0
- package/lib/tracing.js +36 -0
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/index.js
CHANGED
|
@@ -87,6 +87,7 @@ var storage = require("./lib/storage");
|
|
|
87
87
|
var safeJson = require("./lib/safe-json");
|
|
88
88
|
var safeJsonPath = require("./lib/safe-jsonpath");
|
|
89
89
|
var safeMime = require("./lib/safe-mime");
|
|
90
|
+
var safeDns = require("./lib/safe-dns");
|
|
90
91
|
var mailStore = require("./lib/mail-store");
|
|
91
92
|
var ntpCheck = require("./lib/ntp-check");
|
|
92
93
|
var auditSign = require("./lib/audit-sign");
|
|
@@ -160,6 +161,9 @@ var guardHtml = require("./lib/guard-html");
|
|
|
160
161
|
var guardSvg = require("./lib/guard-svg");
|
|
161
162
|
var guardFilename = require("./lib/guard-filename");
|
|
162
163
|
var guardMessageId = require("./lib/guard-message-id");
|
|
164
|
+
var guardSmtpCommand = require("./lib/guard-smtp-command");
|
|
165
|
+
var guardEnvelope = require("./lib/guard-envelope");
|
|
166
|
+
var guardDsn = require("./lib/guard-dsn");
|
|
163
167
|
var guardMailQuery = require("./lib/guard-mail-query");
|
|
164
168
|
var guardMailCompose = require("./lib/guard-mail-compose");
|
|
165
169
|
var guardMailReply = require("./lib/guard-mail-reply");
|
|
@@ -168,9 +172,22 @@ var guardMailSieve = require("./lib/guard-mail-sieve");
|
|
|
168
172
|
var guardAgentRegistry = require("./lib/guard-agent-registry");
|
|
169
173
|
var guardIdempotencyKey = require("./lib/guard-idempotency-key");
|
|
170
174
|
var guardStreamArgs = require("./lib/guard-stream-args");
|
|
175
|
+
var guardEventBusTopic = require("./lib/guard-event-bus-topic");
|
|
176
|
+
var guardEventBusPayload = require("./lib/guard-event-bus-payload");
|
|
177
|
+
var guardTenantId = require("./lib/guard-tenant-id");
|
|
178
|
+
var guardSagaConfig = require("./lib/guard-saga-config");
|
|
179
|
+
var guardPostureChain = require("./lib/guard-posture-chain");
|
|
180
|
+
var guardTraceContext = require("./lib/guard-trace-context");
|
|
181
|
+
var guardSnapshotEnvelope = require("./lib/guard-snapshot-envelope");
|
|
171
182
|
var agentOrchestrator = require("./lib/agent-orchestrator");
|
|
172
183
|
var agentIdempotency = require("./lib/agent-idempotency");
|
|
173
184
|
var agentStream = require("./lib/agent-stream");
|
|
185
|
+
var agentEventBus = require("./lib/agent-event-bus");
|
|
186
|
+
var agentTenant = require("./lib/agent-tenant");
|
|
187
|
+
var agentSaga = require("./lib/agent-saga");
|
|
188
|
+
var agentPostureChain = require("./lib/agent-posture-chain");
|
|
189
|
+
var agentTrace = require("./lib/agent-trace");
|
|
190
|
+
var agentSnapshot = require("./lib/agent-snapshot");
|
|
174
191
|
var guardArchive = require("./lib/guard-archive");
|
|
175
192
|
var guardJson = require("./lib/guard-json");
|
|
176
193
|
var guardYaml = require("./lib/guard-yaml");
|
|
@@ -237,6 +254,9 @@ var csv = require("./lib/csv");
|
|
|
237
254
|
var time = require("./lib/time");
|
|
238
255
|
var uuid = require("./lib/uuid");
|
|
239
256
|
var mail = require("./lib/mail");
|
|
257
|
+
mail.rbl = require("./lib/mail-rbl");
|
|
258
|
+
mail.greylist = require("./lib/mail-greylist");
|
|
259
|
+
mail.helo = require("./lib/mail-helo");
|
|
240
260
|
var mailArf = require("./lib/mail-arf");
|
|
241
261
|
var mailBounce = require("./lib/mail-bounce");
|
|
242
262
|
var mailMdn = require("./lib/mail-mdn");
|
|
@@ -409,6 +429,9 @@ module.exports = {
|
|
|
409
429
|
guardSvg: guardSvg,
|
|
410
430
|
guardFilename: guardFilename,
|
|
411
431
|
guardMessageId: guardMessageId,
|
|
432
|
+
guardSmtpCommand: guardSmtpCommand,
|
|
433
|
+
guardEnvelope: guardEnvelope,
|
|
434
|
+
guardDsn: guardDsn,
|
|
412
435
|
guardMailQuery: guardMailQuery,
|
|
413
436
|
guardMailCompose: guardMailCompose,
|
|
414
437
|
guardMailReply: guardMailReply,
|
|
@@ -417,7 +440,14 @@ module.exports = {
|
|
|
417
440
|
guardAgentRegistry: guardAgentRegistry,
|
|
418
441
|
guardIdempotencyKey: guardIdempotencyKey,
|
|
419
442
|
guardStreamArgs: guardStreamArgs,
|
|
420
|
-
|
|
443
|
+
guardEventBusTopic: guardEventBusTopic,
|
|
444
|
+
guardEventBusPayload: guardEventBusPayload,
|
|
445
|
+
guardTenantId: guardTenantId,
|
|
446
|
+
guardSagaConfig: guardSagaConfig,
|
|
447
|
+
guardPostureChain: guardPostureChain,
|
|
448
|
+
guardTraceContext: guardTraceContext,
|
|
449
|
+
guardSnapshotEnvelope: guardSnapshotEnvelope,
|
|
450
|
+
agent: { orchestrator: agentOrchestrator, idempotency: agentIdempotency, stream: agentStream, eventBus: agentEventBus, tenant: agentTenant, saga: agentSaga, postureChain: agentPostureChain, trace: agentTrace, snapshot: agentSnapshot },
|
|
421
451
|
guardArchive: guardArchive,
|
|
422
452
|
guardJson: guardJson,
|
|
423
453
|
guardYaml: guardYaml,
|
|
@@ -498,6 +528,7 @@ module.exports = {
|
|
|
498
528
|
safeJson: safeJson,
|
|
499
529
|
safeJsonPath: safeJsonPath,
|
|
500
530
|
safeMime: safeMime,
|
|
531
|
+
safeDns: safeDns,
|
|
501
532
|
mailStore: mailStore,
|
|
502
533
|
safeSchema: safeSchema,
|
|
503
534
|
pagination: pagination,
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* b.agent._audit — internal shared audit-emit helper for the agent
|
|
4
|
+
* substrate (`b.agent.orchestrator` / `b.agent.idempotency` /
|
|
5
|
+
* `b.agent.stream` / `b.agent.eventBus` / future substrate slices).
|
|
6
|
+
*
|
|
7
|
+
* Each agent primitive emits audit events at lifecycle boundaries
|
|
8
|
+
* (registered / opened / closed / replay / denied / drop / etc). The
|
|
9
|
+
* emit logic is identical: actor shape → audit.safeEmit() → swallow
|
|
10
|
+
* any audit-side failures. Extracted here so the 4+ agent substrate
|
|
11
|
+
* modules don't re-implement the same wrapper.
|
|
12
|
+
*
|
|
13
|
+
* Internal — operator-facing surface is each primitive's `.audit`
|
|
14
|
+
* opt; this is the implementation detail.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
function safeAudit(auditImpl, action, actor, metadata) {
|
|
18
|
+
try {
|
|
19
|
+
auditImpl.safeEmit({
|
|
20
|
+
action: action,
|
|
21
|
+
actor: actor ? { id: actor.id, roles: actor.roles || [] } : { id: "<system>" },
|
|
22
|
+
outcome: _outcomeFor(action),
|
|
23
|
+
metadata: metadata || {},
|
|
24
|
+
});
|
|
25
|
+
} catch (_e) { /* drop-silent — audit failures don't crash the call */ }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// "denied" / "drop" / "threw" / "different_args" / "miss" / "not_implemented"
|
|
29
|
+
// all imply failure outcome; anything else is success. Per-primitive
|
|
30
|
+
// classification can override by passing a metadata.outcome — that's
|
|
31
|
+
// merged in by the caller, not here.
|
|
32
|
+
function _outcomeFor(action) {
|
|
33
|
+
if (typeof action !== "string") return "success";
|
|
34
|
+
if (action.indexOf("denied") >= 0) return "failure";
|
|
35
|
+
if (action.indexOf("drop") >= 0) return "failure";
|
|
36
|
+
if (action.indexOf("threw") >= 0) return "failure";
|
|
37
|
+
if (action.indexOf("different_args") >= 0) return "failure";
|
|
38
|
+
if (action.indexOf("miss") >= 0) return "failure";
|
|
39
|
+
if (action.indexOf("not_implemented") >= 0) return "failure";
|
|
40
|
+
return "success";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = {
|
|
44
|
+
safeAudit: safeAudit,
|
|
45
|
+
};
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @module b.agent.eventBus
|
|
4
|
+
* @nav Agent
|
|
5
|
+
* @title Agent Event Bus
|
|
6
|
+
* @order 65
|
|
7
|
+
*
|
|
8
|
+
* @intro
|
|
9
|
+
* Typed cross-agent publish/subscribe on top of `b.pubsub` (or any
|
|
10
|
+
* pubsub-shaped instance with `publish` / `subscribe` /
|
|
11
|
+
* `unsubscribe`). Substrate for every agent-to-agent reaction the
|
|
12
|
+
* mail stack + future agents need: `mail.scan.malware-detected` →
|
|
13
|
+
* MX refuses source, `mail.crypto.key-rotated` → vault invalidates
|
|
14
|
+
* cached recipient keys, `ai.classify.prompt-injection-detected` →
|
|
15
|
+
* agent quarantines, etc.
|
|
16
|
+
*
|
|
17
|
+
* The bus owns:
|
|
18
|
+
*
|
|
19
|
+
* - **Topic registry** — `registerTopic(name, { schema, posture,
|
|
20
|
+
* permissions, tenantScope })` declares the wire contract at
|
|
21
|
+
* boot. Unknown topics refuse publish + subscribe so typos
|
|
22
|
+
* fail loudly.
|
|
23
|
+
* - **Schema enforcement** — every payload validated against the
|
|
24
|
+
* declared schema before publish AND at each delivery
|
|
25
|
+
* (defends in-flight tampering).
|
|
26
|
+
* - **Permission gating** — `b.permissions.check(actor, scope)`
|
|
27
|
+
* on every publish + subscribe.
|
|
28
|
+
* - **Posture re-validation at delivery** — same shape as
|
|
29
|
+
* v0.9.20 cross-queue posture check.
|
|
30
|
+
* - **Audit lifecycle** — publish / subscribe / delivery / refused
|
|
31
|
+
* events emit to the operator's audit chain.
|
|
32
|
+
*
|
|
33
|
+
* ```js
|
|
34
|
+
* var bus = b.agent.eventBus.create({
|
|
35
|
+
* pubsub: myPubsub,
|
|
36
|
+
* audit: b.audit,
|
|
37
|
+
* permissions: myPerms,
|
|
38
|
+
* });
|
|
39
|
+
*
|
|
40
|
+
* bus.registerTopic("mail.scan.malware-detected", {
|
|
41
|
+
* schema: {
|
|
42
|
+
* source: "string",
|
|
43
|
+
* confidence: "number",
|
|
44
|
+
* detectedAt: "isoDateTime",
|
|
45
|
+
* },
|
|
46
|
+
* posture: "soc2",
|
|
47
|
+
* permissions: {
|
|
48
|
+
* publish: ["mail-scan:write"],
|
|
49
|
+
* subscribe: ["mail-mx:write"],
|
|
50
|
+
* },
|
|
51
|
+
* });
|
|
52
|
+
*
|
|
53
|
+
* await bus.publish("mail.scan.malware-detected", {
|
|
54
|
+
* source: "1.2.3.4", confidence: 0.95, detectedAt: new Date().toISOString(),
|
|
55
|
+
* }, { actor: { id: "scan-agent", roles: ["mail-scan-internal"] } });
|
|
56
|
+
* ```
|
|
57
|
+
*
|
|
58
|
+
* @card
|
|
59
|
+
* Typed cross-agent publish/subscribe. Topics registered with schema
|
|
60
|
+
* + posture + permissions; every payload validated; subscriber-side
|
|
61
|
+
* posture re-validated at delivery so no posture downgrade survives
|
|
62
|
+
* the bus boundary.
|
|
63
|
+
*/
|
|
64
|
+
|
|
65
|
+
var lazyRequire = require("./lazy-require");
|
|
66
|
+
var { defineClass } = require("./framework-error");
|
|
67
|
+
var guardEventBusTopic = require("./guard-event-bus-topic");
|
|
68
|
+
var guardEventBusPayload = require("./guard-event-bus-payload");
|
|
69
|
+
var agentAudit = require("./agent-audit");
|
|
70
|
+
|
|
71
|
+
var audit = lazyRequire(function () { return require("./audit"); });
|
|
72
|
+
|
|
73
|
+
var AgentEventBusError = defineClass("AgentEventBusError", { alwaysPermanent: true });
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @primitive b.agent.eventBus.create
|
|
77
|
+
* @signature b.agent.eventBus.create(opts)
|
|
78
|
+
* @since 0.9.25
|
|
79
|
+
* @status stable
|
|
80
|
+
* @related b.agent.orchestrator.create, b.pubsub.create
|
|
81
|
+
*
|
|
82
|
+
* Create the bus facade. Returns an instance with `registerTopic` /
|
|
83
|
+
* `publish` / `subscribe` / `listTopics`. Operator supplies a pubsub-
|
|
84
|
+
* shaped backend; framework owns schema validation, permission
|
|
85
|
+
* gating, posture re-validation, audit lifecycle.
|
|
86
|
+
*
|
|
87
|
+
* @opts
|
|
88
|
+
* pubsub: { publish, subscribe, unsubscribe }, // required
|
|
89
|
+
* audit: b.audit namespace, // optional
|
|
90
|
+
* permissions: b.permissions instance, // optional
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* var bus = b.agent.eventBus.create({ pubsub: myPubsub });
|
|
94
|
+
* bus.registerTopic("mail.scan.malware-detected", {
|
|
95
|
+
* schema: { source: "string" },
|
|
96
|
+
* });
|
|
97
|
+
* await bus.publish("mail.scan.malware-detected", { source: "1.2.3.4" });
|
|
98
|
+
*/
|
|
99
|
+
function create(opts) {
|
|
100
|
+
if (!opts || typeof opts !== "object") {
|
|
101
|
+
throw new AgentEventBusError("agent-event-bus/bad-opts",
|
|
102
|
+
"create: opts required");
|
|
103
|
+
}
|
|
104
|
+
if (!opts.pubsub || typeof opts.pubsub.publish !== "function" ||
|
|
105
|
+
typeof opts.pubsub.subscribe !== "function") {
|
|
106
|
+
throw new AgentEventBusError("agent-event-bus/bad-pubsub",
|
|
107
|
+
"create: opts.pubsub must expose { publish, subscribe }");
|
|
108
|
+
}
|
|
109
|
+
var auditImpl = opts.audit || audit();
|
|
110
|
+
var permissions = opts.permissions || null;
|
|
111
|
+
var topics = new Map();
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
registerTopic: function (name, topicOpts) { return _registerTopic(topics, name, topicOpts || {}, auditImpl); },
|
|
115
|
+
publish: function (name, payload, pOpts) { return _publish(topics, opts.pubsub, name, payload, pOpts || {}, permissions, auditImpl); },
|
|
116
|
+
subscribe: function (name, handler, sOpts) { return _subscribe(topics, opts.pubsub, name, handler, sOpts || {}, permissions, auditImpl); },
|
|
117
|
+
listTopics: function (args) { return _listTopics(topics, args || {}, permissions); },
|
|
118
|
+
AgentEventBusError: AgentEventBusError,
|
|
119
|
+
guards: {
|
|
120
|
+
topic: guardEventBusTopic,
|
|
121
|
+
payload: guardEventBusPayload,
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ---- Registry -------------------------------------------------------------
|
|
127
|
+
|
|
128
|
+
function _registerTopic(topics, name, topicOpts, auditImpl) {
|
|
129
|
+
guardEventBusTopic.validate(name);
|
|
130
|
+
if (topics.has(name)) {
|
|
131
|
+
throw new AgentEventBusError("agent-event-bus/topic-duplicate",
|
|
132
|
+
"registerTopic: '" + name + "' already registered");
|
|
133
|
+
}
|
|
134
|
+
if (!topicOpts.schema || typeof topicOpts.schema !== "object") {
|
|
135
|
+
throw new AgentEventBusError("agent-event-bus/bad-schema",
|
|
136
|
+
"registerTopic: schema required (flat key→type map)");
|
|
137
|
+
}
|
|
138
|
+
var entry = {
|
|
139
|
+
name: name,
|
|
140
|
+
schema: Object.freeze(Object.assign({}, topicOpts.schema)),
|
|
141
|
+
posture: topicOpts.posture || null,
|
|
142
|
+
tenantScope: topicOpts.tenantScope === true,
|
|
143
|
+
permissions: {
|
|
144
|
+
publish: topicOpts.permissions && Array.isArray(topicOpts.permissions.publish)
|
|
145
|
+
? topicOpts.permissions.publish.slice() : null,
|
|
146
|
+
subscribe: topicOpts.permissions && Array.isArray(topicOpts.permissions.subscribe)
|
|
147
|
+
? topicOpts.permissions.subscribe.slice() : null,
|
|
148
|
+
},
|
|
149
|
+
registeredAt: Date.now(),
|
|
150
|
+
};
|
|
151
|
+
topics.set(name, entry);
|
|
152
|
+
_safeAudit(auditImpl, "agent.event_bus.topic_registered", null, {
|
|
153
|
+
name: name, posture: entry.posture, tenantScope: entry.tenantScope,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function _listTopics(topics, args, permissions) {
|
|
158
|
+
// Permission gate: list-topics requires no special scope by default;
|
|
159
|
+
// operator can wrap with their own permissions instance for stricter.
|
|
160
|
+
var out = [];
|
|
161
|
+
topics.forEach(function (entry) {
|
|
162
|
+
if (args.kind && entry.kind && entry.kind !== args.kind) return;
|
|
163
|
+
out.push({
|
|
164
|
+
name: entry.name,
|
|
165
|
+
schema: entry.schema,
|
|
166
|
+
posture: entry.posture,
|
|
167
|
+
tenantScope: entry.tenantScope,
|
|
168
|
+
registeredAt: entry.registeredAt,
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
return out;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ---- Publish --------------------------------------------------------------
|
|
175
|
+
|
|
176
|
+
async function _publish(topics, pubsub, name, payload, pOpts, permissions, auditImpl) {
|
|
177
|
+
guardEventBusTopic.validate(name);
|
|
178
|
+
var entry = topics.get(name);
|
|
179
|
+
if (!entry) {
|
|
180
|
+
throw new AgentEventBusError("agent-event-bus/unknown-topic",
|
|
181
|
+
"publish: topic '" + name + "' not registered");
|
|
182
|
+
}
|
|
183
|
+
// Permission check for publish.
|
|
184
|
+
if (permissions && entry.permissions.publish) {
|
|
185
|
+
if (!pOpts.actor) {
|
|
186
|
+
throw new AgentEventBusError("agent-event-bus/no-actor",
|
|
187
|
+
"publish: topic '" + name + "' requires actor");
|
|
188
|
+
}
|
|
189
|
+
var allowedPub = false;
|
|
190
|
+
for (var i = 0; i < entry.permissions.publish.length; i += 1) {
|
|
191
|
+
if (permissions.check(pOpts.actor, entry.permissions.publish[i])) {
|
|
192
|
+
allowedPub = true; break;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
if (!allowedPub) {
|
|
196
|
+
_safeAudit(auditImpl, "agent.event_bus.publish_denied", pOpts.actor, { topic: name });
|
|
197
|
+
throw new AgentEventBusError("agent-event-bus/publish-denied",
|
|
198
|
+
"publish: actor lacks any of " + JSON.stringify(entry.permissions.publish) +
|
|
199
|
+
" required for topic '" + name + "'");
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
// Schema validation.
|
|
203
|
+
guardEventBusPayload.validate(payload, entry.schema);
|
|
204
|
+
// Wrap the payload with topic metadata so subscribers can see the
|
|
205
|
+
// posture + tenantScope at delivery (re-validation).
|
|
206
|
+
var wrapped = {
|
|
207
|
+
_topic: name,
|
|
208
|
+
_posture: entry.posture,
|
|
209
|
+
_tenantId: pOpts.actor && pOpts.actor.tenantId ? pOpts.actor.tenantId : null,
|
|
210
|
+
_publishedAt: Date.now(),
|
|
211
|
+
payload: payload,
|
|
212
|
+
};
|
|
213
|
+
await pubsub.publish(name, wrapped);
|
|
214
|
+
_safeAudit(auditImpl, "agent.event_bus.published", pOpts.actor, {
|
|
215
|
+
topic: name, posture: entry.posture,
|
|
216
|
+
});
|
|
217
|
+
return { topic: name, publishedAt: wrapped._publishedAt };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ---- Subscribe ------------------------------------------------------------
|
|
221
|
+
|
|
222
|
+
async function _subscribe(topics, pubsub, name, handler, sOpts, permissions, auditImpl) {
|
|
223
|
+
guardEventBusTopic.validate(name);
|
|
224
|
+
var entry = topics.get(name);
|
|
225
|
+
if (!entry) {
|
|
226
|
+
throw new AgentEventBusError("agent-event-bus/unknown-topic",
|
|
227
|
+
"subscribe: topic '" + name + "' not registered");
|
|
228
|
+
}
|
|
229
|
+
if (typeof handler !== "function") {
|
|
230
|
+
throw new AgentEventBusError("agent-event-bus/bad-handler",
|
|
231
|
+
"subscribe: handler must be a function");
|
|
232
|
+
}
|
|
233
|
+
// Permission check for subscribe.
|
|
234
|
+
if (permissions && entry.permissions.subscribe) {
|
|
235
|
+
if (!sOpts.actor) {
|
|
236
|
+
throw new AgentEventBusError("agent-event-bus/no-actor",
|
|
237
|
+
"subscribe: topic '" + name + "' requires actor");
|
|
238
|
+
}
|
|
239
|
+
var allowedSub = false;
|
|
240
|
+
for (var i = 0; i < entry.permissions.subscribe.length; i += 1) {
|
|
241
|
+
if (permissions.check(sOpts.actor, entry.permissions.subscribe[i])) {
|
|
242
|
+
allowedSub = true; break;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
if (!allowedSub) {
|
|
246
|
+
_safeAudit(auditImpl, "agent.event_bus.subscribe_denied", sOpts.actor, { topic: name });
|
|
247
|
+
throw new AgentEventBusError("agent-event-bus/subscribe-denied",
|
|
248
|
+
"subscribe: actor lacks any of " + JSON.stringify(entry.permissions.subscribe) +
|
|
249
|
+
" required for topic '" + name + "'");
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
// Cross-tenant subscription gate — when tenantScope is set, the
|
|
253
|
+
// subscriber's actor MUST declare a tenantId at subscribe-time.
|
|
254
|
+
// Subscribers without an actor.tenantId on a tenant-scoped topic
|
|
255
|
+
// are refused outright; the previous shape (filter only when both
|
|
256
|
+
// tenants present) silently accepted such subscribers and let them
|
|
257
|
+
// receive every tenant's events.
|
|
258
|
+
var subscriberTenant = sOpts.actor && sOpts.actor.tenantId ? sOpts.actor.tenantId : null;
|
|
259
|
+
if (entry.tenantScope && !subscriberTenant) {
|
|
260
|
+
_safeAudit(auditImpl, "agent.event_bus.subscribe_denied", sOpts.actor, {
|
|
261
|
+
topic: name, reason: "tenant-scoped-topic-requires-actor-tenant-id",
|
|
262
|
+
});
|
|
263
|
+
throw new AgentEventBusError("agent-event-bus/subscribe-denied",
|
|
264
|
+
"subscribe: tenant-scoped topic '" + name +
|
|
265
|
+
"' requires actor.tenantId; subscribers without a tenant identity are refused");
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Wrapped handler: re-validate posture + tenant at delivery so an
|
|
269
|
+
// in-flight tamper / cross-tenant routing attempt is refused at the
|
|
270
|
+
// consumer boundary (not at the bus's trust boundary alone).
|
|
271
|
+
async function _wrappedHandler(wrapped, evMeta) {
|
|
272
|
+
if (!wrapped || typeof wrapped !== "object" || !wrapped._topic) {
|
|
273
|
+
_safeAudit(auditImpl, "agent.event_bus.delivery_dropped", sOpts.actor,
|
|
274
|
+
{ topic: name, reason: "malformed-envelope" });
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
// Tenant-scope check: subscriber's tenantId must match the
|
|
278
|
+
// publisher's tenantId from the wire envelope. If the envelope
|
|
279
|
+
// lacks _tenantId (publisher omitted), that's a tampered or
|
|
280
|
+
// malformed wire and the delivery drops.
|
|
281
|
+
if (entry.tenantScope) {
|
|
282
|
+
if (!wrapped._tenantId || wrapped._tenantId !== subscriberTenant) {
|
|
283
|
+
_safeAudit(auditImpl, "agent.event_bus.cross_tenant_drop", sOpts.actor, {
|
|
284
|
+
topic: name,
|
|
285
|
+
publisherTenant: wrapped._tenantId || null,
|
|
286
|
+
subscriberTenant: subscriberTenant,
|
|
287
|
+
reason: wrapped._tenantId ? "tenant-mismatch" : "missing-publisher-tenant",
|
|
288
|
+
});
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
// Re-validate payload against schema in case of in-flight tamper.
|
|
293
|
+
try { guardEventBusPayload.validate(wrapped.payload, entry.schema); }
|
|
294
|
+
catch (_e) {
|
|
295
|
+
_safeAudit(auditImpl, "agent.event_bus.delivery_dropped", sOpts.actor,
|
|
296
|
+
{ topic: name, reason: "payload-schema-violation" });
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
// Await handler — supports async handlers + catches their async
|
|
300
|
+
// rejections. Without await, an async handler that rejects would
|
|
301
|
+
// surface as an unhandled rejection and skip the audit emit.
|
|
302
|
+
try {
|
|
303
|
+
await handler(wrapped.payload, {
|
|
304
|
+
topic: name, publishedAt: wrapped._publishedAt,
|
|
305
|
+
source: evMeta && evMeta.source,
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
catch (e) {
|
|
309
|
+
_safeAudit(auditImpl, "agent.event_bus.handler_threw", sOpts.actor,
|
|
310
|
+
{ topic: name, message: (e && e.message) || String(e) });
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
var token = await pubsub.subscribe(name, _wrappedHandler);
|
|
314
|
+
_safeAudit(auditImpl, "agent.event_bus.subscribed", sOpts.actor, { topic: name });
|
|
315
|
+
return function unsubscribe() {
|
|
316
|
+
try {
|
|
317
|
+
if (typeof token === "function") return token();
|
|
318
|
+
if (token && typeof token.unsubscribe === "function") return token.unsubscribe();
|
|
319
|
+
} catch (_e) { /* best-effort */ }
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// ---- Audit helper ---------------------------------------------------------
|
|
324
|
+
|
|
325
|
+
function _safeAudit(auditImpl, action, actor, metadata) {
|
|
326
|
+
agentAudit.safeAudit(auditImpl, action, actor, metadata);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
module.exports = {
|
|
330
|
+
create: create,
|
|
331
|
+
AgentEventBusError: AgentEventBusError,
|
|
332
|
+
guards: {
|
|
333
|
+
topic: guardEventBusTopic,
|
|
334
|
+
payload: guardEventBusPayload,
|
|
335
|
+
},
|
|
336
|
+
};
|
package/lib/agent-idempotency.js
CHANGED
|
@@ -64,6 +64,7 @@ var { defineClass } = require("./framework-error");
|
|
|
64
64
|
var bCrypto = require("./crypto");
|
|
65
65
|
var safeJson = require("./safe-json");
|
|
66
66
|
var guardIdempotencyKey = require("./guard-idempotency-key");
|
|
67
|
+
var agentAudit = require("./agent-audit");
|
|
67
68
|
|
|
68
69
|
var audit = lazyRequire(function () { return require("./audit"); });
|
|
69
70
|
|
|
@@ -331,14 +332,7 @@ function _inMemoryBackend() {
|
|
|
331
332
|
}
|
|
332
333
|
|
|
333
334
|
function _safeAudit(auditImpl, action, actor, metadata) {
|
|
334
|
-
|
|
335
|
-
auditImpl.safeEmit({
|
|
336
|
-
action: action,
|
|
337
|
-
actor: actor ? { id: actor.id, roles: actor.roles || [] } : { id: "<system>" },
|
|
338
|
-
outcome: action.indexOf("denied") >= 0 || action.indexOf("different_args") >= 0 ? "failure" : "success",
|
|
339
|
-
metadata: metadata || {},
|
|
340
|
-
});
|
|
341
|
-
} catch (_e) { /* drop-silent */ }
|
|
335
|
+
agentAudit.safeAudit(auditImpl, action, actor, metadata);
|
|
342
336
|
}
|
|
343
337
|
|
|
344
338
|
module.exports = {
|
|
@@ -56,6 +56,7 @@ var C = require("./constants");
|
|
|
56
56
|
var { defineClass } = require("./framework-error");
|
|
57
57
|
var guardAgentRegistry = require("./guard-agent-registry");
|
|
58
58
|
var bCrypto = require("./crypto");
|
|
59
|
+
var agentAudit = require("./agent-audit");
|
|
59
60
|
|
|
60
61
|
var audit = lazyRequire(function () { return require("./audit"); });
|
|
61
62
|
var cluster = lazyRequire(function () { return require("./cluster"); });
|
|
@@ -449,14 +450,7 @@ function _checkPermission(ctx, actor, scope) {
|
|
|
449
450
|
}
|
|
450
451
|
|
|
451
452
|
function _safeAudit(ctx, action, actor, metadata) {
|
|
452
|
-
|
|
453
|
-
ctx.audit.safeEmit({
|
|
454
|
-
action: action,
|
|
455
|
-
actor: actor ? { id: actor.id, roles: actor.roles || [] } : { id: "<system>" },
|
|
456
|
-
outcome: action.indexOf("denied") >= 0 || action.indexOf("miss") >= 0 ? "failure" : "success",
|
|
457
|
-
metadata: metadata || {},
|
|
458
|
-
});
|
|
459
|
-
} catch (_e) { /* drop-silent — audit emit failures don't crash the call */ }
|
|
453
|
+
agentAudit.safeAudit(ctx.audit, action, actor, metadata);
|
|
460
454
|
}
|
|
461
455
|
|
|
462
456
|
module.exports = {
|