@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.
@@ -0,0 +1,208 @@
1
+ "use strict";
2
+ /**
3
+ * @module b.agent.postureChain
4
+ * @nav Agent
5
+ * @title Agent Posture Chain
6
+ * @order 80
7
+ *
8
+ * @intro
9
+ * Set-based compliance posture propagated across every agent
10
+ * boundary (sub-agent delegation, queue envelopes, event-bus
11
+ * payloads, saga steps). The hard rule: **target's posture set MUST
12
+ * be a SUPERSET of source's posture set.** A "downgrade" (target
13
+ * missing a regime the source requires) is refused at the boundary.
14
+ *
15
+ * Compliance regimes (HIPAA / PCI-DSS / GDPR / SOC2) protect
16
+ * DIFFERENT regulated-data classes — they're orthogonal, not a
17
+ * linear lattice. A clinic that processes payment cards operates
18
+ * under BOTH HIPAA + PCI; an EU clinic adds GDPR; an aggregator
19
+ * may add SOC2. Set semantics match how real-world regulations
20
+ * actually overlap.
21
+ *
22
+ * ```js
23
+ * var chain = b.agent.postureChain.create({});
24
+ *
25
+ * var sourceSet = ["hipaa", "pci-dss"];
26
+ * var targetSet = ["pci-dss"]; // missing hipaa
27
+ *
28
+ * chain.isSubset(targetSet, sourceSet); // false — target lacks hipaa
29
+ * chain.canDelegate(sourceSet, targetSet, "mail.fetch");
30
+ * // → false; agent.posture-chain.canDelegate-denied audit emit
31
+ * ```
32
+ *
33
+ * ## Per-module declaration
34
+ *
35
+ * Each module declares its applicable regimes via a static
36
+ * `POSTURES` export OR an `@compliance` JSDoc tag. The agent
37
+ * primitive's posture SET = union of all composed modules' declared
38
+ * regimes (operator can narrow at composition time).
39
+ *
40
+ * ## Hop trail
41
+ *
42
+ * Every cross-boundary envelope carries `{ postureSet, chainTrail,
43
+ * enteredAt, hopCount }`. Hop count caps at default 16 — defends
44
+ * infinite recursion across agent delegation. `appendHop` extends
45
+ * the trail when an envelope crosses a new boundary.
46
+ *
47
+ * @card
48
+ * Set-based compliance posture propagated across every boundary.
49
+ * target.set ⊇ source.set required; downgrade refused. Hop-trail
50
+ * tracking for audit + debugging.
51
+ */
52
+
53
+ var lazyRequire = require("./lazy-require");
54
+ var { defineClass } = require("./framework-error");
55
+ var guardPostureChain = require("./guard-posture-chain");
56
+ var agentAudit = require("./agent-audit");
57
+
58
+ var audit = lazyRequire(function () { return require("./audit"); });
59
+
60
+ var AgentPostureChainError = defineClass("AgentPostureChainError", { alwaysPermanent: true });
61
+
62
+ var BUILTIN_REGIMES = Object.freeze(["hipaa", "pci-dss", "gdpr", "soc2"]);
63
+
64
+ /**
65
+ * @primitive b.agent.postureChain.create
66
+ * @signature b.agent.postureChain.create(opts)
67
+ * @since 0.9.28
68
+ * @status stable
69
+ * @related b.agent.tenant.create, b.agent.eventBus.create
70
+ *
71
+ * Create the posture-chain facade. Returns an instance with
72
+ * `isSubset` / `union` / `canDelegate` / `declareRegime` / `validate`
73
+ * / `appendHop`.
74
+ *
75
+ * @opts
76
+ * audit: b.audit namespace, // optional
77
+ *
78
+ * @example
79
+ * var chain = b.agent.postureChain.create({});
80
+ * chain.isSubset(["pci-dss"], ["hipaa", "pci-dss"]); // → false
81
+ */
82
+ function create(opts) {
83
+ opts = opts || {};
84
+ var auditImpl = opts.audit || audit();
85
+ var declaredRegimes = Object.create(null);
86
+ for (var i = 0; i < BUILTIN_REGIMES.length; i += 1) declaredRegimes[BUILTIN_REGIMES[i]] = true;
87
+ return {
88
+ declareRegime: function (name) { return _declareRegime(declaredRegimes, name); },
89
+ isSubset: function (targetSet, sourceSet) { return _isSubset(targetSet, sourceSet); },
90
+ union: function () { return _union.apply(null, arguments); },
91
+ canDelegate: function (sourceSet, targetSet, method) { return _canDelegate(sourceSet, targetSet, method, auditImpl); },
92
+ appendHop: function (envelope, hopName) { return _appendHop(envelope, hopName); },
93
+ validate: function (envelope, agentPostureSet) { return _validate(envelope, agentPostureSet, auditImpl); },
94
+ REGIMES: Object.freeze(Object.keys(declaredRegimes)),
95
+ AgentPostureChainError: AgentPostureChainError,
96
+ _declaredRegimes: declaredRegimes,
97
+ };
98
+ }
99
+
100
+ function _declareRegime(declaredRegimes, name) {
101
+ if (typeof name !== "string" || name.length === 0) {
102
+ throw new AgentPostureChainError("agent-posture-chain/bad-regime",
103
+ "declareRegime: name must be a non-empty string");
104
+ }
105
+ if (declaredRegimes[name]) {
106
+ throw new AgentPostureChainError("agent-posture-chain/duplicate-regime",
107
+ "declareRegime: '" + name + "' already declared");
108
+ }
109
+ declaredRegimes[name] = true;
110
+ }
111
+
112
+ function _isSubset(targetSet, sourceSet) {
113
+ if (!Array.isArray(targetSet) || !Array.isArray(sourceSet)) return false;
114
+ if (sourceSet.length === 0) return true; // empty source ⊆ any target
115
+ var targetIdx = Object.create(null);
116
+ for (var i = 0; i < targetSet.length; i += 1) targetIdx[targetSet[i]] = true;
117
+ for (var j = 0; j < sourceSet.length; j += 1) {
118
+ if (!targetIdx[sourceSet[j]]) return false;
119
+ }
120
+ return true;
121
+ }
122
+
123
+ function _union() {
124
+ var seen = Object.create(null);
125
+ var out = [];
126
+ for (var a = 0; a < arguments.length; a += 1) {
127
+ var set = arguments[a];
128
+ if (!Array.isArray(set)) continue;
129
+ for (var i = 0; i < set.length; i += 1) {
130
+ if (!seen[set[i]]) {
131
+ seen[set[i]] = true;
132
+ out.push(set[i]);
133
+ }
134
+ }
135
+ }
136
+ return out;
137
+ }
138
+
139
+ function _canDelegate(sourceSet, targetSet, method, auditImpl) {
140
+ if (_isSubset(targetSet, sourceSet)) return true;
141
+ agentAudit.safeAudit(auditImpl, "agent.posture_chain.delegate_denied", null, {
142
+ method: method, sourceSet: sourceSet, targetSet: targetSet,
143
+ missing: _missing(targetSet, sourceSet),
144
+ });
145
+ return false;
146
+ }
147
+
148
+ function _missing(targetSet, sourceSet) {
149
+ var idx = Object.create(null);
150
+ if (Array.isArray(targetSet)) for (var i = 0; i < targetSet.length; i += 1) idx[targetSet[i]] = true;
151
+ var out = [];
152
+ if (Array.isArray(sourceSet)) for (var j = 0; j < sourceSet.length; j += 1) {
153
+ if (!idx[sourceSet[j]]) out.push(sourceSet[j]);
154
+ }
155
+ return out;
156
+ }
157
+
158
+ function _appendHop(envelope, hopName) {
159
+ if (!envelope || typeof envelope !== "object") {
160
+ throw new AgentPostureChainError("agent-posture-chain/bad-envelope",
161
+ "appendHop: envelope required");
162
+ }
163
+ if (typeof hopName !== "string" || hopName.length === 0) {
164
+ throw new AgentPostureChainError("agent-posture-chain/bad-hop-name",
165
+ "appendHop: hopName must be a non-empty string");
166
+ }
167
+ var trail = Array.isArray(envelope.chainTrail) ? envelope.chainTrail.slice() : [];
168
+ trail.push(hopName);
169
+ var enteredAt = Array.isArray(envelope.enteredAt) ? envelope.enteredAt.slice() : [];
170
+ enteredAt.push(Date.now());
171
+ var newEnvelope = Object.assign({}, envelope, {
172
+ chainTrail: trail,
173
+ enteredAt: enteredAt,
174
+ hopCount: trail.length,
175
+ });
176
+ guardPostureChain.validate(newEnvelope);
177
+ return newEnvelope;
178
+ }
179
+
180
+ function _validate(envelope, agentPostureSet, auditImpl) {
181
+ guardPostureChain.validate(envelope);
182
+ // The source (envelope) carries a postureSet; the target (the agent
183
+ // we're entering) declares its own posture set. Target must be a
184
+ // superset of source — i.e., the agent covers every regime the
185
+ // calling context requires.
186
+ if (Array.isArray(agentPostureSet)) {
187
+ if (!_isSubset(agentPostureSet, envelope.postureSet)) {
188
+ var missing = _missing(agentPostureSet, envelope.postureSet);
189
+ agentAudit.safeAudit(auditImpl, "agent.posture_chain.downgrade_refused", null, {
190
+ sourceSet: envelope.postureSet, targetSet: agentPostureSet, missing: missing,
191
+ chainTrail: envelope.chainTrail,
192
+ });
193
+ throw new AgentPostureChainError("agent-posture-chain/downgrade-refused",
194
+ "validate: agent posture-set " + JSON.stringify(agentPostureSet) +
195
+ " missing regimes required by envelope: " + JSON.stringify(missing));
196
+ }
197
+ }
198
+ return envelope;
199
+ }
200
+
201
+ module.exports = {
202
+ create: create,
203
+ BUILTIN_REGIMES: BUILTIN_REGIMES,
204
+ AgentPostureChainError: AgentPostureChainError,
205
+ guards: {
206
+ chain: guardPostureChain,
207
+ },
208
+ };
@@ -0,0 +1,191 @@
1
+ "use strict";
2
+ /**
3
+ * @module b.agent.saga
4
+ * @nav Agent
5
+ * @title Agent Saga
6
+ * @order 75
7
+ *
8
+ * @intro
9
+ * Multi-step coordination with compensation cascade. When a saga's
10
+ * step fails mid-way, the framework fires every previously-completed
11
+ * step's `compensate` in reverse order so the operator-side state
12
+ * doesn't end up half-written.
13
+ *
14
+ * Substrate for v0.9.34 submission (DKIM-sign → ARC-sign → outbox-
15
+ * enqueue → SMTP-deliver → store-move-to-Sent), regulated export,
16
+ * journal compaction, every future multi-step write.
17
+ *
18
+ * ```js
19
+ * var sendSaga = b.agent.saga.create({
20
+ * name: "mail.send",
21
+ * audit: b.audit,
22
+ * steps: [
23
+ * {
24
+ * name: "dkim-sign",
25
+ * run: async function (ctx, state) { state.signed = sign(state.message); },
26
+ * compensate: async function (ctx, state) { /* sign is pure, nothing to undo *\/ },
27
+ * },
28
+ * {
29
+ * name: "store-draft",
30
+ * run: async function (ctx, state) { state.draftId = store.append("Drafts", state.signed); },
31
+ * compensate: async function (ctx, state) { if (state.draftId) store.delete(state.draftId); },
32
+ * },
33
+ * {
34
+ * name: "smtp-deliver",
35
+ * run: async function (ctx, state) { await smtp.deliver(state.signed); },
36
+ * compensate: async function (ctx, state) { /* idempotent: SMTP delivery doesn't have a recall *\/ },
37
+ * },
38
+ * ],
39
+ * });
40
+ *
41
+ * var result = await sendSaga.run({ store, smtp }, { message: bytes });
42
+ * ```
43
+ *
44
+ * ## Compensation order
45
+ *
46
+ * If step `i` throws, the framework calls `step[i-1].compensate`,
47
+ * `step[i-2].compensate`, ..., `step[0].compensate` in reverse
48
+ * order. Each compensate receives the SAME `state` object that
49
+ * the corresponding `run` mutated — operator inspects what got
50
+ * written and undoes it.
51
+ *
52
+ * Compensations that throw emit `agent.saga.compensation_failed`
53
+ * audit at CRITICAL severity and halt further compensations
54
+ * (operator alert; manual intervention needed). The saga returns
55
+ * `{ status: "failed", failedStep, lastCompensationError }`.
56
+ *
57
+ * ## No saga-level retry
58
+ *
59
+ * Per the substrate playbook decision (operator-confirmed
60
+ * 2026-05-14): saga's value-add is compensation, not retry. If a
61
+ * step needs retry-with-backoff, the operator wraps `step.run`
62
+ * with `b.retry` inside the step body. With v0.9.22 idempotency
63
+ * available, internal retry inside step.run is side-effect-safe.
64
+ *
65
+ * @card
66
+ * Multi-step coordination with compensation cascade. Reverse-order
67
+ * compensations on step failure. No saga-level retry — step.run
68
+ * owns its own retry semantics via b.retry + v0.9.22 idempotency.
69
+ */
70
+
71
+ var lazyRequire = require("./lazy-require");
72
+ var { defineClass } = require("./framework-error");
73
+ var guardSagaConfig = require("./guard-saga-config");
74
+ var bCrypto = require("./crypto");
75
+ var agentAudit = require("./agent-audit");
76
+
77
+ var audit = lazyRequire(function () { return require("./audit"); });
78
+
79
+ var AgentSagaError = defineClass("AgentSagaError", { alwaysPermanent: true });
80
+
81
+ var SAGA_ID_RAND_BYTES = 8; // allow:raw-byte-literal — saga-id random-suffix byte length
82
+
83
+ /**
84
+ * @primitive b.agent.saga.create
85
+ * @signature b.agent.saga.create(config)
86
+ * @since 0.9.27
87
+ * @status stable
88
+ * @related b.agent.idempotency.create, b.outbox.enqueue
89
+ *
90
+ * Create a saga definition. Returns an instance with `run(ctx,
91
+ * initialState, opts) → Promise<finalState>`.
92
+ *
93
+ * @opts
94
+ * name: string, // required (audit label)
95
+ * steps: Array<{ name, run, compensate? }>, // required, non-empty
96
+ * audit: b.audit namespace, // optional
97
+ *
98
+ * @example
99
+ * var saga = b.agent.saga.create({
100
+ * name: "my.workflow",
101
+ * steps: [{ name: "step1", run: async (ctx, s) => { s.x = 1; } }],
102
+ * });
103
+ * var final = await saga.run({}, {});
104
+ */
105
+ function create(config) {
106
+ guardSagaConfig.validate(config);
107
+ var auditImpl = config.audit || audit();
108
+ return {
109
+ run: function (ctx, initialState, opts) { return _run(config, auditImpl, ctx, initialState, opts || {}); },
110
+ name: config.name,
111
+ stepCount: config.steps.length,
112
+ AgentSagaError: AgentSagaError,
113
+ };
114
+ }
115
+
116
+ async function _run(config, auditImpl, ctx, initialState, opts) {
117
+ var sagaId = opts.sagaId || "saga-" + bCrypto.generateToken(SAGA_ID_RAND_BYTES);
118
+ var state = Object.assign({}, initialState || {});
119
+ var completedSteps = [];
120
+
121
+ agentAudit.safeAudit(auditImpl, "agent.saga.started", opts.actor, {
122
+ sagaId: sagaId, name: config.name, stepCount: config.steps.length,
123
+ });
124
+
125
+ for (var i = 0; i < config.steps.length; i += 1) {
126
+ var step = config.steps[i];
127
+ try {
128
+ agentAudit.safeAudit(auditImpl, "agent.saga.step_started", opts.actor, {
129
+ sagaId: sagaId, name: config.name, stepName: step.name, stepIndex: i,
130
+ });
131
+ await step.run(ctx, state);
132
+ completedSteps.push(step);
133
+ agentAudit.safeAudit(auditImpl, "agent.saga.step_completed", opts.actor, {
134
+ sagaId: sagaId, name: config.name, stepName: step.name, stepIndex: i,
135
+ });
136
+ } catch (stepErr) {
137
+ // Step failed — compensate in reverse over already-completed steps.
138
+ agentAudit.safeAudit(auditImpl, "agent.saga.step_failed", opts.actor, {
139
+ sagaId: sagaId, name: config.name, stepName: step.name, stepIndex: i,
140
+ message: (stepErr && stepErr.message) || String(stepErr),
141
+ });
142
+ var compensationError = null;
143
+ for (var c = completedSteps.length - 1; c >= 0; c -= 1) {
144
+ var compStep = completedSteps[c];
145
+ if (typeof compStep.compensate !== "function") continue;
146
+ try {
147
+ agentAudit.safeAudit(auditImpl, "agent.saga.compensation_started", opts.actor, {
148
+ sagaId: sagaId, name: config.name, stepName: compStep.name,
149
+ });
150
+ await compStep.compensate(ctx, state);
151
+ agentAudit.safeAudit(auditImpl, "agent.saga.compensation_completed", opts.actor, {
152
+ sagaId: sagaId, name: config.name, stepName: compStep.name,
153
+ });
154
+ } catch (compErr) {
155
+ // CRITICAL: compensation failed — operator intervention needed.
156
+ // Halt further compensations; record what failed so audit
157
+ // pipeline can alert.
158
+ compensationError = compErr;
159
+ agentAudit.safeAudit(auditImpl, "agent.saga.compensation_failed", opts.actor, {
160
+ sagaId: sagaId, name: config.name, stepName: compStep.name,
161
+ message: (compErr && compErr.message) || String(compErr),
162
+ });
163
+ break;
164
+ }
165
+ }
166
+ agentAudit.safeAudit(auditImpl, "agent.saga.failed", opts.actor, {
167
+ sagaId: sagaId, name: config.name, failedStep: step.name,
168
+ compensationFailed: compensationError !== null,
169
+ });
170
+ throw new AgentSagaError("agent-saga/failed",
171
+ "saga '" + config.name + "' failed at step '" + step.name + "': " +
172
+ ((stepErr && stepErr.message) || String(stepErr)) +
173
+ (compensationError ? " — and compensation '" + completedSteps[completedSteps.length - 1].name +
174
+ "' subsequently failed: " +
175
+ (compensationError.message || String(compensationError))
176
+ : ""));
177
+ }
178
+ }
179
+ agentAudit.safeAudit(auditImpl, "agent.saga.completed", opts.actor, {
180
+ sagaId: sagaId, name: config.name, stepCount: config.steps.length,
181
+ });
182
+ return { status: "completed", sagaId: sagaId, state: state };
183
+ }
184
+
185
+ module.exports = {
186
+ create: create,
187
+ AgentSagaError: AgentSagaError,
188
+ guards: {
189
+ config: guardSagaConfig,
190
+ },
191
+ };