@blamejs/core 0.11.41 → 0.11.43

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.11.x
10
10
 
11
+ - v0.11.43 (2026-05-22) — **Wiki nav: alphabetical sidebar + dedup drifted category labels.** The wiki sidebar now lists categories alphabetically (case-insensitive) instead of in editorial order. Welcome remains pinned first (landing page); Other and Reference remain pinned last (catch-all groups). Three category-label dups are also consolidated at the source comment-blocks: `Agent Protocols` (one entry — `a2a-tasks`) joins `Agent` with the rest of the agent substrate; `Networking` (two entries — `stream-throttle`, `web-push-vapid`) joins `Network`; `Audit & Compliance` (one entry — `nist-crosswalk`) joins `Compliance`. New `@nav` categories now land in their alphabetical slot automatically without a site.config edit. **Changed:** *Wiki sidebar group order alphabetical* — Replaces the curated `GROUP_ORDER` editorial list in `examples/wiki/site.config.js` with an alphabetical sort. Pinning preserved: `Welcome` always first; `Other` and `Reference` always last. Adding a new `@nav` category to any `lib/*.js` `@module` block now slots into its alphabetical position automatically — no separate site.config update required. · *Category consolidation: Agent Protocols → Agent* — `a2a-tasks` was the sole entry under `Agent Protocols`. Moved to `Agent` to live alongside the orchestrator / saga / idempotency / event-bus / posture-chain / stream / trace / tenant / snapshot / fsm primitives — the W3C A2A task surface is part of the same agent-substrate concern. · *Category consolidation: Networking → Network* — `stream-throttle` and `web-push-vapid` were tagged `Networking` while every other network-layer primitive used `Network`. Drift cleanup — both now live under `Network`. · *Category consolidation: Audit & Compliance → Compliance* — `nist-crosswalk` was the sole entry under `Audit & Compliance`. Moved to `Compliance` alongside the other regulatory primitives. **Detectors:** *Nav-category allowlist gate* — `testNavCategoryAllowlist` in the codebase-patterns runner walks every `lib/*.js` `@module` block and refuses any `@nav` value not in the canonical category list. Adding a new sidebar category is a deliberate edit — it lands in `NAV_ALLOWLIST` + the operator-facing `FIRST_GROUPS` / `LAST_GROUPS` pin list in `examples/wiki/site.config.js` at the same time. Prevents the Networking-vs-Network and Agent-vs-Agent-Protocols dup classes from re-emerging silently.
12
+
13
+ - v0.11.42 (2026-05-22) — **`b.calendar` JSCalendar Group objects (RFC 8984 §1.4.4).** Closes the v0.11.31 deferral on JSCalendar Group. `b.calendar.validate` now recognises `@type: "Group"` as a container envelope for multiple Event / Task / Note entries that share a logical name + categories. `b.calendar.toIcal` emits a single VCALENDAR wrap containing every entry's component in declared order (VEVENT for Event, VTODO for Task, VJOURNAL for Note). Group is JSCalendar-only — iCalendar has no envelope-level metadata for Group.name / Group.categories, so a Group's metadata is dropped on the iCal-side of a round-trip; operators preserving Group state across systems use the JSON-native surface. **Added:** *`@type: "Group"` envelope (RFC 8984 §1.4.4)* — A Group carries `uid`, `updated`, `entries` (required non-empty array), and optional `name`, `description`, `categories` (String-keyed Boolean set), and `source` (URI string). Entries are validated recursively — each entry must be a valid Event / Task / Note per its own type rules. Groups nesting Groups is refused (RFC 8984 does not define a nesting semantic). · *`b.calendar.toIcal` emits single VCALENDAR for Group* — When `@type === "Group"`, toIcal emits one BEGIN:VCALENDAR / END:VCALENDAR envelope containing every entry's component in declared order. The Group's own uid / updated / name / description / categories are NOT round-tripped to iCalendar (RFC 5545 has no envelope-level metadata for these); operators preserving Group state across a round-trip use the JSON-native JSCalendar surface. · *Refusal vocabulary* — New structured `CalendarError` codes: `calendar/bad-entries` (entries missing, empty, non-array, or any entry malformed / Group-typed), `calendar/bad-group` (entry-specific field like start / duration / due / progress / recurrenceRules set on the Group envelope), `calendar/bad-categories` (non-object categories or non-`true` value), `calendar/bad-source` (non-string source). · *`JSCAL_TYPES.Group` export* — `b.calendar.JSCAL_TYPES.Group === "Group"` exposes the discriminator string for operator-side type-dispatch. **References:** [RFC 8984 §1.4.4 (JSCalendar Group)](https://www.rfc-editor.org/rfc/rfc8984.html#section-1.4.4) · [RFC 5545 §3.4 (VCALENDAR envelope)](https://www.rfc-editor.org/rfc/rfc5545.html#section-3.4)
14
+
11
15
  - v0.11.41 (2026-05-21) — **`b.calendar.expandRecurrence` picks up BYSETPOS (RFC 5545 §3.3.10).** Closes the v0.11.31 deferral on BYSETPOS — the recurrence filter that picks the Nth candidate from a BY*-filtered set within a FREQ interval. Common operator patterns now work directly: `FREQ=MONTHLY;BYDAY=FR;BYSETPOS=-1` for last Friday of each month, `FREQ=MONTHLY;BYDAY=TU;BYSETPOS=2` for second Tuesday, `FREQ=YEARLY;BYMONTH=10;BYDAY=SU;BYSETPOS=1` for first Sunday of October (the DST-end announcement pattern). Supported for `FREQ=MONTHLY` / `YEARLY` / `WEEKLY` at day-granularity (time-of-day inherited from `start`). `FREQ=DAILY` + BYSETPOS is refused with `calendar/bad-recurrence` since the semantics aren't meaningful at sub-day frequency. **Added:** *`bySetPos` filter on RecurrenceRule* — `recurrenceRules[i].bySetPos: [1, -1, 2]` picks the listed positions from the BY*-filtered candidate set within each FREQ interval. Positive values are 1-indexed from the start of the period; negative values count from the end. Multiple positions emit per period (e.g. `[1, -1]` emits both the first and last matching day of each month). Out-of-range positions silently drop per RFC 5545's tolerant grammar. · *MONTHLY / YEARLY / WEEKLY support* — BYSETPOS expands within month / year / WKST-aligned-week boundaries. For each period, all day-level candidates that pass the existing BY* filters (byDay / byMonth / byMonthDay / byWeekNo / byYearDay) are enumerated, sorted ascending, then indexed by `bySetPos`. The expand loop's step budget (`MAX_EXPAND_INSTANCES * 366`) is shared across periods so the BYSETPOS path can't outrun the v0.11.31 DoS bound. · *DAILY frequency refused* — `FREQ=DAILY` + BYSETPOS throws `calendar/bad-recurrence` at expand time. The combination has no meaningful per-period set semantics (a day has no sub-day BY* set to pick from in the v1 day-granularity model). Operators with a sub-day BYSETPOS use case use `FREQ=HOURLY` semantics directly without BYSETPOS, or open an issue with the use case. **Security:** *Step budget shared with non-BYSETPOS path* — BYSETPOS enumeration decrements the same `MAX_EXPAND_INSTANCES * 366` step budget the non-BYSETPOS path uses, and the per-period day-loop has a hard 400-iteration safety cap (covers max-366-days in a leap year + slack). Adversarial events combining many rules + sparse BY* filters + BYSETPOS can't outpace the original single-rule DoS bound. **References:** [RFC 5545 §3.3.10 (RRULE — BYSETPOS)](https://www.rfc-editor.org/rfc/rfc5545.html#section-3.3.10) · [RFC 8984 §4.3.2 (JSCalendar RecurrenceRule)](https://www.rfc-editor.org/rfc/rfc8984.html#section-4.3.2)
12
16
 
13
17
  - v0.11.40 (2026-05-21) — **Wiki Docker default port moves from 8080 → 3008.** The `examples/wiki` Docker stack now defaults to port 3008 throughout — `WIKI_PORT`, the Dockerfile `EXPOSE` directive, the in-container HEALTHCHECK URL, the dev `docker-compose.yml` host mapping, the Caddy reverse-proxy upstream, and the `server.js` + `lib/build-app.js` code defaults. Production deployments (`docker-compose.prod.yml`) are operator-invisible since Caddy fronts the container on 80/443 — the upstream port is never exposed to the host. Dev users hitting the wiki container directly now use `http://localhost:3008`. Operators who set `WIKI_PORT` explicitly in their `.env` keep working unchanged. **Changed:** *Wiki default port 8080 → 3008* — Default `WIKI_PORT` moves from 8080 (HTTP-alt, frequently collides with Tomcat / Jenkins / Confluence / Spring-Boot defaults) to 3008 (IANA-unassigned, no service-convention collision). The new default applies consistently across: `examples/wiki/Dockerfile` (`ENV WIKI_PORT`, `EXPOSE`, HEALTHCHECK URL), `examples/wiki/server.js`, `examples/wiki/lib/build-app.js`, `examples/wiki/docker-compose.yml` (host mapping `3008:3008`), `examples/wiki/docker-compose.prod.yml` (internal-network expose), `examples/wiki/Caddyfile` (upstream resolver), `examples/wiki/README.md`, `examples/wiki/DEPLOY.md`. · *Operator action* — Dev users with `localhost:8080` bookmarks, curl scripts, host-firewall allowlists, or IDE port forwarders pointed at the wiki need to switch to `localhost:3008`. Operators who set `WIKI_PORT=8080` explicitly in their `.env` keep working unchanged. Production deployments behind Caddy see no change — the upstream port is internal-network only. **References:** [IANA Service Name and Port Number Registry](https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml)
package/lib/a2a-tasks.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  /**
3
3
  * @module b.a2a
4
- * @nav Agent Protocols
4
+ * @nav Agent
5
5
  * @title A2A Tasks
6
6
  * @order 600
7
7
  *
package/lib/calendar.js CHANGED
@@ -123,9 +123,9 @@ function validate(jsCal) {
123
123
  "b.calendar.validate: input must be a JSCalendar object");
124
124
  }
125
125
  var t = jsCal["@type"];
126
- if (t !== JSCAL_TYPES.Event && t !== JSCAL_TYPES.Task && t !== JSCAL_TYPES.Note) {
126
+ if (t !== JSCAL_TYPES.Event && t !== JSCAL_TYPES.Task && t !== JSCAL_TYPES.Note && t !== JSCAL_TYPES.Group) {
127
127
  throw new CalendarError("calendar/bad-type",
128
- "b.calendar.validate: @type must be 'Event' or 'Task' or 'Note' (got " + JSON.stringify(t) + ")");
128
+ "b.calendar.validate: @type must be 'Event', 'Task', 'Note' or 'Group' (got " + JSON.stringify(t) + ")");
129
129
  }
130
130
  if (typeof jsCal.uid !== "string" || jsCal.uid.length === 0) {
131
131
  throw new CalendarError("calendar/no-uid",
@@ -224,7 +224,68 @@ function validate(jsCal) {
224
224
  Object.keys(JSCAL_NOTE_STATUS).join(" | ") + " (RFC 5545 §3.8.1.11 VJOURNAL STATUS)");
225
225
  }
226
226
  }
227
- if (jsCal.recurrenceRules !== undefined) {
227
+ if (t === JSCAL_TYPES.Group) {
228
+ // RFC 8984 §1.4.4 — a Group is a container envelope for multiple
229
+ // Event / Task / Note entries that share a logical name +
230
+ // categories. Group itself does not carry start / duration / due
231
+ // / progress — those live on the entries. `entries` MUST be a
232
+ // non-empty array; every entry MUST be a valid Event / Task /
233
+ // Note (Groups nesting Groups is refused — the spec does not
234
+ // define a nesting recursion semantic).
235
+ if (!Array.isArray(jsCal.entries) || jsCal.entries.length === 0) {
236
+ throw new CalendarError("calendar/bad-entries",
237
+ "b.calendar.validate: Group.entries MUST be a non-empty array (RFC 8984 §1.4.4)");
238
+ }
239
+ for (var gei = 0; gei < jsCal.entries.length; gei += 1) {
240
+ var entry = jsCal.entries[gei];
241
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
242
+ throw new CalendarError("calendar/bad-entries",
243
+ "b.calendar.validate: Group.entries[" + gei + "] MUST be an object");
244
+ }
245
+ var et = entry["@type"];
246
+ if (et !== JSCAL_TYPES.Event && et !== JSCAL_TYPES.Task && et !== JSCAL_TYPES.Note) {
247
+ throw new CalendarError("calendar/bad-entries",
248
+ "b.calendar.validate: Group.entries[" + gei + "].@type MUST be 'Event', 'Task' or 'Note' " +
249
+ "(got " + JSON.stringify(et) + ") — Groups do not nest");
250
+ }
251
+ // Recurse into each entry so per-type shape rules apply.
252
+ validate(entry);
253
+ }
254
+ if (jsCal.source !== undefined && typeof jsCal.source !== "string") {
255
+ throw new CalendarError("calendar/bad-source",
256
+ "b.calendar.validate: Group.source MUST be a string URI when present (RFC 8984 §1.4.4)");
257
+ }
258
+ if (jsCal.categories !== undefined) {
259
+ // Codex P1 — `typeof null === "object"` would let `categories:
260
+ // null` through this check, and the subsequent Object.keys
261
+ // throws a raw TypeError instead of a structured CalendarError.
262
+ // Refuse null explicitly so callers depending on the
263
+ // `calendar/bad-categories` refusal code stay stable.
264
+ if (jsCal.categories === null || typeof jsCal.categories !== "object" ||
265
+ Array.isArray(jsCal.categories)) {
266
+ throw new CalendarError("calendar/bad-categories",
267
+ "b.calendar.validate: Group.categories MUST be a String-keyed Boolean object (RFC 8984 §1.4.4)");
268
+ }
269
+ var catKeys = Object.keys(jsCal.categories);
270
+ for (var ci = 0; ci < catKeys.length; ci += 1) {
271
+ if (jsCal.categories[catKeys[ci]] !== true) {
272
+ throw new CalendarError("calendar/bad-categories",
273
+ "b.calendar.validate: Group.categories['" + catKeys[ci] + "'] MUST be `true` (boolean set per RFC 8984 §1.4.4)");
274
+ }
275
+ }
276
+ }
277
+ // Group itself MUST NOT carry the entry-specific fields. Refuse
278
+ // explicit setting so operators don't accidentally model entry
279
+ // state on the envelope.
280
+ if (jsCal.start !== undefined || jsCal.duration !== undefined || jsCal.due !== undefined ||
281
+ jsCal.progress !== undefined || jsCal.percentComplete !== undefined ||
282
+ jsCal.progressUpdated !== undefined || jsCal.recurrenceRules !== undefined) {
283
+ throw new CalendarError("calendar/bad-group",
284
+ "b.calendar.validate: Group MUST NOT carry start / duration / due / progress / " +
285
+ "percentComplete / progressUpdated / recurrenceRules — those live on the entries");
286
+ }
287
+ }
288
+ if (t !== JSCAL_TYPES.Group && jsCal.recurrenceRules !== undefined) {
228
289
  if (!Array.isArray(jsCal.recurrenceRules)) {
229
290
  throw new CalendarError("calendar/bad-recurrence",
230
291
  "b.calendar.validate: recurrenceRules MUST be an array of RecurrenceRule");
@@ -243,7 +304,7 @@ function validate(jsCal) {
243
304
  }
244
305
  }
245
306
  if (jsCal.alerts !== undefined) {
246
- if (typeof jsCal.alerts !== "object" || Array.isArray(jsCal.alerts)) {
307
+ if (jsCal.alerts === null || typeof jsCal.alerts !== "object" || Array.isArray(jsCal.alerts)) {
247
308
  throw new CalendarError("calendar/bad-alerts",
248
309
  "b.calendar.validate: alerts MUST be an object map keyed by alert-id");
249
310
  }
@@ -328,6 +389,33 @@ function fromIcal(text, opts) {
328
389
  function toIcal(jsCal, opts) {
329
390
  validate(jsCal);
330
391
  var prodid = (opts && opts.prodid) || "-//blamejs//Calendar//EN";
392
+ // RFC 8984 §1.4.4 — a Group emits a single VCALENDAR envelope
393
+ // containing every entry's component in declared order. The Group's
394
+ // own uid + updated + name + description are NOT round-tripped to
395
+ // iCalendar (RFC 5545 has no envelope-level metadata for these);
396
+ // operators preserving Group metadata across a round-trip use the
397
+ // JSON-native JSCalendar surface.
398
+ if (jsCal["@type"] === "Group") {
399
+ var groupLines = [
400
+ "BEGIN:VCALENDAR",
401
+ "VERSION:2.0",
402
+ "PRODID:" + prodid,
403
+ ];
404
+ for (var gei2 = 0; gei2 < jsCal.entries.length; gei2 += 1) {
405
+ var entryIcal = toIcal(jsCal.entries[gei2], { prodid: prodid });
406
+ // Strip outer VCALENDAR envelope; keep inner component lines.
407
+ var entryLines = entryIcal.split("\r\n");
408
+ for (var eli = 0; eli < entryLines.length; eli += 1) {
409
+ var line = entryLines[eli];
410
+ if (line && line !== "BEGIN:VCALENDAR" && line !== "VERSION:2.0" &&
411
+ line.indexOf("PRODID:") !== 0 && line !== "END:VCALENDAR") {
412
+ groupLines.push(line);
413
+ }
414
+ }
415
+ }
416
+ groupLines.push("END:VCALENDAR");
417
+ return groupLines.join("\r\n") + "\r\n";
418
+ }
331
419
  // RFC 8984 §6 — JSCalendar Task maps to RFC 5545 §3.6.2 VTODO; Event
332
420
  // maps to VEVENT. The wrapper + most properties are identical; the
333
421
  // wrapping component tag + Task-specific fields (DUE / STATUS /
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  /**
3
3
  * @module b.nistCrosswalk
4
- * @nav Audit & Compliance
4
+ * @nav Compliance
5
5
  * @title NIST control crosswalk
6
6
  * @order 150
7
7
  * @slug nist-crosswalk
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  /**
3
3
  * @module b.streamThrottle
4
- * @nav Networking
4
+ * @nav Network
5
5
  * @title Stream Throttle
6
6
  * @order 130
7
7
  * @slug stream-throttle
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  /**
3
3
  * @module b.webPush
4
- * @nav Networking
4
+ * @nav Network
5
5
  * @title Web Push (VAPID)
6
6
  * @order 240
7
7
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/core",
3
- "version": "0.11.41",
3
+ "version": "0.11.43",
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:5557c11a-5977-46f0-8a4f-c25e3b621e70",
5
+ "serialNumber": "urn:uuid:a9f75389-959c-4392-b31b-fb5f89c46d4a",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-05-22T01:26:53.795Z",
8
+ "timestamp": "2026-05-22T03:08:03.790Z",
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.11.41",
22
+ "bom-ref": "@blamejs/core@0.11.43",
23
23
  "type": "application",
24
24
  "name": "blamejs",
25
- "version": "0.11.41",
25
+ "version": "0.11.43",
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.11.41",
29
+ "purl": "pkg:npm/%40blamejs/core@0.11.43",
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.11.41",
57
+ "ref": "@blamejs/core@0.11.43",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]