@blamejs/core 0.9.24 → 0.9.28

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
+ };
@@ -0,0 +1,138 @@
1
+ "use strict";
2
+ /**
3
+ * @module b.guardTenantId
4
+ * @nav Guards
5
+ * @title Guard Tenant Id
6
+ * @order 440
7
+ *
8
+ * @intro
9
+ * Tenant-id shape validator. Tenant ids surface in audit log lines,
10
+ * sealed registry rows, derived-key context labels, and routing
11
+ * keys — they have to be ASCII-greppable across the whole framework
12
+ * stack. Refuses:
13
+ *
14
+ * - non-ASCII (NFC + ASCII-only)
15
+ * - path-traversal shapes (`..` / `/` / `\` / NUL / C0 / DEL)
16
+ * - oversized (default 64 bytes)
17
+ * - reserved `ROOT` / `FRAMEWORK` / `*` / empty
18
+ * - leading `.` (hidden-folder shape)
19
+ *
20
+ * @card
21
+ * Validates tenant-id strings. ASCII-only, bounded, no path-
22
+ * traversal, no reserved names.
23
+ */
24
+
25
+ var { defineClass } = require("./framework-error");
26
+
27
+ var GuardTenantIdError = defineClass("GuardTenantIdError", { alwaysPermanent: true });
28
+
29
+ var DEFAULT_PROFILE = "strict";
30
+
31
+ var PROFILES = Object.freeze({
32
+ strict: { maxBytes: 64 }, // allow:raw-byte-literal
33
+ balanced: { maxBytes: 128 }, // allow:raw-byte-literal
34
+ permissive: { maxBytes: 512 }, // allow:raw-byte-literal
35
+ });
36
+
37
+ var COMPLIANCE_POSTURES = Object.freeze({
38
+ hipaa: "strict",
39
+ "pci-dss": "strict",
40
+ gdpr: "strict",
41
+ soc2: "strict",
42
+ });
43
+
44
+ var RESERVED = Object.freeze({ "ROOT": true, "FRAMEWORK": true, "*": true });
45
+
46
+ /**
47
+ * @primitive b.guardTenantId.validate
48
+ * @signature b.guardTenantId.validate(tenantId, opts?)
49
+ * @since 0.9.26
50
+ * @status stable
51
+ * @related b.agent.tenant.create
52
+ *
53
+ * Validate a tenant-id string. Returns the id on success; throws
54
+ * `GuardTenantIdError` on refusal.
55
+ *
56
+ * @opts
57
+ * profile: "strict" | "balanced" | "permissive",
58
+ * posture: "hipaa" | "pci-dss" | "gdpr" | "soc2",
59
+ *
60
+ * @example
61
+ * b.guardTenantId.validate("acme-clinic");
62
+ */
63
+ function validate(tenantId, opts) {
64
+ opts = opts || {};
65
+ var profile = PROFILES[_resolveProfile(opts)];
66
+ if (typeof tenantId !== "string" || tenantId.length === 0) {
67
+ throw new GuardTenantIdError("tenant-id/bad-input",
68
+ "guardTenantId.validate: tenantId must be a non-empty string");
69
+ }
70
+ if (Buffer.byteLength(tenantId, "utf8") > profile.maxBytes) {
71
+ throw new GuardTenantIdError("tenant-id/oversize",
72
+ "guardTenantId.validate: tenantId exceeds maxBytes=" + profile.maxBytes);
73
+ }
74
+ if (RESERVED[tenantId]) {
75
+ throw new GuardTenantIdError("tenant-id/reserved",
76
+ "guardTenantId.validate: tenantId '" + tenantId + "' is framework-reserved");
77
+ }
78
+ if (tenantId.charAt(0) === ".") {
79
+ throw new GuardTenantIdError("tenant-id/hidden",
80
+ "guardTenantId.validate: tenantId cannot start with '.'");
81
+ }
82
+ if (tenantId.indexOf("..") >= 0) {
83
+ throw new GuardTenantIdError("tenant-id/path-traversal",
84
+ "guardTenantId.validate: tenantId contains '..'");
85
+ }
86
+ for (var i = 0; i < tenantId.length; i += 1) {
87
+ var c = tenantId.charCodeAt(i);
88
+ if (c > 0x7F) { // allow:raw-byte-literal — ASCII-only cap
89
+ throw new GuardTenantIdError("tenant-id/non-ascii",
90
+ "guardTenantId.validate: non-ASCII codepoint at offset " + i);
91
+ }
92
+ if (c < 0x20 || c === 0x7F || c === 0x2F || c === 0x5C) { // allow:raw-byte-literal — C0/DEL/slash/backslash
93
+ throw new GuardTenantIdError("tenant-id/bad-char",
94
+ "guardTenantId.validate: forbidden char 0x" + c.toString(16) + " at offset " + i);
95
+ }
96
+ }
97
+ return tenantId;
98
+ }
99
+
100
+ /**
101
+ * @primitive b.guardTenantId.compliancePosture
102
+ * @signature b.guardTenantId.compliancePosture(posture)
103
+ * @since 0.9.26
104
+ * @status stable
105
+ *
106
+ * Return the effective profile for a given compliance posture name.
107
+ * Returns `null` for unknown posture names so operator typos surface
108
+ * here instead of silently falling through to the default profile.
109
+ *
110
+ * @example
111
+ * b.guardTenantId.compliancePosture("hipaa"); // → "strict"
112
+ */
113
+ function compliancePosture(posture) {
114
+ return COMPLIANCE_POSTURES[posture] || null;
115
+ }
116
+
117
+ function _resolveProfile(opts) {
118
+ if (opts.posture && COMPLIANCE_POSTURES[opts.posture]) {
119
+ return COMPLIANCE_POSTURES[opts.posture];
120
+ }
121
+ var p = opts.profile || DEFAULT_PROFILE;
122
+ if (!PROFILES[p]) {
123
+ throw new GuardTenantIdError("tenant-id/bad-profile",
124
+ "guardTenantId: unknown profile '" + p + "'");
125
+ }
126
+ return p;
127
+ }
128
+
129
+ module.exports = {
130
+ validate: validate,
131
+ compliancePosture: compliancePosture,
132
+ PROFILES: PROFILES,
133
+ COMPLIANCE_POSTURES: COMPLIANCE_POSTURES,
134
+ RESERVED: RESERVED,
135
+ GuardTenantIdError: GuardTenantIdError,
136
+ NAME: "tenantId",
137
+ KIND: "tenant-id",
138
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/core",
3
- "version": "0.9.24",
3
+ "version": "0.9.28",
4
4
  "description": "The Node framework that owns its stack.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "blamejs contributors",
package/sbom.cdx.json CHANGED
@@ -2,10 +2,10 @@
2
2
  "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
3
3
  "bomFormat": "CycloneDX",
4
4
  "specVersion": "1.6",
5
- "serialNumber": "urn:uuid:49322f04-cea2-480b-ac77-16cc39333700",
5
+ "serialNumber": "urn:uuid:16ede553-d8b4-4690-b8b3-506468a16e3f",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-05-14T23:15:40.993Z",
8
+ "timestamp": "2026-05-15T00:18:30.842Z",
9
9
  "lifecycles": [
10
10
  {
11
11
  "phase": "build"
@@ -19,14 +19,14 @@
19
19
  }
20
20
  ],
21
21
  "component": {
22
- "bom-ref": "@blamejs/core@0.9.24",
22
+ "bom-ref": "@blamejs/core@0.9.28",
23
23
  "type": "library",
24
24
  "name": "blamejs",
25
- "version": "0.9.24",
25
+ "version": "0.9.28",
26
26
  "scope": "required",
27
27
  "author": "blamejs contributors",
28
28
  "description": "The Node framework that owns its stack.",
29
- "purl": "pkg:npm/%40blamejs/core@0.9.24",
29
+ "purl": "pkg:npm/%40blamejs/core@0.9.28",
30
30
  "properties": [],
31
31
  "externalReferences": [
32
32
  {
@@ -54,7 +54,7 @@
54
54
  "components": [],
55
55
  "dependencies": [
56
56
  {
57
- "ref": "@blamejs/core@0.9.24",
57
+ "ref": "@blamejs/core@0.9.28",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]