@blamejs/core 0.13.4 → 0.13.6

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 CHANGED
@@ -8,6 +8,10 @@ upgrading across more than a few patches at a time.
8
8
 
9
9
  ## v0.13.x
10
10
 
11
+ - v0.13.6 (2026-05-26) — **`b.ai.frontierModelProtocol` — California SB 53 frontier-AI obligations.** b.ai.frontierModelProtocol assesses a developer's obligations under California's Transparency in Frontier Artificial Intelligence Act — SB 53, Cal. Bus. & Prof. Code §22757.10, effective 2026-01-01 — from a model's training compute and the developer's revenue. It reports whether the model crosses the frontier threshold (more than 10^26 training FLOPs), whether the developer is a large frontier developer (prior-year revenue, with affiliates, above $500M), and the resulting obligations: every frontier developer must report critical safety incidents and publish a transparency report, and a large frontier developer must additionally publish an annual safety framework and disclose its catastrophic-risk assessment. Passing a candidate safety framework reports which required elements (risk identification, mitigation, governance, cybersecurity, standards alignment) are missing. b.ai.frontierModelProtocol.incidentReport validates a critical-incident type against the Act's four categories and computes the notification deadline to the California Office of Emergency Services — 15 days from discovery, or 24 hours when there is an imminent risk of death or serious physical injury. The ca-tfaia compliance posture was already in the catalog. **Added:** *`b.ai.frontierModelProtocol` — SB 53 threshold classification, obligations, and incident reporting* — `b.ai.frontierModelProtocol({ trainingFlops, annualRevenueUsd, framework? })` returns `isFrontierModel`, `isLargeFrontierDeveloper`, the `obligations` list, and (when a framework is supplied) its `frameworkGaps`. `b.ai.frontierModelProtocol.incidentReport({ type, discoveredAt, imminentRiskToLife? })` builds a critical-safety-incident report with the California OES recipient and a `dueAt` / `deadlineHours` computed from the 15-day (or 24-hour imminent-risk) statutory window; `type` must be one of the four categories in `INCIDENT_TYPES`. Throws `FrontierProtocolError` on malformed input.
12
+
13
+ - v0.13.5 (2026-05-26) — **`b.ai.aedtBiasAudit` — NYC Local Law 144 bias audit.** b.ai.aedtBiasAudit computes the bias-audit figures New York City Local Law 144 requires before an Automated Employment Decision Tool may screen candidates (NYC Admin. Code §20-870 et seq.; DCWP rules 6 RCNY §5-300). Given the per-category counts an independent auditor collected — selected/total for a pass-fail tool, or scored-above-the-overall-median/total for a continuous-score tool — it returns the selection (or scoring) rate, the impact ratio (each group's rate divided by the most-selected group's rate), and an adverse-impact flag (impact ratio below the EEOC four-fifths threshold of 0.8) for every group, across the sex, race/ethnicity, and intersectional dimensions, plus the most-selected group per dimension and an overall flag. Categories under 2% of the audited data are marked excluded per DCWP discretion. It is a pure calculation that produces exactly the figures the annual published summary must contain — the law mandates the calculation, not any particular remediation. The relevant compliance postures (nyc-ll144, and ca-tfaia for California SB 53) were already in the catalog. **Added:** *`b.ai.aedtBiasAudit` — Local Law 144 selection/scoring rates and four-fifths impact ratios* — `b.ai.aedtBiasAudit({ type, metadata, categories, minCategoryShare? })` where `type` is `"selection"` (group entries `{ selected, total }`) or `"scoring"` (`{ scoredAboveMedian, total }`). Returns per-group rate, impact ratio, and `adverseImpact` flag across the `sex`, `raceEthnicity`, and `intersectional` dimensions, plus the most-selected group per dimension and an `anyAdverseImpact` summary. Categories below `minCategoryShare` (2% default) are excluded from the impact-ratio basis. Throws `AedtBiasAuditError` on malformed input.
14
+
11
15
  - v0.13.4 (2026-05-26) — **`b.crdt` — conflict-free replicated data types.** b.crdt adds state-based Conflict-free Replicated Data Types: data structures that independent replicas update without coordination and still converge to the same value once they have exchanged state. Each type's merge is a join over a semilattice — commutative, associative, and idempotent — so replicas can merge in any order, any number of times, and agree, which makes these the substrate for active/active cluster state, offline-first clients that reconcile on reconnect, and eventually-consistent counters, sets, and maps. The release ships the full state-based family: grow-only and positive-negative counters (gCounter / pnCounter), grow-only, two-phase, and observed-remove sets (gSet / twoPSet / orSet), a last-write-wins register (lwwRegister), and an observed-remove map (orMap). Every type exposes the same contract — local mutators, merge(other) that returns a converged instance without mutating either operand, value() for the materialized value, and state() / fromState() for a JSON-serializable form to snapshot via b.archive or b.backup or ship to a peer — and carries a replicaId so per-replica contributions stay distinct. **Added:** *`b.crdt` — state-based CvRDT counters, sets, register, and map* — `b.crdt.gCounter` / `pnCounter` (grow-only and increment/decrement counters), `b.crdt.gSet` / `twoPSet` / `orSet` (grow-only, two-phase, and observed-remove sets — `orSet` supports re-add and resolves a concurrent add-vs-remove as add-wins), `b.crdt.lwwRegister` (last-write-wins with a deterministic replicaId tie-break), and `b.crdt.orMap` (observed-remove keys with last-write-wins values). Each exposes `merge` / `value` / `state` / `fromState` and converges by the CvRDT laws. `orSet` and `orMap` accept `tombstoneRetention` to bound tombstone memory against a remove flood.
12
16
 
13
17
  - v0.13.3 (2026-05-26) — **`b.crypto.xwing` — X-Wing hybrid post-quantum KEM.** b.crypto.xwing adds the X-Wing hybrid key-encapsulation mechanism (draft-connolly-cfrg-xwing-kem): it runs ML-KEM-768 and X25519 side by side and binds their shared secrets with SHA3-256, so an encapsulated key stays secure as long as either ML-KEM-768 or X25519 holds. That is the conservative shape for moving off classical ECDH today — a harvest-now-decrypt-later attacker must break the lattice KEM, and a hypothetical ML-KEM break still leaves X25519 standing. keygen() produces a 32-byte decapsulation seed and a 1216-byte public key; encapsulate(publicKey) returns a 1120-byte ciphertext and a 32-byte shared secret; decapsulate(secretKey, ciphertext) recovers it. The X-Wing combiner is frozen, but its specification is still an IETF Internet-Draft, so this primitive is marked experimental and sits beside the existing pre-RFC post-quantum HPKE drafts; it composes the framework's vendored ML-KEM-768 and X25519 with SHA3 and adds no new cryptographic core. The combiner is known-answer-tested byte-for-byte against the draft's definition. **Added:** *`b.crypto.xwing` — X-Wing hybrid PQ/T KEM (experimental)* — `keygen(seed?)` → `{ publicKey (1216 B), secretKey (32-byte seed) }`; `encapsulate(publicKey, eseed?)` → `{ ciphertext (1120 B), sharedSecret (32 B) }`; `decapsulate(secretKey, ciphertext)` → the 32-byte shared secret. Both `keygen` and `encapsulate` accept an optional seed for deterministic operation. The combiner — `SHA3-256(ssMLKEM ‖ ssX25519 ‖ ctX25519 ‖ pkX25519 ‖ label)` — is exposed as `combiner` for advanced use. Marked `experimental` while draft-connolly-cfrg-xwing-kem remains an Internet-Draft; the algorithm itself is frozen.
package/README.md CHANGED
@@ -189,6 +189,8 @@ The framework bundles the surface a typical Node app reaches for. Every primitiv
189
189
  - **Content provenance** — C2PA 2.1 + California SB-942 / AB-853 manifest builder for AI-generated media (provider, model id + version, timestamp, content ID, signed) (`b.contentCredentials`)
190
190
  - **AI usage quotas** — per-tenant / per-model budgets metered by tokens / requests / cost-usd / compute-hours over calendar-aligned windows, with an atomic conditional reserve (no charge-then-refund race) + hard/soft/warn enforcement and an optional cross-node store; defends OWASP LLM10:2025 unbounded consumption / denial-of-wallet (`b.ai.quota`)
191
191
  - **AI capability routing** — model-capability registry (context window / modalities / tool use / reasoning tier / cost rates) + a router that picks the cheapest model satisfying a request's requirements, refusing capability mismatches before the inference call (NIST AI RMF MAP + Model Cards); composes with `b.ai.quota` cost budgets (`b.ai.capability`)
192
+ - **AEDT bias audit** — NYC Local Law 144 bias-audit figures (`b.ai.aedtBiasAudit`): selection / scoring rates and EEOC four-fifths-rule impact ratios across sex, race/ethnicity, and their intersection, with the most-selected group and adverse-impact flags (impact ratio < 0.8) for the annual published summary; sub-2% categories excludable per DCWP §5-301
193
+ - **Frontier AI protocol** — California SB 53 (Transparency in Frontier AI Act) obligations (`b.ai.frontierModelProtocol`): classify the frontier-model (>10²⁶ training FLOPs) and large-frontier-developer (>$500M revenue) thresholds, enumerate the resulting obligations, check a safety framework for required elements, and build a critical-safety-incident report with the 15-day / 24-hour California OES notification deadline (`.incidentReport`)
192
194
  ### Compliance regimes
193
195
 
194
196
  - **Posture coordinator** — `b.compliance` cascades operator-declared regime into retention / audit / db / cryptoField via POSTURE_DEFAULTS:
package/index.js CHANGED
@@ -477,6 +477,8 @@ module.exports = {
477
477
  quota: require("./lib/ai-quota"),
478
478
  capability: require("./lib/ai-capability"),
479
479
  dp: require("./lib/ai-dp"),
480
+ aedtBiasAudit: require("./lib/ai-aedt-bias-audit"),
481
+ frontierModelProtocol: require("./lib/ai-frontier-protocol"),
480
482
  },
481
483
  promisePool: require("./lib/promise-pool"),
482
484
  sdNotify: require("./lib/sd-notify"),
@@ -0,0 +1,180 @@
1
+ "use strict";
2
+ /**
3
+ * @module b.ai.aedtBiasAudit
4
+ * @nav Compliance
5
+ * @title AEDT Bias Audit
6
+ *
7
+ * @intro
8
+ * Compute the bias-audit statistics New York City Local Law 144 requires
9
+ * before an Automated Employment Decision Tool (AEDT) may be used to screen
10
+ * candidates or employees. The law (NYC Admin. Code §20-870 et seq., in force
11
+ * since 2023-07-05; DCWP rules 6 RCNY §5-300 et seq.) requires an independent
12
+ * annual audit that reports, for each demographic category, the rate at which
13
+ * the tool selects (or scores above the median for) that group and the
14
+ * <em>impact ratio</em> — that group's rate divided by the rate of the
15
+ * most-selected group. An impact ratio below the four-fifths (0.8) threshold
16
+ * from the EEOC Uniform Guidelines flags potential adverse impact; the law
17
+ * requires the number to be calculated and published, not any particular
18
+ * remediation.
19
+ *
20
+ * The audit is computed across three dimensions: sex, race/ethnicity, and
21
+ * the intersection of the two, using the EEOC categories. Categories that
22
+ * make up less than 2% of the audited data may be excluded from the impact-
23
+ * ratio calculation at the auditor's discretion (DCWP §5-301). This primitive
24
+ * takes the per-category counts — selected/total for a pass-fail tool, or
25
+ * scored-above-the-overall-median/total for a continuous-score tool — and
26
+ * returns the selection (or scoring) rate, impact ratio, and adverse-impact
27
+ * flag per group, plus the most-selected group and an overall flag. It is a
28
+ * pure calculation: the operator supplies the data an independent auditor
29
+ * collected, and gets back the figures the published summary must contain.
30
+ *
31
+ * @card
32
+ * NYC Local Law 144 AEDT bias audit (`b.ai.aedtBiasAudit`) — selection /
33
+ * scoring rates and four-fifths-rule impact ratios across sex, race/ethnicity,
34
+ * and their intersection, with the most-selected group and adverse-impact
35
+ * flags for the published audit summary.
36
+ */
37
+
38
+ var validateOpts = require("./validate-opts");
39
+ var { defineClass } = require("./framework-error");
40
+
41
+ var AedtBiasAuditError = defineClass("AedtBiasAuditError", { alwaysPermanent: true });
42
+
43
+ var FOUR_FIFTHS = 4 / 5; // EEOC Uniform Guidelines four-fifths adverse-impact threshold
44
+ var DEFAULT_MIN_SHARE = 0.02; // DCWP §5-301 — categories under 2% may be excluded
45
+ var DIMENSIONS = ["sex", "raceEthnicity", "intersectional"];
46
+
47
+ function _str(v, label) {
48
+ if (typeof v !== "string" || v.length === 0) throw new AedtBiasAuditError("aedt/bad-metadata", "aedtBiasAudit: metadata." + label + " must be a non-empty string");
49
+ return v;
50
+ }
51
+ function _count(v, label) {
52
+ if (typeof v !== "number" || !isFinite(v) || v < 0 || Math.floor(v) !== v) throw new AedtBiasAuditError("aedt/bad-count", "aedtBiasAudit: " + label + " must be a non-negative integer");
53
+ return v;
54
+ }
55
+
56
+ // Reduce one dimension's per-group counts to the LL-144 figures.
57
+ function _auditDimension(groups, type, minShare) {
58
+ var names = Object.keys(groups);
59
+ var rows = [];
60
+ var dimensionTotal = 0;
61
+ var numeratorKey = type === "scoring" ? "scoredAboveMedian" : "selected";
62
+
63
+ names.forEach(function (name) {
64
+ var g = groups[name];
65
+ if (!g || typeof g !== "object") throw new AedtBiasAuditError("aedt/bad-count", "aedtBiasAudit: group '" + name + "' must be an object with " + numeratorKey + " + total");
66
+ var total = _count(g.total, name + ".total");
67
+ var num = _count(g[numeratorKey], name + "." + numeratorKey);
68
+ if (num > total) throw new AedtBiasAuditError("aedt/bad-count", "aedtBiasAudit: " + name + "." + numeratorKey + " (" + num + ") exceeds total (" + total + ")");
69
+ dimensionTotal += total;
70
+ rows.push({ category: name, total: total, _num: num, rate: total === 0 ? 0 : num / total });
71
+ });
72
+
73
+ // Exclude sub-threshold categories (auditor discretion), then find the
74
+ // most-selected group among those that remain.
75
+ var maxRate = 0;
76
+ var mostSelected = null;
77
+ rows.forEach(function (r) {
78
+ r.share = dimensionTotal === 0 ? 0 : r.total / dimensionTotal;
79
+ r.excluded = dimensionTotal > 0 && r.share < minShare;
80
+ if (!r.excluded && r.rate > maxRate) { maxRate = r.rate; mostSelected = r.category; }
81
+ });
82
+
83
+ rows.forEach(function (r) {
84
+ r.impactRatio = (r.excluded || maxRate === 0) ? null : r.rate / maxRate;
85
+ r.adverseImpact = r.impactRatio !== null && r.impactRatio < FOUR_FIFTHS;
86
+ delete r._num;
87
+ });
88
+ // Stable order: highest rate first, then category name.
89
+ rows.sort(function (a, b) { return b.rate - a.rate || (a.category < b.category ? -1 : a.category > b.category ? 1 : 0); });
90
+ return { rows: rows, mostSelected: mostSelected };
91
+ }
92
+
93
+ /**
94
+ * @primitive b.ai.aedtBiasAudit
95
+ * @signature b.ai.aedtBiasAudit(opts)
96
+ * @since 0.13.5
97
+ * @status stable
98
+ * @compliance nyc-ll144, soc2
99
+ * @related b.ai.disclosure.applyAll, b.ai.disclosure.chatbot
100
+ *
101
+ * Compute the NYC Local Law 144 bias-audit figures from per-category counts.
102
+ * <code>type</code> is <code>"selection"</code> for a pass-fail tool (each
103
+ * group entry is <code>{ selected, total }</code>) or <code>"scoring"</code>
104
+ * for a continuous-score tool (<code>{ scoredAboveMedian, total }</code>, where
105
+ * the count is candidates scoring above the <em>overall</em> median). Returns
106
+ * the selection/scoring rate, impact ratio (group rate ÷ most-selected group's
107
+ * rate), and an <code>adverseImpact</code> flag (impact ratio &lt; 0.8) per
108
+ * group, across the <code>sex</code>, <code>raceEthnicity</code>, and
109
+ * <code>intersectional</code> dimensions, plus the most-selected group per
110
+ * dimension. Categories under <code>minCategoryShare</code> (2% by default) are
111
+ * marked <code>excluded</code> and left out of the impact-ratio basis. Throws
112
+ * <code>AedtBiasAuditError</code> on malformed input. The result is the data an
113
+ * employer must publish; the law mandates the calculation, not any remediation.
114
+ *
115
+ * @opts
116
+ * type: string, // "selection" | "scoring" (required)
117
+ * metadata: object, // { tool, auditor, auditDate, distributionDate? } (tool/auditor/auditDate required)
118
+ * categories: object, // { sex?, raceEthnicity?, intersectional? } → { <group>: { selected|scoredAboveMedian, total } }
119
+ * minCategoryShare: number, // default: 0.02 (DCWP §5-301 — sub-2% categories may be excluded)
120
+ *
121
+ * @example
122
+ * var report = b.ai.aedtBiasAudit({
123
+ * type: "selection",
124
+ * metadata: { tool: "ResumeRanker v3", auditor: "Acme Audit LLC", auditDate: "2026-05-26" },
125
+ * categories: { sex: { Male: { selected: 60, total: 100 }, Female: { selected: 42, total: 100 } } },
126
+ * });
127
+ * report.results.sex[1].impactRatio; // → 0.7 (Female: 42% / 60%)
128
+ * report.results.sex[1].adverseImpact; // → true (below the 0.8 four-fifths threshold)
129
+ */
130
+ function aedtBiasAudit(opts) {
131
+ opts = opts || {};
132
+ // Surface an unknown/typoed option as this primitive's own error type rather
133
+ // than the generic Error validateOpts throws, so the malformed-input contract
134
+ // (AedtBiasAuditError / e.code) holds for every bad-config path.
135
+ try { validateOpts(opts, ["type", "metadata", "categories", "minCategoryShare"], "aedtBiasAudit"); }
136
+ catch (e) { throw new AedtBiasAuditError("aedt/bad-opts", e && e.message || "aedtBiasAudit: invalid options"); }
137
+ if (opts.type !== "selection" && opts.type !== "scoring") throw new AedtBiasAuditError("aedt/bad-type", "aedtBiasAudit: type must be 'selection' or 'scoring'");
138
+
139
+ var md = opts.metadata || {};
140
+ var metadata = {
141
+ tool: _str(md.tool, "tool"),
142
+ auditor: _str(md.auditor, "auditor"),
143
+ auditDate: _str(md.auditDate, "auditDate"),
144
+ distributionDate: md.distributionDate != null ? _str(md.distributionDate, "distributionDate") : null,
145
+ };
146
+
147
+ var minShare = opts.minCategoryShare != null ? opts.minCategoryShare : DEFAULT_MIN_SHARE;
148
+ if (typeof minShare !== "number" || !isFinite(minShare) || minShare < 0 || minShare >= 1) throw new AedtBiasAuditError("aedt/bad-share", "aedtBiasAudit: minCategoryShare must be a number in [0, 1)");
149
+
150
+ var cats = opts.categories || {};
151
+ var present = DIMENSIONS.filter(function (d) { return cats[d] && typeof cats[d] === "object" && Object.keys(cats[d]).length > 0; });
152
+ if (present.length === 0) throw new AedtBiasAuditError("aedt/no-categories", "aedtBiasAudit: at least one of sex / raceEthnicity / intersectional must carry group counts");
153
+
154
+ var results = {};
155
+ var mostSelected = {};
156
+ var adverseImpactGroups = [];
157
+ present.forEach(function (dim) {
158
+ var out = _auditDimension(cats[dim], opts.type, minShare);
159
+ results[dim] = out.rows;
160
+ mostSelected[dim] = out.mostSelected;
161
+ out.rows.forEach(function (r) { if (r.adverseImpact) adverseImpactGroups.push({ dimension: dim, category: r.category, impactRatio: r.impactRatio }); });
162
+ });
163
+
164
+ return {
165
+ type: opts.type,
166
+ metadata: metadata,
167
+ results: results,
168
+ summary: {
169
+ mostSelected: mostSelected,
170
+ adverseImpactGroups: adverseImpactGroups,
171
+ anyAdverseImpact: adverseImpactGroups.length > 0,
172
+ fourFifthsThreshold: FOUR_FIFTHS,
173
+ },
174
+ };
175
+ }
176
+
177
+ aedtBiasAudit.FOUR_FIFTHS = FOUR_FIFTHS;
178
+ aedtBiasAudit.AedtBiasAuditError = AedtBiasAuditError;
179
+
180
+ module.exports = aedtBiasAudit;
@@ -0,0 +1,196 @@
1
+ "use strict";
2
+ /**
3
+ * @module b.ai.frontierModelProtocol
4
+ * @nav Compliance
5
+ * @title Frontier AI Protocol
6
+ *
7
+ * @intro
8
+ * Assess a developer's obligations under California's Transparency in
9
+ * Frontier Artificial Intelligence Act — SB 53, Cal. Bus. &amp; Prof. Code
10
+ * §22757.10 et seq., signed 2025-09-29 and effective 2026-01-01 — and build
11
+ * the critical-safety-incident report the law requires. SB 53 attaches
12
+ * obligations by two thresholds: a <em>frontier model</em> is one trained
13
+ * with more than 10<sup>26</sup> floating-point operations (including
14
+ * cumulative fine-tuning), and a <em>large frontier developer</em> is a
15
+ * frontier developer whose annual revenue, with affiliates, exceeded
16
+ * $500,000,000 in the prior calendar year. Frontier developers must report
17
+ * critical safety incidents; large frontier developers must additionally
18
+ * publish an annual frontier-AI safety framework.
19
+ *
20
+ * <code>frontierModelProtocol(opts)</code> takes the model's training compute
21
+ * and the developer's revenue and returns which thresholds are crossed and
22
+ * the resulting obligations, optionally checking that a supplied safety
23
+ * framework carries the elements the Act expects (risk identification,
24
+ * mitigation, governance, cybersecurity, and alignment with a recognized
25
+ * standard such as the NIST AI RMF or ISO/IEC 42001).
26
+ * <code>frontierModelProtocol.incidentReport(opts)</code> validates a
27
+ * critical-incident type against the Act's four definitions and computes the
28
+ * notification deadline: a routine report goes to the California Office of
29
+ * Emergency Services within 15 days of discovery; an imminent risk of death
30
+ * or serious physical injury is reported to an applicable authority within 24
31
+ * hours.
32
+ *
33
+ * @card
34
+ * California SB 53 frontier-AI protocol (`b.ai.frontierModelProtocol`) —
35
+ * classify frontier-model / large-developer thresholds (10²⁶ FLOPs, $500M
36
+ * revenue), enumerate obligations, and build the critical-safety-incident
37
+ * report with the 15-day / 24-hour OES notification deadline.
38
+ */
39
+
40
+ var C = require("./constants");
41
+ var { defineClass } = require("./framework-error");
42
+
43
+ var FrontierProtocolError = defineClass("FrontierProtocolError", { alwaysPermanent: true });
44
+
45
+ // SB 53 thresholds.
46
+ var FRONTIER_FLOP_THRESHOLD = 1e26; // > 10^26 training FLOPs → frontier model
47
+ var LARGE_DEVELOPER_REVENUE_USD = 5e8; // > $500,000,000 prior-year revenue → large developer
48
+ var INCIDENT_DEADLINE_MS = C.TIME.days(15); // report to CA OES within 15 days of discovery
49
+ var IMMINENT_DEADLINE_MS = C.TIME.hours(24); // within 24 hours if imminent risk to life
50
+
51
+ // The four critical safety incident categories (Cal. Bus. & Prof. Code §22757.10).
52
+ var CRITICAL_INCIDENT_TYPES = {
53
+ "weights-exfiltration-harm": "Unauthorized access, modification, or exfiltration of frontier-model weights resulting in death or bodily injury",
54
+ "catastrophic-risk-materialization": "Harm resulting from the materialization of a catastrophic risk (including loss of life or property from a dangerous capability)",
55
+ "loss-of-control-harm": "Loss of control of a frontier model causing death or bodily injury",
56
+ "deceptive-control-subversion": "A frontier model using deceptive techniques to subvert developer controls, demonstrating materially increased catastrophic risk",
57
+ };
58
+
59
+ // Elements a large frontier developer's published safety framework must address.
60
+ var REQUIRED_FRAMEWORK_ELEMENTS = ["riskIdentification", "riskMitigation", "governance", "cybersecurity", "standardsAlignment"];
61
+
62
+ function _posNum(v, label) {
63
+ if (typeof v !== "number" || !isFinite(v) || v < 0) throw new FrontierProtocolError("frontier/bad-value", "frontierModelProtocol: " + label + " must be a non-negative finite number");
64
+ return v;
65
+ }
66
+
67
+ /**
68
+ * @primitive b.ai.frontierModelProtocol
69
+ * @signature b.ai.frontierModelProtocol(opts)
70
+ * @since 0.13.6
71
+ * @status stable
72
+ * @compliance ca-tfaia, soc2
73
+ * @related b.ai.aedtBiasAudit, b.ai.disclosure.applyAll
74
+ *
75
+ * Determine SB 53 obligations from a model's training compute and the
76
+ * developer's revenue. Returns <code>isFrontierModel</code> (training FLOPs
77
+ * above 10<sup>26</sup>), <code>isLargeFrontierDeveloper</code> (a frontier
78
+ * developer with revenue above $500M), and the resulting
79
+ * <code>obligations</code>. When <code>framework</code> is supplied, its
80
+ * required elements are checked and reported in <code>frameworkGaps</code>.
81
+ * Throws <code>FrontierProtocolError</code> on malformed input.
82
+ *
83
+ * @opts
84
+ * trainingFlops: number, // total training FLOPs incl. fine-tuning (required)
85
+ * annualRevenueUsd: number, // developer prior-year revenue with affiliates (default: 0)
86
+ * framework: object, // optional safety framework to check for required elements
87
+ *
88
+ * @example
89
+ * var p = b.ai.frontierModelProtocol({ trainingFlops: 5e26, annualRevenueUsd: 1e9 });
90
+ * p.isFrontierModel; // → true
91
+ * p.isLargeFrontierDeveloper; // → true
92
+ * p.obligations; // → ["report-critical-safety-incidents", "publish-annual-safety-framework", ...]
93
+ */
94
+ function frontierModelProtocol(opts) {
95
+ opts = opts || {};
96
+ if (typeof opts !== "object") throw new FrontierProtocolError("frontier/bad-opts", "frontierModelProtocol: opts must be an object");
97
+ var allowed = { trainingFlops: 1, annualRevenueUsd: 1, framework: 1 };
98
+ Object.keys(opts).forEach(function (k) { if (!allowed[k]) throw new FrontierProtocolError("frontier/bad-opts", "frontierModelProtocol: unknown option '" + k + "'"); });
99
+
100
+ var flops = _posNum(opts.trainingFlops, "trainingFlops");
101
+ var revenue = opts.annualRevenueUsd != null ? _posNum(opts.annualRevenueUsd, "annualRevenueUsd") : 0;
102
+
103
+ var isFrontierModel = flops > FRONTIER_FLOP_THRESHOLD;
104
+ var isLargeFrontierDeveloper = isFrontierModel && revenue > LARGE_DEVELOPER_REVENUE_USD;
105
+
106
+ var obligations = [];
107
+ if (isFrontierModel) {
108
+ obligations.push("report-critical-safety-incidents");
109
+ obligations.push("publish-transparency-report");
110
+ }
111
+ if (isLargeFrontierDeveloper) {
112
+ obligations.push("publish-annual-safety-framework");
113
+ obligations.push("disclose-catastrophic-risk-assessment");
114
+ }
115
+
116
+ var out = {
117
+ isFrontierModel: isFrontierModel,
118
+ isLargeFrontierDeveloper: isLargeFrontierDeveloper,
119
+ thresholds: { frontierFlops: FRONTIER_FLOP_THRESHOLD, largeDeveloperRevenueUsd: LARGE_DEVELOPER_REVENUE_USD },
120
+ obligations: obligations,
121
+ };
122
+
123
+ if (opts.framework != null) {
124
+ if (typeof opts.framework !== "object") throw new FrontierProtocolError("frontier/bad-value", "frontierModelProtocol: framework must be an object");
125
+ var gaps = REQUIRED_FRAMEWORK_ELEMENTS.filter(function (el) { return !opts.framework[el]; });
126
+ out.frameworkGaps = gaps;
127
+ out.frameworkComplete = gaps.length === 0;
128
+ }
129
+ return out;
130
+ }
131
+
132
+ /**
133
+ * @primitive b.ai.frontierModelProtocol.incidentReport
134
+ * @signature b.ai.frontierModelProtocol.incidentReport(opts)
135
+ * @since 0.13.6
136
+ * @status stable
137
+ * @compliance ca-tfaia, soc2
138
+ * @related b.ai.frontierModelProtocol, b.ai.aedtBiasAudit
139
+ *
140
+ * Build a critical-safety-incident report and compute its notification
141
+ * deadline to the California Office of Emergency Services. <code>type</code>
142
+ * must be one of the Act's four categories; <code>discoveredAt</code> is when
143
+ * the incident was discovered. The deadline is 15 days from discovery, or 24
144
+ * hours when <code>imminentRiskToLife</code> is set. Returns the structured
145
+ * report with <code>dueAt</code> and <code>deadlineHours</code>. Throws
146
+ * <code>FrontierProtocolError</code> on an unknown type or bad timestamp.
147
+ *
148
+ * @opts
149
+ * type: string, // one of b.ai.frontierModelProtocol.INCIDENT_TYPES (required)
150
+ * discoveredAt: Date, // discovery time (Date or epoch-ms; default: now)
151
+ * imminentRiskToLife: boolean, // true → 24-hour deadline (default: false → 15 days)
152
+ * description: string, // free-text incident description (optional)
153
+ *
154
+ * @example
155
+ * var r = b.ai.frontierModelProtocol.incidentReport({
156
+ * type: "loss-of-control-harm", discoveredAt: new Date("2026-06-01T00:00:00Z"),
157
+ * });
158
+ * r.deadlineHours; // → 360 (15 days)
159
+ * r.recipient; // → "California Office of Emergency Services"
160
+ */
161
+ function incidentReport(opts) {
162
+ opts = opts || {};
163
+ if (typeof opts !== "object") throw new FrontierProtocolError("frontier/bad-opts", "incidentReport: opts must be an object");
164
+ if (!CRITICAL_INCIDENT_TYPES[opts.type]) throw new FrontierProtocolError("frontier/bad-incident-type", "incidentReport: type must be one of " + Object.keys(CRITICAL_INCIDENT_TYPES).join(", "));
165
+
166
+ var discoveredMs;
167
+ if (opts.discoveredAt == null) discoveredMs = Date.now();
168
+ else if (opts.discoveredAt instanceof Date) discoveredMs = opts.discoveredAt.getTime();
169
+ else discoveredMs = Number(opts.discoveredAt);
170
+ if (!isFinite(discoveredMs) || discoveredMs < 0) throw new FrontierProtocolError("frontier/bad-value", "incidentReport: discoveredAt must be a Date or non-negative epoch-ms");
171
+
172
+ var imminent = opts.imminentRiskToLife === true;
173
+ var windowMs = imminent ? IMMINENT_DEADLINE_MS : INCIDENT_DEADLINE_MS;
174
+
175
+ return {
176
+ type: opts.type,
177
+ typeDescription: CRITICAL_INCIDENT_TYPES[opts.type],
178
+ description: typeof opts.description === "string" ? opts.description : null,
179
+ imminentRiskToLife: imminent,
180
+ discoveredAt: new Date(discoveredMs).toISOString(),
181
+ dueAt: new Date(discoveredMs + windowMs).toISOString(),
182
+ deadlineHours: windowMs / C.TIME.hours(1),
183
+ // §22757.13: the routine report goes to OES within 15 days; an imminent
184
+ // risk of death or serious physical injury is reported to an applicable
185
+ // authority within 24 hours, which the operator selects for the incident.
186
+ recipient: imminent ? "An applicable authority with jurisdiction (e.g. law enforcement or a public-safety agency)" : "California Office of Emergency Services",
187
+ citation: "Cal. Bus. & Prof. Code §22757.13 (SB 53)",
188
+ };
189
+ }
190
+
191
+ frontierModelProtocol.incidentReport = incidentReport;
192
+ frontierModelProtocol.INCIDENT_TYPES = Object.keys(CRITICAL_INCIDENT_TYPES);
193
+ frontierModelProtocol.REQUIRED_FRAMEWORK_ELEMENTS = REQUIRED_FRAMEWORK_ELEMENTS;
194
+ frontierModelProtocol.FrontierProtocolError = FrontierProtocolError;
195
+
196
+ module.exports = frontierModelProtocol;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/core",
3
- "version": "0.13.4",
3
+ "version": "0.13.6",
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.5",
5
- "serialNumber": "urn:uuid:832111fb-8b91-4fb8-bb5f-c491bae476c5",
5
+ "serialNumber": "urn:uuid:4234e2f1-7c10-42c0-8f19-b429101d42d1",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-05-27T01:19:38.243Z",
8
+ "timestamp": "2026-05-27T03:23:03.075Z",
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.13.4",
22
+ "bom-ref": "@blamejs/core@0.13.6",
23
23
  "type": "application",
24
24
  "name": "blamejs",
25
- "version": "0.13.4",
25
+ "version": "0.13.6",
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.13.4",
29
+ "purl": "pkg:npm/%40blamejs/core@0.13.6",
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.13.4",
57
+ "ref": "@blamejs/core@0.13.6",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]