@blamejs/blamejs-shop 0.3.12 → 0.3.13
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 +2 -0
- package/lib/admin.js +155 -113
- package/lib/asset-manifest.json +1 -1
- package/lib/vendor/MANIFEST.json +2 -2
- package/lib/vendor/blamejs/CHANGELOG.md +2 -0
- package/lib/vendor/blamejs/README.md +3 -2
- package/lib/vendor/blamejs/SECURITY.md +3 -0
- package/lib/vendor/blamejs/api-snapshot.json +14 -2
- package/lib/vendor/blamejs/lib/agent-event-bus.js +4 -4
- package/lib/vendor/blamejs/lib/agent-idempotency.js +6 -6
- package/lib/vendor/blamejs/lib/agent-orchestrator.js +9 -9
- package/lib/vendor/blamejs/lib/agent-posture-chain.js +10 -10
- package/lib/vendor/blamejs/lib/agent-saga.js +6 -7
- package/lib/vendor/blamejs/lib/agent-snapshot.js +8 -8
- package/lib/vendor/blamejs/lib/agent-stream.js +3 -3
- package/lib/vendor/blamejs/lib/agent-tenant.js +4 -4
- package/lib/vendor/blamejs/lib/agent-trace.js +5 -5
- package/lib/vendor/blamejs/lib/ai-disclosure.js +3 -3
- package/lib/vendor/blamejs/lib/app.js +2 -2
- package/lib/vendor/blamejs/lib/archive-read.js +1 -1
- package/lib/vendor/blamejs/lib/archive-tar-read.js +1 -1
- package/lib/vendor/blamejs/lib/archive-wrap.js +5 -5
- package/lib/vendor/blamejs/lib/audit-tools.js +65 -5
- package/lib/vendor/blamejs/lib/audit.js +2 -2
- package/lib/vendor/blamejs/lib/auth/ciba.js +1 -1
- package/lib/vendor/blamejs/lib/auth/dpop.js +1 -1
- package/lib/vendor/blamejs/lib/auth/fal.js +1 -1
- package/lib/vendor/blamejs/lib/auth/fido-mds3.js +2 -3
- package/lib/vendor/blamejs/lib/auth/jwt-external.js +2 -2
- package/lib/vendor/blamejs/lib/auth/oauth.js +9 -9
- package/lib/vendor/blamejs/lib/auth/oid4vci.js +7 -7
- package/lib/vendor/blamejs/lib/auth/oid4vp.js +1 -1
- package/lib/vendor/blamejs/lib/auth/openid-federation.js +5 -5
- package/lib/vendor/blamejs/lib/auth/passkey.js +6 -6
- package/lib/vendor/blamejs/lib/auth/saml.js +1 -1
- package/lib/vendor/blamejs/lib/auth/sd-jwt-vc.js +3 -6
- package/lib/vendor/blamejs/lib/backup/index.js +18 -18
- package/lib/vendor/blamejs/lib/cache.js +4 -4
- package/lib/vendor/blamejs/lib/calendar.js +5 -5
- package/lib/vendor/blamejs/lib/circuit-breaker.js +1 -1
- package/lib/vendor/blamejs/lib/cms-codec.js +2 -2
- package/lib/vendor/blamejs/lib/compliance.js +14 -14
- package/lib/vendor/blamejs/lib/crypto-field.js +58 -21
- package/lib/vendor/blamejs/lib/crypto.js +5 -6
- package/lib/vendor/blamejs/lib/db-query.js +131 -9
- package/lib/vendor/blamejs/lib/db.js +106 -22
- package/lib/vendor/blamejs/lib/external-db.js +64 -16
- package/lib/vendor/blamejs/lib/framework-schema.js +4 -4
- package/lib/vendor/blamejs/lib/guard-list-id.js +2 -2
- package/lib/vendor/blamejs/lib/guard-list-unsubscribe.js +1 -2
- package/lib/vendor/blamejs/lib/incident-report.js +150 -0
- package/lib/vendor/blamejs/lib/mail-crypto-smime.js +1 -1
- package/lib/vendor/blamejs/lib/mail-deploy.js +3 -3
- package/lib/vendor/blamejs/lib/mail-server-managesieve.js +2 -2
- package/lib/vendor/blamejs/lib/mail-server-pop3.js +2 -2
- package/lib/vendor/blamejs/lib/mail-store.js +1 -1
- package/lib/vendor/blamejs/lib/metrics.js +8 -8
- package/lib/vendor/blamejs/lib/middleware/csrf-protect.js +1 -1
- package/lib/vendor/blamejs/lib/middleware/dpop.js +5 -5
- package/lib/vendor/blamejs/lib/middleware/idempotency-key.js +21 -22
- package/lib/vendor/blamejs/lib/middleware/protected-resource-metadata.js +2 -2
- package/lib/vendor/blamejs/lib/network-dns-resolver.js +2 -2
- package/lib/vendor/blamejs/lib/network-dns.js +1 -2
- package/lib/vendor/blamejs/lib/network-tls.js +0 -1
- package/lib/vendor/blamejs/lib/outbox.js +1 -1
- package/lib/vendor/blamejs/lib/pqc-agent.js +1 -1
- package/lib/vendor/blamejs/lib/retention.js +1 -1
- package/lib/vendor/blamejs/lib/retry.js +1 -1
- package/lib/vendor/blamejs/lib/safe-archive.js +2 -2
- package/lib/vendor/blamejs/lib/safe-ical.js +2 -2
- package/lib/vendor/blamejs/lib/safe-mime.js +1 -1
- package/lib/vendor/blamejs/lib/self-update-standalone-verifier.js +1 -1
- package/lib/vendor/blamejs/lib/self-update.js +2 -2
- package/lib/vendor/blamejs/lib/static.js +1 -1
- package/lib/vendor/blamejs/lib/subject.js +2 -2
- package/lib/vendor/blamejs/lib/vault/index.js +64 -1
- package/lib/vendor/blamejs/lib/vault/rotate.js +19 -0
- package/lib/vendor/blamejs/lib/vendor-data.js +1 -1
- package/lib/vendor/blamejs/package.json +1 -1
- package/lib/vendor/blamejs/release-notes/v0.14.7.json +77 -0
- package/lib/vendor/blamejs/scripts/release.js +28 -3
- package/lib/vendor/blamejs/test/layer-0-primitives/audit-tools-dual-control.test.js +115 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/codebase-patterns.test.js +163 -3
- package/lib/vendor/blamejs/test/layer-0-primitives/crypto-field-derived-hash.test.js +102 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-column-gate.test.js +150 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/db-key-aad.test.js +109 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/external-db-hardening.test.js +53 -0
- package/lib/vendor/blamejs/test/layer-0-primitives/incident-report.test.js +65 -0
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 1,
|
|
3
|
-
"frameworkVersion": "0.14.
|
|
4
|
-
"createdAt": "2026-05-
|
|
3
|
+
"frameworkVersion": "0.14.7",
|
|
4
|
+
"createdAt": "2026-05-30T20:25:42.630Z",
|
|
5
5
|
"exports": {
|
|
6
6
|
"a2a": {
|
|
7
7
|
"type": "object",
|
|
@@ -14518,6 +14518,10 @@
|
|
|
14518
14518
|
"type": "function",
|
|
14519
14519
|
"arity": 0
|
|
14520
14520
|
},
|
|
14521
|
+
"getDeclaredColumns": {
|
|
14522
|
+
"type": "function",
|
|
14523
|
+
"arity": 1
|
|
14524
|
+
},
|
|
14521
14525
|
"getMode": {
|
|
14522
14526
|
"type": "function",
|
|
14523
14527
|
"arity": 0
|
|
@@ -37103,6 +37107,10 @@
|
|
|
37103
37107
|
"create": {
|
|
37104
37108
|
"type": "function",
|
|
37105
37109
|
"arity": 1
|
|
37110
|
+
},
|
|
37111
|
+
"createDeadlineClock": {
|
|
37112
|
+
"type": "function",
|
|
37113
|
+
"arity": 1
|
|
37106
37114
|
}
|
|
37107
37115
|
}
|
|
37108
37116
|
}
|
|
@@ -49732,6 +49740,10 @@
|
|
|
49732
49740
|
"type": "function",
|
|
49733
49741
|
"arity": 0
|
|
49734
49742
|
},
|
|
49743
|
+
"getDerivedHashMacKey": {
|
|
49744
|
+
"type": "function",
|
|
49745
|
+
"arity": 0
|
|
49746
|
+
},
|
|
49735
49747
|
"getDerivedHashSalt": {
|
|
49736
49748
|
"type": "function",
|
|
49737
49749
|
"arity": 0
|
|
@@ -136,7 +136,7 @@ function _registerTopic(topics, name, topicOpts, auditImpl) {
|
|
|
136
136
|
throw new AgentEventBusError("agent-event-bus/bad-schema",
|
|
137
137
|
"registerTopic: schema required (flat key→type map)");
|
|
138
138
|
}
|
|
139
|
-
//
|
|
139
|
+
// `kind` is now captured on register so listTopics's kind
|
|
140
140
|
// filter actually matches. Prior shape never set entry.kind, so the
|
|
141
141
|
// filter at args.kind was dead. Default value derives from the
|
|
142
142
|
// dotted topic name's first segment ("mail.scan.x" → "mail"), giving
|
|
@@ -164,7 +164,7 @@ function _registerTopic(topics, name, topicOpts, auditImpl) {
|
|
|
164
164
|
});
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
-
//
|
|
167
|
+
// Operators reloading a module (test runners between
|
|
168
168
|
// runs, hot-reload tools, multi-tenant onboarding flows that
|
|
169
169
|
// register-deregister topics) need a clean unregister path; without
|
|
170
170
|
// it the second register throws topic-duplicate and the operator is
|
|
@@ -183,7 +183,7 @@ function _unregisterTopic(topics, name, auditImpl) {
|
|
|
183
183
|
function _listTopics(topics, args, permissions) {
|
|
184
184
|
// Permission gate: list-topics requires no special scope by default;
|
|
185
185
|
// operator can wrap with their own permissions instance for stricter.
|
|
186
|
-
//
|
|
186
|
+
// Kind filter now matches because register captures kind.
|
|
187
187
|
var out = [];
|
|
188
188
|
topics.forEach(function (entry) {
|
|
189
189
|
if (args.kind && entry.kind !== args.kind) return;
|
|
@@ -229,7 +229,7 @@ async function _publish(topics, pubsub, name, payload, pOpts, permissions, audit
|
|
|
229
229
|
}
|
|
230
230
|
// Schema validation.
|
|
231
231
|
guardEventBusPayload.validate(payload, entry.schema);
|
|
232
|
-
//
|
|
232
|
+
// When a topic is tenant-scoped, require the publisher
|
|
233
233
|
// to declare a tenantId BEFORE the event reaches the durable bus
|
|
234
234
|
// backend. Prior shape allowed `wrapped._tenantId: null` to land on
|
|
235
235
|
// the bus, and the receive-side drop only fired AFTER persistence —
|
|
@@ -158,7 +158,7 @@ function create(opts) {
|
|
|
158
158
|
return {
|
|
159
159
|
get: function (method, actorId, key) { return _get(store, method, actorId, key, auditImpl, ttlMs, maxResultBytes); },
|
|
160
160
|
put: function (method, actorId, key, result, putOpts) { return _put(store, method, actorId, key, result, putOpts || {}, ttlMs, maxResultBytes, fingerprintArgs, auditImpl); },
|
|
161
|
-
//
|
|
161
|
+
// putIfAbsent gates concurrent retries at the cache
|
|
162
162
|
// boundary so only one consumer runs the handler. Operator wraps:
|
|
163
163
|
// var claim = await idem.putIfAbsent(method, actor, key, args);
|
|
164
164
|
// if (claim.alreadyClaimed) return claim.result; // another retry won
|
|
@@ -173,7 +173,7 @@ function create(opts) {
|
|
|
173
173
|
};
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
-
//
|
|
176
|
+
// Atomic claim/check/run pattern. Returns one of:
|
|
177
177
|
// { alreadyClaimed: false, fingerprint } — caller runs the handler
|
|
178
178
|
// { alreadyClaimed: true, pending: true } — another in-flight claim holds the slot
|
|
179
179
|
// { alreadyClaimed: true, result: <cached> } — prior handler completed; cached result
|
|
@@ -299,7 +299,7 @@ async function _get(store, method, actorId, key, auditImpl, ttlMs, maxResultByte
|
|
|
299
299
|
throw new AgentIdempotencyError("agent-idempotency/corrupt-result",
|
|
300
300
|
"get: cached result failed to parse — " + (e && e.message ? e.message : String(e)));
|
|
301
301
|
}
|
|
302
|
-
//
|
|
302
|
+
// Atomic replayCount increment. The prior shape
|
|
303
303
|
// (read row → mutate → put row) raced two concurrent retries: each
|
|
304
304
|
// saw replayCount=N, both wrote replayCount=N+1, so the counter
|
|
305
305
|
// missed bumps and the put-with-fresh-result race-clobbered prior
|
|
@@ -439,7 +439,7 @@ function _fingerprintArgs(args) {
|
|
|
439
439
|
// unique fingerprint and defeat the args-mismatch defense. Strip
|
|
440
440
|
// _traceContext (varies per-hop, doesn't change result).
|
|
441
441
|
//
|
|
442
|
-
//
|
|
442
|
+
// DO NOT strip _postureChain. The prior shape ignored
|
|
443
443
|
// _postureChain.postureSet, so a request made under
|
|
444
444
|
// postureSet:["hipaa","pci-dss"] cached the same result that a
|
|
445
445
|
// downgrade attempt under postureSet:["pci-dss"] would replay
|
|
@@ -493,7 +493,7 @@ function _inMemoryBackend(maxEntries) {
|
|
|
493
493
|
map.set(_k(method, actorId, hash), row);
|
|
494
494
|
return Promise.resolve();
|
|
495
495
|
},
|
|
496
|
-
//
|
|
496
|
+
// Atomic insert. Map.set is synchronous so the
|
|
497
497
|
// get+set pair below is naturally race-free within the in-memory
|
|
498
498
|
// backend (V8 single-threaded). Returns true when inserted, false
|
|
499
499
|
// when the row already exists.
|
|
@@ -503,7 +503,7 @@ function _inMemoryBackend(maxEntries) {
|
|
|
503
503
|
map.set(k, row);
|
|
504
504
|
return Promise.resolve(true);
|
|
505
505
|
},
|
|
506
|
-
//
|
|
506
|
+
// Atomic replayCount increment. Operators wiring
|
|
507
507
|
// a SQL backend implement this with `UPDATE ... SET
|
|
508
508
|
// replay_count = replay_count + 1 WHERE keyHash = $1 RETURNING *`
|
|
509
509
|
// — read-modify-write race-free. In-memory backend is naturally
|
|
@@ -119,7 +119,7 @@ function _unsealRegistryRow(row) {
|
|
|
119
119
|
var DEFAULT_DRAIN_TIMEOUT_MS = C.TIME.minutes(2);
|
|
120
120
|
var STREAM_ID_RAND_BYTES = 8; // stream-id random-suffix byte length, not a size cap
|
|
121
121
|
var DEFAULT_PER_CONSUMER_STOP_MS = C.TIME.seconds(5);
|
|
122
|
-
//
|
|
122
|
+
// FNV-1a offset basis salted with the first 32 bits of
|
|
123
123
|
// SHA3-512(vault master). Attackers who don't have read access to the
|
|
124
124
|
// vault keypair can't compute the salt, so they can't engineer
|
|
125
125
|
// tenantIds that all map to one shard. Cached per-process; rotation
|
|
@@ -183,7 +183,7 @@ function create(opts) {
|
|
|
183
183
|
// operator-supplied metadata (kind / tenantId / posture / ...);
|
|
184
184
|
// every consuming process holds its own runtime map of name → agent.
|
|
185
185
|
liveAgents: new Map(),
|
|
186
|
-
//
|
|
186
|
+
// Drain quiesce wiring. Operator passes
|
|
187
187
|
// { outbox, sagaInFlightCount, pubsubFlush } via create() so the
|
|
188
188
|
// drain phase can quiesce real in-flight work, not just stop
|
|
189
189
|
// consumers. Optional — operators with no outbox / saga / pubsub
|
|
@@ -192,7 +192,7 @@ function create(opts) {
|
|
|
192
192
|
sagaInFlightCount: typeof opts.sagaInFlightCount === "function" ? opts.sagaInFlightCount : null,
|
|
193
193
|
pubsubFlush: typeof opts.pubsubFlush === "function" ? opts.pubsubFlush : null,
|
|
194
194
|
perConsumerStopMs: typeof opts.perConsumerStopMs === "number" ? opts.perConsumerStopMs : DEFAULT_PER_CONSUMER_STOP_MS,
|
|
195
|
-
//
|
|
195
|
+
// onTransition handler invalidates election cache
|
|
196
196
|
// on lease-lost / acquired / released. Operator opts out via
|
|
197
197
|
// { cacheElections: false } to always re-query b.cluster.
|
|
198
198
|
cacheElections: opts.cacheElections !== false,
|
|
@@ -205,7 +205,7 @@ function create(opts) {
|
|
|
205
205
|
});
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
-
//
|
|
208
|
+
// Subscribe to cluster lease transitions so cached
|
|
209
209
|
// election state can't go stale after a partition. b.cluster
|
|
210
210
|
// .onTransition fires for every lease-acquired / lease-lost / lease-
|
|
211
211
|
// released event; we invalidate the affected resource's cached
|
|
@@ -254,7 +254,7 @@ function create(opts) {
|
|
|
254
254
|
* @status stable
|
|
255
255
|
* @related b.agent.orchestrator.create
|
|
256
256
|
*
|
|
257
|
-
*
|
|
257
|
+
* Attach an in-process live agent reference to a row
|
|
258
258
|
* that already exists in the persistent registry backend. The
|
|
259
259
|
* canonical boot-phase contract: the *first* process to start a new
|
|
260
260
|
* agent calls `register()` (writes the backend row + holds the live
|
|
@@ -499,7 +499,7 @@ function _spawnSingleConsumer(ctx, agent, queue, topic, maxConcurrency) {
|
|
|
499
499
|
* // → integer in [0, 8)
|
|
500
500
|
*/
|
|
501
501
|
function _saltedFnvBasis() {
|
|
502
|
-
//
|
|
502
|
+
// Salt FNV-1a offset basis with the vault master so
|
|
503
503
|
// an attacker can't engineer tenantIds that all hash to one shard.
|
|
504
504
|
// Vault-less path (single-process tests / dev) falls back to the
|
|
505
505
|
// standard FNV offset basis; production deployments with vault
|
|
@@ -561,7 +561,7 @@ async function _elect(ctx, args) {
|
|
|
561
561
|
});
|
|
562
562
|
return elec;
|
|
563
563
|
}
|
|
564
|
-
//
|
|
564
|
+
// Cluster mode: ALWAYS query truth from b.cluster.
|
|
565
565
|
// The onTransition handler installed in create() invalidates the
|
|
566
566
|
// cache on every lease event, so a cache hit here is safe (it
|
|
567
567
|
// means no lease event has fired since the last query). But the
|
|
@@ -600,10 +600,10 @@ async function _drain(ctx, args) {
|
|
|
600
600
|
var drained = 0;
|
|
601
601
|
var startedAt = Date.now();
|
|
602
602
|
var perConsumerMs = ctx.perConsumerStopMs;
|
|
603
|
-
//
|
|
603
|
+
// Drain phases:
|
|
604
604
|
// 1. set ctx.draining so streams emit drain-markers + new task
|
|
605
605
|
// dispatches refuse (consumers re-check on every envelope).
|
|
606
|
-
// 2. stop each consumer with
|
|
606
|
+
// 2. stop each consumer with a per-consumer timeout race —
|
|
607
607
|
// one hung consumer can't block the full drain budget.
|
|
608
608
|
// 3. quiesce in-flight: poll outbox.pendingCount + sagaInFlightCount
|
|
609
609
|
// until 0 OR remaining-budget-ms elapses.
|
|
@@ -65,7 +65,7 @@ var AgentPostureChainError = defineClass("AgentPostureChainError", { alwaysPerma
|
|
|
65
65
|
|
|
66
66
|
var BUILTIN_REGIMES = Object.freeze(["hipaa", "pci-dss", "gdpr", "soc2"]);
|
|
67
67
|
|
|
68
|
-
//
|
|
68
|
+
// Envelope MAC vocabulary. Cross-process envelope
|
|
69
69
|
// integrity: an attacker with queue / event-bus write access who
|
|
70
70
|
// strips postureSet to [] and re-sends a saga / sub-agent envelope
|
|
71
71
|
// can bypass the downgrade refusal in _validate (which only checks
|
|
@@ -73,7 +73,7 @@ var BUILTIN_REGIMES = Object.freeze(["hipaa", "pci-dss", "gdpr", "soc2"]);
|
|
|
73
73
|
// envelope bytes, computed at appendHop and verified at validate.
|
|
74
74
|
var ENVELOPE_MAC_LABEL = "blamejs.agent.postureChain/v1";
|
|
75
75
|
var ENVELOPE_MAC_KEY_BYTES = 32; // HMAC-SHA3-512 keyed bytes
|
|
76
|
-
//
|
|
76
|
+
// Hop count cap defends infinite recursion across
|
|
77
77
|
// agent delegation. 16 is the spec default; operators can lower via
|
|
78
78
|
// opts.maxHopCount but never raise (audit fan-out without a cap is a
|
|
79
79
|
// DoS class).
|
|
@@ -111,7 +111,7 @@ function _resolveMacKey() {
|
|
|
111
111
|
|
|
112
112
|
function _envelopeMacBytes(envelope) {
|
|
113
113
|
// Sign every field that downstream consumers verify off the wire,
|
|
114
|
-
// except the `_mac` field itself.
|
|
114
|
+
// except the `_mac` field itself. Also includes
|
|
115
115
|
// hopCount + chainTrail so a hostile rewriter can't roll back the
|
|
116
116
|
// trail to evade the cap.
|
|
117
117
|
var payload = {
|
|
@@ -163,8 +163,8 @@ function create(opts) {
|
|
|
163
163
|
opts.maxHopCount <= DEFAULT_MAX_HOP_COUNT
|
|
164
164
|
? Math.floor(opts.maxHopCount)
|
|
165
165
|
: DEFAULT_MAX_HOP_COUNT;
|
|
166
|
-
//
|
|
167
|
-
//
|
|
166
|
+
// Escape hatch — only single-process unit tests should opt out of
|
|
167
|
+
// envelope MAC. Production / multi-
|
|
168
168
|
// process / queue-spanning deployments leave the default on; the
|
|
169
169
|
// gate audit-emits when bypassed so the posture is visible.
|
|
170
170
|
var requireMac = opts.requireMac !== false;
|
|
@@ -257,7 +257,7 @@ function _appendHop(ctx, envelope, hopName) {
|
|
|
257
257
|
"appendHop: hopName must be a non-empty string");
|
|
258
258
|
}
|
|
259
259
|
var trail = Array.isArray(envelope.chainTrail) ? envelope.chainTrail.slice() : [];
|
|
260
|
-
//
|
|
260
|
+
// Cap enforced BEFORE the push so the hop-cap throw
|
|
261
261
|
// fires consistently regardless of whether the operator inspects
|
|
262
262
|
// trail.length first. Cap is a hard refusal (no truncation) because
|
|
263
263
|
// a silently-dropped hop loses audit provenance for the call.
|
|
@@ -279,8 +279,8 @@ function _appendHop(ctx, envelope, hopName) {
|
|
|
279
279
|
hopCount: trail.length,
|
|
280
280
|
});
|
|
281
281
|
guardPostureChain.validate(newEnvelope);
|
|
282
|
-
//
|
|
283
|
-
// ctx.requireMac=false (
|
|
282
|
+
// Sign at every hop. Verify-side enforces requireMac.
|
|
283
|
+
// ctx.requireMac=false (test escape hatch) skips
|
|
284
284
|
// the sign so a vault-less test path still works.
|
|
285
285
|
if (ctx.requireMac) {
|
|
286
286
|
try {
|
|
@@ -301,7 +301,7 @@ function _appendHop(ctx, envelope, hopName) {
|
|
|
301
301
|
|
|
302
302
|
function _validate(ctx, envelope, agentPostureSet) {
|
|
303
303
|
guardPostureChain.validate(envelope);
|
|
304
|
-
//
|
|
304
|
+
// MAC verification BEFORE any field-based decision so
|
|
305
305
|
// the wire-rewrite attack (postureSet:[] downgrade with valid SHAPE
|
|
306
306
|
// but no integrity binding) is refused. ctx.requireMac=false skips
|
|
307
307
|
// verification and emits an audit so the bypass is visible.
|
|
@@ -326,7 +326,7 @@ function _validate(ctx, envelope, agentPostureSet) {
|
|
|
326
326
|
chainTrail: envelope.chainTrail,
|
|
327
327
|
});
|
|
328
328
|
}
|
|
329
|
-
//
|
|
329
|
+
// Hop cap also enforced at validate-time. A hostile
|
|
330
330
|
// envelope might arrive with hopCount > cap if a prior hop's
|
|
331
331
|
// requireMac was off; refuse here regardless.
|
|
332
332
|
if (Array.isArray(envelope.chainTrail) && envelope.chainTrail.length > ctx.maxHopCount) {
|
|
@@ -58,8 +58,7 @@
|
|
|
58
58
|
*
|
|
59
59
|
* ## No saga-level retry
|
|
60
60
|
*
|
|
61
|
-
*
|
|
62
|
-
* 2026-05-14): saga's value-add is compensation, not retry. If a
|
|
61
|
+
* Saga's value-add is compensation, not retry. If a
|
|
63
62
|
* step needs retry-with-backoff, the operator wraps `step.run`
|
|
64
63
|
* with `b.retry` inside the step body. With v0.9.22 idempotency
|
|
65
64
|
* available, internal retry inside step.run is side-effect-safe.
|
|
@@ -109,7 +108,7 @@ var SAGA_ID_RAND_BYTES = 8;
|
|
|
109
108
|
function create(config) {
|
|
110
109
|
guardSagaConfig.validate(config);
|
|
111
110
|
var auditImpl = config.audit || audit();
|
|
112
|
-
//
|
|
111
|
+
// Operator wires a stateStore for crash-safe resume.
|
|
113
112
|
// Interface: { saveStep, loadResumePoint, markCompleted, markFailed }.
|
|
114
113
|
// saveStep({sagaId, stepIndex, stepName, state, status}) commits
|
|
115
114
|
// after each step.run; loadResumePoint(sagaId) returns the resume
|
|
@@ -189,7 +188,7 @@ async function _runFrom(config, auditImpl, stateStore, ctx, state, opts, sagaId,
|
|
|
189
188
|
});
|
|
190
189
|
await step.run(ctx, state);
|
|
191
190
|
completedSteps.push({ step: step, index: i });
|
|
192
|
-
//
|
|
191
|
+
// Checkpoint after the step.run returns. saveStep
|
|
193
192
|
// commits the post-step state so a crash before the NEXT step
|
|
194
193
|
// resumes from i+1. The audit chain records the checkpoint
|
|
195
194
|
// independently of the operator's stateStore — operator can
|
|
@@ -232,7 +231,7 @@ async function _runFrom(config, auditImpl, stateStore, ctx, state, opts, sagaId,
|
|
|
232
231
|
message: (stepErr && stepErr.message) || String(stepErr),
|
|
233
232
|
});
|
|
234
233
|
var compensationError = null;
|
|
235
|
-
//
|
|
234
|
+
// Capture the compensation step that ACTUALLY failed,
|
|
236
235
|
// not "completedSteps[completedSteps.length-1].name" which
|
|
237
236
|
// names the last-COMPLETED step regardless of which compensation
|
|
238
237
|
// threw. CWE-209-adjacent (information disclosure via wrong
|
|
@@ -278,7 +277,7 @@ async function _runFrom(config, auditImpl, stateStore, ctx, state, opts, sagaId,
|
|
|
278
277
|
});
|
|
279
278
|
} catch (_e) { /* drop-silent — audit already records */ }
|
|
280
279
|
}
|
|
281
|
-
//
|
|
280
|
+
// Attach cause:stepErr so the original step
|
|
282
281
|
// error stack survives. ES2022 Error.cause is the standard
|
|
283
282
|
// mechanism; the framework's defineClass-built AgentSagaError
|
|
284
283
|
// accepts cause via the third arg.
|
|
@@ -290,7 +289,7 @@ async function _runFrom(config, auditImpl, stateStore, ctx, state, opts, sagaId,
|
|
|
290
289
|
((compensationError.message) || String(compensationError));
|
|
291
290
|
}
|
|
292
291
|
var sagaErr = new AgentSagaError("agent-saga/failed", detailMsg);
|
|
293
|
-
//
|
|
292
|
+
// ES2022 Error.cause attaches the originating
|
|
294
293
|
// stepErr so operator stack-trace tooling can walk the chain.
|
|
295
294
|
// defineClass({alwaysPermanent:true}) doesn't accept cause in
|
|
296
295
|
// its constructor signature; the property assignment after
|
|
@@ -60,7 +60,7 @@ var vault = lazyRequire(function () { return require("./vault");
|
|
|
60
60
|
|
|
61
61
|
var AgentSnapshotError = defineClass("AgentSnapshotError", { alwaysPermanent: true });
|
|
62
62
|
|
|
63
|
-
//
|
|
63
|
+
// Sealed envelopes start with this prefix on disk; the
|
|
64
64
|
// loader sniffs it and routes through unseal before guardSnapshotEnvelope
|
|
65
65
|
// validation. Compatible with operator backends that store the value
|
|
66
66
|
// as a string (JSON DBs, k/v stores) or wrap it in `{ value: "..." }`.
|
|
@@ -113,19 +113,19 @@ function create(opts) {
|
|
|
113
113
|
var snapshotIntervalMs = typeof policy.snapshotIntervalMs === "number" ? policy.snapshotIntervalMs : DEFAULT_SNAPSHOT_INTERVAL_MS;
|
|
114
114
|
var maxSnapshotBytes = typeof policy.maxSnapshotBytes === "number" ? policy.maxSnapshotBytes : DEFAULT_MAX_SNAPSHOT_BYTES;
|
|
115
115
|
var auditImpl = opts.audit || audit();
|
|
116
|
-
//
|
|
116
|
+
// Operator may inject `signer` (interface
|
|
117
117
|
// `{ sign(bytes) → Buffer, verify(bytes, sig, pubKey?) → boolean }`)
|
|
118
118
|
// for testing / alternate key custody. Default = b.auditSign when
|
|
119
119
|
// initialized at boot; refuses persist() with a clear error if
|
|
120
120
|
// neither is wired so secure-by-default holds.
|
|
121
121
|
var signer = opts.signer || null;
|
|
122
|
-
//
|
|
122
|
+
// Operator may inject `sealer` (interface
|
|
123
123
|
// `{ seal(plaintext, aadParts) → string, unseal(value, aadParts) → string }`)
|
|
124
124
|
// for alternate KMS integration. Default = b.vault.aad. Refused if
|
|
125
125
|
// neither is wired AND opts.allowPlaintext is not explicitly true
|
|
126
126
|
// (operator-justified dev / single-tenant deployments only).
|
|
127
127
|
var sealer = opts.sealer || null;
|
|
128
|
-
//
|
|
128
|
+
// Operator-supplied restoreHandlers walk the
|
|
129
129
|
// snapshot inFlight + idempotencyCache + orchestratorState segments
|
|
130
130
|
// and hydrate the corresponding consumer module. Map shape:
|
|
131
131
|
// { streams, sagas, outboxJobs, busSubscribers, pendingDeliveries,
|
|
@@ -300,7 +300,7 @@ async function _takeSnapshot(ctx, snapshotOpts) {
|
|
|
300
300
|
|
|
301
301
|
async function _persist(ctx, snap) {
|
|
302
302
|
guardSnapshotEnvelope.validate(snap);
|
|
303
|
-
//
|
|
303
|
+
// Sign first so a backend that mutates on put() (very
|
|
304
304
|
// common for k/v stores adding metadata) doesn't poison the signed
|
|
305
305
|
// bytes downstream readers verify.
|
|
306
306
|
var signer = _resolveSigner(ctx);
|
|
@@ -314,7 +314,7 @@ async function _persist(ctx, snap) {
|
|
|
314
314
|
// pubkey at verify time).
|
|
315
315
|
snap.sigPubKey = (typeof signer.getPublicKey === "function" && signer.getPublicKey()) || null;
|
|
316
316
|
|
|
317
|
-
//
|
|
317
|
+
// Seal the entire envelope under AAD that pins
|
|
318
318
|
// snapshotId + schemaVersion + tenantId. AAD mismatch on unseal (a
|
|
319
319
|
// copy-paste attack from one snapshotId's row into another) fails
|
|
320
320
|
// the Poly1305 tag check; tampered bytes also fail. The sealed
|
|
@@ -405,7 +405,7 @@ async function _unwrapAndVerify(ctx, raw, expectedId) {
|
|
|
405
405
|
throw new AgentSnapshotError("agent-snapshot/snapshot-id-mismatch",
|
|
406
406
|
"load: wrapper snapshotId='" + expectedId + "' does not match envelope='" + snap.snapshotId + "'");
|
|
407
407
|
}
|
|
408
|
-
//
|
|
408
|
+
// Verify the signature before returning the envelope
|
|
409
409
|
// to the caller. Restore-side trust derives from this gate. The
|
|
410
410
|
// allowPlaintext escape hatch (operator-acknowledged dev mode)
|
|
411
411
|
// also waives signature verification because there's no key custody
|
|
@@ -496,7 +496,7 @@ async function _restore(ctx, snap, restoreOpts) {
|
|
|
496
496
|
affectedStreams: (snap.inFlight && snap.inFlight.streams || []).length,
|
|
497
497
|
});
|
|
498
498
|
}
|
|
499
|
-
//
|
|
499
|
+
// Invoke operator-supplied restoreHandlers across
|
|
500
500
|
// every segment the snapshot envelope carries. Handlers are
|
|
501
501
|
// declared at create() time; the snapshot primitive owns ordering
|
|
502
502
|
// (orchestratorState first so live agents register before consumers
|
|
@@ -138,7 +138,7 @@ function _makeIterator(ctx) {
|
|
|
138
138
|
var done = false;
|
|
139
139
|
var closed = false;
|
|
140
140
|
var drained = false;
|
|
141
|
-
//
|
|
141
|
+
// Track the cursor of the LAST row actually yielded
|
|
142
142
|
// to the consumer. The prior shape called cursor.lastSeenCursor()
|
|
143
143
|
// at drain-marker emit, which returned the position of the last
|
|
144
144
|
// FETCHED batch — clients resuming from that cursor SKIPPED every
|
|
@@ -167,7 +167,7 @@ function _makeIterator(ctx) {
|
|
|
167
167
|
try {
|
|
168
168
|
if (buffer.length > 0) {
|
|
169
169
|
var row = buffer.shift();
|
|
170
|
-
//
|
|
170
|
+
// Record the cursor for this yielded row so a
|
|
171
171
|
// drain that fires BETWEEN buffered yields emits a marker
|
|
172
172
|
// whose lastSeenCursor matches what the client actually
|
|
173
173
|
// received. The cursor extraction shape mirrors the
|
|
@@ -248,7 +248,7 @@ function _safeAudit(auditImpl, action, actor, metadata) {
|
|
|
248
248
|
agentAudit.safeAudit(auditImpl, action, actor, metadata);
|
|
249
249
|
}
|
|
250
250
|
|
|
251
|
-
//
|
|
251
|
+
// Resolve the resume cursor for a row about to be
|
|
252
252
|
// yielded. Operators may attach the cursor per-row (`row._cursor` /
|
|
253
253
|
// `row.cursor`) OR rely on the cursor's own per-row tracker
|
|
254
254
|
// (`cursor.cursorForRow(row)`) — both shapes supported.
|
|
@@ -222,7 +222,7 @@ async function _unregister(ctx, tenantId, args) {
|
|
|
222
222
|
}
|
|
223
223
|
// Archive default — retain the key + metadata for retention-mandated
|
|
224
224
|
// restoration. Operator's compliance regime drives archivePolicy.
|
|
225
|
-
//
|
|
225
|
+
// Persist as a `status: "archived"` row in the same
|
|
226
226
|
// backend rather than only the process-local Map. GDPR Art. 17 +
|
|
227
227
|
// HIPAA §164.530(j) require the archived state to survive process
|
|
228
228
|
// restart (auditor pulls a deleted tenant's archival record years
|
|
@@ -373,7 +373,7 @@ function _check(ctx, actor, agentTenantId) {
|
|
|
373
373
|
|
|
374
374
|
// ---- Per-tenant derived key -----------------------------------------------
|
|
375
375
|
//
|
|
376
|
-
//
|
|
376
|
+
// `namespaceHash(label, tenantId)` is a PUBLIC function
|
|
377
377
|
// over PUBLIC inputs; an attacker who learns `tenantId` (an account id
|
|
378
378
|
// surfaced in URLs / API responses) reconstructs every per-tenant key
|
|
379
379
|
// without any secret material. The defense the docstring promises —
|
|
@@ -555,7 +555,7 @@ var TENANT_FIELD_PREFIX = "tnt-v1:";
|
|
|
555
555
|
function _tenantFieldKey(tenantId, table) {
|
|
556
556
|
// 32-byte symmetric key for XChaCha20-Poly1305. _deriveTenantKeyBytes
|
|
557
557
|
// returns the raw key bound to the vault master + tenantId + purpose
|
|
558
|
-
// — see
|
|
558
|
+
// — see the commentary above _derivedKey for the threat
|
|
559
559
|
// model that drove this away from public-input-only derivation.
|
|
560
560
|
return _deriveTenantKeyBytes(tenantId, "cryptoField:" + table);
|
|
561
561
|
}
|
|
@@ -646,7 +646,7 @@ function _unsealRowForTenant(ctx, tenantId, table, row) {
|
|
|
646
646
|
if (out[f] !== undefined && out[f] !== null) {
|
|
647
647
|
try { out[f] = _unsealField(tenantId, table, f, out[f]); }
|
|
648
648
|
catch (e) {
|
|
649
|
-
//
|
|
649
|
+
// Null-on-decrypt-failure was silent; the docstring
|
|
650
650
|
// promised "audit chain surfaces the failure" but no emit ever
|
|
651
651
|
// ran. Cross-tenant ciphertext replay / tampered row / wrong-
|
|
652
652
|
// prefix all hit this path; operator audit pipelines need the
|
|
@@ -70,7 +70,7 @@ var audit = lazyRequire(function () { return require("./audit"); })
|
|
|
70
70
|
|
|
71
71
|
var AgentTraceError = defineClass("AgentTraceError", { alwaysPermanent: true });
|
|
72
72
|
|
|
73
|
-
//
|
|
73
|
+
// Once-per-process audit emit on the first tracer
|
|
74
74
|
// failure each install fires. Operators get the signal even when
|
|
75
75
|
// individual span calls are best-effort suppressed.
|
|
76
76
|
var _failureAuditEmittedFor = Object.create(null);
|
|
@@ -122,7 +122,7 @@ function create(opts) {
|
|
|
122
122
|
injectIntoEnvelope: function (envelope, span) { return _injectIntoEnvelope(opts.tracing, envelope, span); },
|
|
123
123
|
extractFromEnvelope: function (envelope) { return _extractFromEnvelope(envelope); },
|
|
124
124
|
recordResult: function (span, result, error) { return _recordResult(span, result, error, auditImpl); },
|
|
125
|
-
//
|
|
125
|
+
// `shouldSample` now takes a traceId so the same
|
|
126
126
|
// trace gets the same decision across hops. Operator-supplied
|
|
127
127
|
// traceId comes from the W3C `traceparent` header at request-
|
|
128
128
|
// entry; absent that, falls back to Math.random (start of trace).
|
|
@@ -150,7 +150,7 @@ function _startSpan(tracing, name, sopts, auditImpl) {
|
|
|
150
150
|
return tracing.startSpan(name, sopts);
|
|
151
151
|
}
|
|
152
152
|
} catch (e) {
|
|
153
|
-
//
|
|
153
|
+
// Tracer failures should not crash the agent's
|
|
154
154
|
// method call; surface the first failure to the audit chain
|
|
155
155
|
// (rate-limited) so operators get the signal.
|
|
156
156
|
_emitFirstFailureAudit(auditImpl, "startSpan", e && e.message);
|
|
@@ -206,7 +206,7 @@ function _extractFromEnvelope(envelope) {
|
|
|
206
206
|
|
|
207
207
|
function _recordResult(span, result, error, auditImpl) {
|
|
208
208
|
if (!span || typeof span !== "object") return;
|
|
209
|
-
//
|
|
209
|
+
// Surface first occurrence of each span-op failure
|
|
210
210
|
// via audit so the operator gets the signal. Subsequent failures
|
|
211
211
|
// stay silent (best-effort) per the operational spec.
|
|
212
212
|
if (error) {
|
|
@@ -228,7 +228,7 @@ function _recordResult(span, result, error, auditImpl) {
|
|
|
228
228
|
}
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
-
//
|
|
231
|
+
// Deterministic sampling per W3C Trace Context §3.2.3.1.
|
|
232
232
|
// `Math.random` makes child-vs-parent sampling decisions non-coherent:
|
|
233
233
|
// a parent span sampled OUT can still have child spans sampled IN,
|
|
234
234
|
// producing orphaned spans operators can't correlate. Hashing the
|
|
@@ -133,7 +133,7 @@ function chatbot(session, opts) {
|
|
|
133
133
|
var text = typeof opts.text === "string" && opts.text.length > 0
|
|
134
134
|
? opts.text
|
|
135
135
|
: DEFAULT_CHATBOT_TEXT;
|
|
136
|
-
//
|
|
136
|
+
// "on-request" placement gates on
|
|
137
137
|
// the operator's explicit `opts.requested: true` signal. Without
|
|
138
138
|
// it, "on-request" collapsed into "always" semantics and emitted
|
|
139
139
|
// every call. The operator wires this from an explicit user
|
|
@@ -155,7 +155,7 @@ function chatbot(session, opts) {
|
|
|
155
155
|
regulation: "Regulation (EU) 2024/1689",
|
|
156
156
|
};
|
|
157
157
|
if (shouldEmit) {
|
|
158
|
-
//
|
|
158
|
+
// Mark the session so subsequent
|
|
159
159
|
// calls with the same session under "first-message" placement
|
|
160
160
|
// see `aiDisclosureEmitted: true` and return shouldEmit=false.
|
|
161
161
|
// Without this mutation operators had to remember to flip the
|
|
@@ -385,7 +385,7 @@ function applyAll(scenario) {
|
|
|
385
385
|
"applyAll: scenario.kinds must be a non-empty array of " +
|
|
386
386
|
"\"chatbot\" / \"deepfake\" / \"emotion\"");
|
|
387
387
|
}
|
|
388
|
-
//
|
|
388
|
+
// Validate every kind +
|
|
389
389
|
// per-kind required field UP FRONT, before any emission.
|
|
390
390
|
// Previously a later-kind failure (e.g. deepfake missing
|
|
391
391
|
// contentType, unknown trailing kind) ran AFTER earlier kinds
|
|
@@ -113,7 +113,7 @@ function _resolveMiddlewareOpt(value, allowDefault, name) {
|
|
|
113
113
|
if (value === false) {
|
|
114
114
|
// Operator explicitly disabled this middleware. When it's one of the
|
|
115
115
|
// security-on-by-default layers (allowDefault), leave an audit trace
|
|
116
|
-
// so the weakened posture is visible —
|
|
116
|
+
// so the weakened posture is visible — security defaults
|
|
117
117
|
// shouldn't be silently opt-out-able. Drop-silent observability sink.
|
|
118
118
|
if (allowDefault && name) {
|
|
119
119
|
try {
|
|
@@ -227,7 +227,7 @@ async function createApp(opts) {
|
|
|
227
227
|
var rateLimitOpts = _resolveMiddlewareOpt(mwConfig.rateLimit, false, "rateLimit");
|
|
228
228
|
if (rateLimitOpts) router.use(middleware.rateLimit(rateLimitOpts));
|
|
229
229
|
|
|
230
|
-
// Security middleware wired ON by default
|
|
230
|
+
// Security middleware wired ON by default. Each reads its
|
|
231
231
|
// config from opts.middleware.<name>: pass `false` to opt out (audited
|
|
232
232
|
// via _resolveMiddlewareOpt), or an object to customize — operator cookie
|
|
233
233
|
// / field names flow straight through, nothing static is baked in.
|
|
@@ -260,7 +260,7 @@ async function _readCentralDirectory(adapter, eocd) {
|
|
|
260
260
|
"central directory entry " + n + " carries ZIP64 sentinel sizes (unsupported — use tar for >4 GiB / >65535 entries)");
|
|
261
261
|
}
|
|
262
262
|
// ZIP names are CP437 or UTF-8 (per FLAG_UTF8_NAME bit). Decode
|
|
263
|
-
// as UTF-8 unconditionally —
|
|
263
|
+
// as UTF-8 unconditionally — a concern if operators in
|
|
264
264
|
// the wild rely on CP437; v0.12.7 ships UTF-8 only and operators
|
|
265
265
|
// with legacy CP437-only producers reach for an external decoder.
|
|
266
266
|
var name = cdBytes.slice(nameStart, nameStart + nameLen).toString("utf8");
|
|
@@ -237,7 +237,7 @@ function tar(adapter, opts) {
|
|
|
237
237
|
}
|
|
238
238
|
var bodyStart = pos;
|
|
239
239
|
var paddedSize = Math.ceil(hdr.size / BLOCK_SIZE) * BLOCK_SIZE;
|
|
240
|
-
//
|
|
240
|
+
// Refuse truncated archives upfront.
|
|
241
241
|
// The walker advances `pos` by the declared padded block size; if
|
|
242
242
|
// the buffer ends mid-body, extract() would silently slice a
|
|
243
243
|
// partial payload (header says 11 bytes, buffer holds 8 — without
|
|
@@ -272,7 +272,7 @@ function _encryptForRecipient(bytes, opts) {
|
|
|
272
272
|
};
|
|
273
273
|
}
|
|
274
274
|
if (r.publicKey) {
|
|
275
|
-
//
|
|
275
|
+
// B.crypto.encrypt falls back to
|
|
276
276
|
// ML-KEM-only when ecPublicKey is undefined (with a one-shot
|
|
277
277
|
// audit). For archive-wrap's recipient contract the hybrid leg
|
|
278
278
|
// (P-384 ECDH defence-in-depth backstop on top of ML-KEM-1024)
|
|
@@ -343,7 +343,7 @@ function sniffEnvelope(bytes) {
|
|
|
343
343
|
if (!Buffer.isBuffer(bytes) && !(bytes instanceof Uint8Array)) {
|
|
344
344
|
return "none";
|
|
345
345
|
}
|
|
346
|
-
//
|
|
346
|
+
// `Buffer.from(uint8Array)` copies
|
|
347
347
|
// the entire input, turning a constant-time 5-byte probe into an
|
|
348
348
|
// O(n) allocation. Use the zero-copy view form so the sniff is
|
|
349
349
|
// truly cheap regardless of input size.
|
|
@@ -351,7 +351,7 @@ function sniffEnvelope(bytes) {
|
|
|
351
351
|
? bytes
|
|
352
352
|
: Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
353
353
|
if (buf.length < 5) return "none";
|
|
354
|
-
//
|
|
354
|
+
// Match on the 5-byte ASCII magic
|
|
355
355
|
// alone, NOT on the full header (which requires version + saltLen
|
|
356
356
|
// bytes). A truncated envelope (`BAWRP` + nothing else) is still a
|
|
357
357
|
// recipient envelope; the unwrap call surfaces the truncation with
|
|
@@ -413,7 +413,7 @@ async function wrapWithPassphrase(bytes, opts) {
|
|
|
413
413
|
// alphabet. Operators sourcing passphrases from a random-bytes
|
|
414
414
|
// generator (high entropy density) pass without issue; operators
|
|
415
415
|
// typing dictionary phrases trip the gate.
|
|
416
|
-
//
|
|
416
|
+
// Typeof NaN === "number" passes
|
|
417
417
|
// typeof gate but bypasses downstream comparisons. Use isFinite
|
|
418
418
|
// so NaN / Infinity can't slip past the entropy gate.
|
|
419
419
|
var minEntropy;
|
|
@@ -516,7 +516,7 @@ async function unwrapWithPassphrase(sealed, opts) {
|
|
|
516
516
|
}
|
|
517
517
|
|
|
518
518
|
function _estimatePassphraseEntropyBits(passphrase) {
|
|
519
|
-
//
|
|
519
|
+
// Buffer passphrases (CSPRNG-
|
|
520
520
|
// generated random bytes) shouldn't be UTF-8 decoded for entropy
|
|
521
521
|
// estimation; the decoding artifacts (invalid sequences, BOM,
|
|
522
522
|
// surrogate pairs) make the alphabet-class measure unstable and
|