@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,201 @@
1
+ "use strict";
2
+ /**
3
+ * @module b.guardPostureChain
4
+ * @nav Guards
5
+ * @title Guard Posture Chain
6
+ * @order 442
7
+ *
8
+ * @intro
9
+ * Validates cross-boundary posture-chain envelopes. The envelope
10
+ * carries the set of compliance regimes the call is operating
11
+ * under (`postureSet: ["hipaa", "pci-dss"]`), the hop trail
12
+ * (`chainTrail: ["api-gateway", "mail-agent", "audit"]`), per-hop
13
+ * timestamps, and hop count. Refuses:
14
+ *
15
+ * - oversized trail (default hop cap = 16; defends infinite
16
+ * recursion across agent delegation)
17
+ * - non-ASCII hop names (operator-greppable in audit logs)
18
+ * - duplicate hop in trail (recursion guard)
19
+ * - missing or non-monotonic enteredAt timestamps
20
+ * - posture set contains non-string entries OR duplicates
21
+ *
22
+ * @card
23
+ * Validates cross-boundary posture envelopes. Hop trail caps,
24
+ * ASCII-only hop names, monotonic timestamps, set-shape
25
+ * posture-regime entries.
26
+ */
27
+
28
+ var { defineClass } = require("./framework-error");
29
+
30
+ var GuardPostureChainError = defineClass("GuardPostureChainError", { alwaysPermanent: true });
31
+
32
+ var DEFAULT_PROFILE = "strict";
33
+
34
+ var PROFILES = Object.freeze({
35
+ strict: { maxHops: 16, maxHopBytes: 64, maxRegimes: 8 }, // allow:raw-byte-literal
36
+ balanced: { maxHops: 32, maxHopBytes: 128, maxRegimes: 16 }, // allow:raw-byte-literal
37
+ permissive: { maxHops: 128, maxHopBytes: 256, maxRegimes: 64 }, // allow:raw-byte-literal
38
+ });
39
+
40
+ var COMPLIANCE_POSTURES = Object.freeze({
41
+ hipaa: "strict",
42
+ "pci-dss": "strict",
43
+ gdpr: "strict",
44
+ soc2: "strict",
45
+ });
46
+
47
+ /**
48
+ * @primitive b.guardPostureChain.validate
49
+ * @signature b.guardPostureChain.validate(envelope, opts?)
50
+ * @since 0.9.28
51
+ * @status stable
52
+ * @related b.agent.postureChain.create
53
+ *
54
+ * Validate a posture-chain envelope. Returns the envelope on success;
55
+ * throws on refusal.
56
+ *
57
+ * @opts
58
+ * profile: "strict" | "balanced" | "permissive",
59
+ * posture: "hipaa" | "pci-dss" | "gdpr" | "soc2",
60
+ *
61
+ * @example
62
+ * b.guardPostureChain.validate({
63
+ * postureSet: ["hipaa"],
64
+ * chainTrail: ["api-gateway", "mail-agent"],
65
+ * enteredAt: [1700000000000, 1700000000100],
66
+ * hopCount: 2,
67
+ * });
68
+ */
69
+ function validate(envelope, opts) {
70
+ opts = opts || {};
71
+ var profile = PROFILES[_resolveProfile(opts)];
72
+ if (!envelope || typeof envelope !== "object") {
73
+ throw new GuardPostureChainError("posture-chain/bad-input",
74
+ "guardPostureChain.validate: envelope required");
75
+ }
76
+ // postureSet — array of distinct ASCII regime names
77
+ if (!Array.isArray(envelope.postureSet)) {
78
+ throw new GuardPostureChainError("posture-chain/bad-posture-set",
79
+ "guardPostureChain.validate: postureSet must be an array");
80
+ }
81
+ if (envelope.postureSet.length > profile.maxRegimes) {
82
+ throw new GuardPostureChainError("posture-chain/too-many-regimes",
83
+ "guardPostureChain.validate: " + envelope.postureSet.length +
84
+ " regimes exceeds maxRegimes=" + profile.maxRegimes);
85
+ }
86
+ var regSeen = Object.create(null);
87
+ for (var r = 0; r < envelope.postureSet.length; r += 1) {
88
+ var regime = envelope.postureSet[r];
89
+ if (typeof regime !== "string" || regime.length === 0) {
90
+ throw new GuardPostureChainError("posture-chain/bad-regime",
91
+ "guardPostureChain.validate: postureSet[" + r + "] must be a non-empty string");
92
+ }
93
+ if (regSeen[regime]) {
94
+ throw new GuardPostureChainError("posture-chain/duplicate-regime",
95
+ "guardPostureChain.validate: duplicate regime '" + regime + "' in postureSet");
96
+ }
97
+ regSeen[regime] = true;
98
+ }
99
+ // chainTrail — bounded hop list
100
+ if (!Array.isArray(envelope.chainTrail)) {
101
+ throw new GuardPostureChainError("posture-chain/bad-trail",
102
+ "guardPostureChain.validate: chainTrail must be an array");
103
+ }
104
+ if (envelope.chainTrail.length > profile.maxHops) {
105
+ throw new GuardPostureChainError("posture-chain/hop-limit-exceeded",
106
+ "guardPostureChain.validate: " + envelope.chainTrail.length +
107
+ " hops exceeds maxHops=" + profile.maxHops);
108
+ }
109
+ var hopSeen = Object.create(null);
110
+ for (var h = 0; h < envelope.chainTrail.length; h += 1) {
111
+ var hop = envelope.chainTrail[h];
112
+ if (typeof hop !== "string" || hop.length === 0) {
113
+ throw new GuardPostureChainError("posture-chain/bad-hop",
114
+ "guardPostureChain.validate: chainTrail[" + h + "] must be a non-empty string");
115
+ }
116
+ if (Buffer.byteLength(hop, "utf8") > profile.maxHopBytes) {
117
+ throw new GuardPostureChainError("posture-chain/hop-name-too-long",
118
+ "guardPostureChain.validate: chainTrail[" + h + "] exceeds maxHopBytes=" + profile.maxHopBytes);
119
+ }
120
+ for (var hi = 0; hi < hop.length; hi += 1) {
121
+ var hc = hop.charCodeAt(hi);
122
+ if (hc > 0x7F) { // allow:raw-byte-literal — ASCII-only
123
+ throw new GuardPostureChainError("posture-chain/non-ascii-hop",
124
+ "guardPostureChain.validate: chainTrail[" + h + "] has non-ASCII codepoint");
125
+ }
126
+ if (hc < 0x20 || hc === 0x7F) { // allow:raw-byte-literal — C0/DEL
127
+ throw new GuardPostureChainError("posture-chain/bad-hop-char",
128
+ "guardPostureChain.validate: chainTrail[" + h + "] has forbidden char 0x" + hc.toString(16));
129
+ }
130
+ }
131
+ if (hopSeen[hop]) {
132
+ throw new GuardPostureChainError("posture-chain/duplicate-hop",
133
+ "guardPostureChain.validate: duplicate hop '" + hop + "' in chainTrail");
134
+ }
135
+ hopSeen[hop] = true;
136
+ }
137
+ // enteredAt timestamps (optional but if present must match length + be monotonic)
138
+ if (typeof envelope.enteredAt !== "undefined") {
139
+ if (!Array.isArray(envelope.enteredAt)) {
140
+ throw new GuardPostureChainError("posture-chain/bad-entered-at",
141
+ "guardPostureChain.validate: enteredAt must be an array of timestamps");
142
+ }
143
+ if (envelope.enteredAt.length !== envelope.chainTrail.length) {
144
+ throw new GuardPostureChainError("posture-chain/entered-at-length-mismatch",
145
+ "guardPostureChain.validate: enteredAt length must equal chainTrail length");
146
+ }
147
+ var prevT = -Infinity;
148
+ for (var t = 0; t < envelope.enteredAt.length; t += 1) {
149
+ var ts = envelope.enteredAt[t];
150
+ if (typeof ts !== "number" || !isFinite(ts) || ts < 0) {
151
+ throw new GuardPostureChainError("posture-chain/bad-timestamp",
152
+ "guardPostureChain.validate: enteredAt[" + t + "] must be a finite non-negative number");
153
+ }
154
+ if (ts < prevT) {
155
+ throw new GuardPostureChainError("posture-chain/non-monotonic-timestamps",
156
+ "guardPostureChain.validate: enteredAt[" + t + "] < enteredAt[" + (t - 1) + "]");
157
+ }
158
+ prevT = ts;
159
+ }
160
+ }
161
+ return envelope;
162
+ }
163
+
164
+ /**
165
+ * @primitive b.guardPostureChain.compliancePosture
166
+ * @signature b.guardPostureChain.compliancePosture(posture)
167
+ * @since 0.9.28
168
+ * @status stable
169
+ *
170
+ * Return the effective profile for a given compliance posture name.
171
+ * Returns `null` for unknown posture names so operator typos surface
172
+ * here instead of silently falling through to the default profile.
173
+ *
174
+ * @example
175
+ * b.guardPostureChain.compliancePosture("hipaa"); // → "strict"
176
+ */
177
+ function compliancePosture(posture) {
178
+ return COMPLIANCE_POSTURES[posture] || null;
179
+ }
180
+
181
+ function _resolveProfile(opts) {
182
+ if (opts.posture && COMPLIANCE_POSTURES[opts.posture]) {
183
+ return COMPLIANCE_POSTURES[opts.posture];
184
+ }
185
+ var p = opts.profile || DEFAULT_PROFILE;
186
+ if (!PROFILES[p]) {
187
+ throw new GuardPostureChainError("posture-chain/bad-profile",
188
+ "guardPostureChain: unknown profile '" + p + "'");
189
+ }
190
+ return p;
191
+ }
192
+
193
+ module.exports = {
194
+ validate: validate,
195
+ compliancePosture: compliancePosture,
196
+ PROFILES: PROFILES,
197
+ COMPLIANCE_POSTURES: COMPLIANCE_POSTURES,
198
+ GuardPostureChainError: GuardPostureChainError,
199
+ NAME: "postureChain",
200
+ KIND: "posture-chain",
201
+ };
@@ -0,0 +1,157 @@
1
+ "use strict";
2
+ /**
3
+ * @module b.guardSagaConfig
4
+ * @nav Guards
5
+ * @title Guard Saga Config
6
+ * @order 441
7
+ *
8
+ * @intro
9
+ * Saga-creation config validator. Refuses empty steps array,
10
+ * duplicate step names, non-ASCII saga name, non-async-function
11
+ * run/compensate fields, oversized step count.
12
+ *
13
+ * @card
14
+ * Validates `b.agent.saga.create()` opts. Step-list shape, name
15
+ * uniqueness, run/compensate function checks.
16
+ */
17
+
18
+ var { defineClass } = require("./framework-error");
19
+
20
+ var GuardSagaConfigError = defineClass("GuardSagaConfigError", { alwaysPermanent: true });
21
+
22
+ var DEFAULT_PROFILE = "strict";
23
+
24
+ var PROFILES = Object.freeze({
25
+ strict: { maxSteps: 32, maxNameBytes: 64 }, // allow:raw-byte-literal
26
+ balanced: { maxSteps: 128, maxNameBytes: 128 }, // allow:raw-byte-literal
27
+ permissive: { maxSteps: 512, maxNameBytes: 256 }, // allow:raw-byte-literal
28
+ });
29
+
30
+ var COMPLIANCE_POSTURES = Object.freeze({
31
+ hipaa: "strict",
32
+ "pci-dss": "strict",
33
+ gdpr: "strict",
34
+ soc2: "strict",
35
+ });
36
+
37
+ /**
38
+ * @primitive b.guardSagaConfig.validate
39
+ * @signature b.guardSagaConfig.validate(config, opts?)
40
+ * @since 0.9.27
41
+ * @status stable
42
+ * @related b.agent.saga.create
43
+ *
44
+ * Validate saga config. Returns config on success; throws on refusal.
45
+ *
46
+ * @opts
47
+ * profile: "strict" | "balanced" | "permissive",
48
+ * posture: "hipaa" | "pci-dss" | "gdpr" | "soc2",
49
+ *
50
+ * @example
51
+ * b.guardSagaConfig.validate({
52
+ * name: "mail.send",
53
+ * steps: [
54
+ * { name: "sign", run: async () => {}, compensate: async () => {} },
55
+ * ],
56
+ * });
57
+ */
58
+ function validate(config, opts) {
59
+ opts = opts || {};
60
+ var profile = PROFILES[_resolveProfile(opts)];
61
+ if (!config || typeof config !== "object") {
62
+ throw new GuardSagaConfigError("saga-config/bad-input",
63
+ "guardSagaConfig.validate: config required");
64
+ }
65
+ if (typeof config.name !== "string" || config.name.length === 0) {
66
+ throw new GuardSagaConfigError("saga-config/bad-name",
67
+ "guardSagaConfig.validate: name required");
68
+ }
69
+ if (Buffer.byteLength(config.name, "utf8") > profile.maxNameBytes) {
70
+ throw new GuardSagaConfigError("saga-config/name-too-long",
71
+ "guardSagaConfig.validate: name exceeds maxNameBytes=" + profile.maxNameBytes);
72
+ }
73
+ for (var i = 0; i < config.name.length; i += 1) {
74
+ var c = config.name.charCodeAt(i);
75
+ if (c > 0x7F) { // allow:raw-byte-literal — ASCII-only
76
+ throw new GuardSagaConfigError("saga-config/non-ascii-name",
77
+ "guardSagaConfig.validate: name has non-ASCII codepoint at offset " + i);
78
+ }
79
+ if (c < 0x20 || c === 0x7F) { // allow:raw-byte-literal — C0/DEL
80
+ throw new GuardSagaConfigError("saga-config/bad-name-char",
81
+ "guardSagaConfig.validate: name has forbidden char 0x" + c.toString(16));
82
+ }
83
+ }
84
+ if (!Array.isArray(config.steps) || config.steps.length === 0) {
85
+ throw new GuardSagaConfigError("saga-config/no-steps",
86
+ "guardSagaConfig.validate: steps must be a non-empty array");
87
+ }
88
+ if (config.steps.length > profile.maxSteps) {
89
+ throw new GuardSagaConfigError("saga-config/too-many-steps",
90
+ "guardSagaConfig.validate: " + config.steps.length + " steps exceeds " + profile.maxSteps);
91
+ }
92
+ var seenNames = Object.create(null);
93
+ for (var s = 0; s < config.steps.length; s += 1) {
94
+ var step = config.steps[s];
95
+ if (!step || typeof step !== "object") {
96
+ throw new GuardSagaConfigError("saga-config/bad-step",
97
+ "guardSagaConfig.validate: steps[" + s + "] must be an object");
98
+ }
99
+ if (typeof step.name !== "string" || step.name.length === 0) {
100
+ throw new GuardSagaConfigError("saga-config/bad-step-name",
101
+ "guardSagaConfig.validate: steps[" + s + "].name required");
102
+ }
103
+ if (seenNames[step.name]) {
104
+ throw new GuardSagaConfigError("saga-config/duplicate-step-name",
105
+ "guardSagaConfig.validate: duplicate step name '" + step.name + "'");
106
+ }
107
+ seenNames[step.name] = true;
108
+ if (typeof step.run !== "function") {
109
+ throw new GuardSagaConfigError("saga-config/bad-step-run",
110
+ "guardSagaConfig.validate: steps[" + s + "].run must be a function");
111
+ }
112
+ if (typeof step.compensate !== "undefined" && typeof step.compensate !== "function") {
113
+ throw new GuardSagaConfigError("saga-config/bad-step-compensate",
114
+ "guardSagaConfig.validate: steps[" + s + "].compensate must be a function (or omitted)");
115
+ }
116
+ }
117
+ return config;
118
+ }
119
+
120
+ /**
121
+ * @primitive b.guardSagaConfig.compliancePosture
122
+ * @signature b.guardSagaConfig.compliancePosture(posture)
123
+ * @since 0.9.27
124
+ * @status stable
125
+ *
126
+ * Return the effective profile for a given compliance posture name.
127
+ * Returns `null` for unknown posture names so operator typos surface
128
+ * here instead of silently falling through to the default profile.
129
+ *
130
+ * @example
131
+ * b.guardSagaConfig.compliancePosture("hipaa"); // → "strict"
132
+ */
133
+ function compliancePosture(posture) {
134
+ return COMPLIANCE_POSTURES[posture] || null;
135
+ }
136
+
137
+ function _resolveProfile(opts) {
138
+ if (opts.posture && COMPLIANCE_POSTURES[opts.posture]) {
139
+ return COMPLIANCE_POSTURES[opts.posture];
140
+ }
141
+ var p = opts.profile || DEFAULT_PROFILE;
142
+ if (!PROFILES[p]) {
143
+ throw new GuardSagaConfigError("saga-config/bad-profile",
144
+ "guardSagaConfig: unknown profile '" + p + "'");
145
+ }
146
+ return p;
147
+ }
148
+
149
+ module.exports = {
150
+ validate: validate,
151
+ compliancePosture: compliancePosture,
152
+ PROFILES: PROFILES,
153
+ COMPLIANCE_POSTURES: COMPLIANCE_POSTURES,
154
+ GuardSagaConfigError: GuardSagaConfigError,
155
+ NAME: "sagaConfig",
156
+ KIND: "saga-config",
157
+ };