camstack 1.2.140 → 1.2.142

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/dist/cli.js CHANGED
@@ -38,7 +38,7 @@ async function runServe(args) {
38
38
  ...typeof values.data === "string" ? { data: values.data } : {}
39
39
  };
40
40
  Object.assign(process.env, buildServeEnv(opts));
41
- await import("./launcher-TQKCEWPV.js");
41
+ await import("./launcher-7S4JDW34.js");
42
42
  }
43
43
 
44
44
  // src/commands/agent.ts
@@ -83,7 +83,7 @@ async function runAgent(args) {
83
83
  ...typeof values.port === "string" ? { port: values.port } : {}
84
84
  };
85
85
  Object.assign(process.env, buildAgentEnv(opts));
86
- await import("./launcher-TQKCEWPV.js");
86
+ await import("./launcher-7S4JDW34.js");
87
87
  }
88
88
 
89
89
  // src/commands/setup.ts
@@ -531,6 +531,7 @@ async function deployAddon(addonPath, opts) {
531
531
  targets.push({ label: "hub" });
532
532
  }
533
533
  const outcomes = [];
534
+ let propagating = false;
534
535
  for (const t of targets) {
535
536
  console.log(`[camstack] \u2192 Deploying ${path2.basename(tgzPath)} to ${t.label}...`);
536
537
  const result = await withNetworkRetry(
@@ -553,6 +554,7 @@ async function deployAddon(addonPath, opts) {
553
554
  console.error(`[camstack] \u2717 ${t.label}: ${misrouted}`);
554
555
  outcomes.push({ target: t.label, ok: false, detail: misrouted });
555
556
  } else if (result.success) {
557
+ if (result.propagatingToAgentsInBackground === true) propagating = true;
556
558
  const { detail, warning } = describeDeployOutcome(result, t.nodeId !== void 0);
557
559
  console.log(`[camstack] \u2713 ${t.label}: ${detail}`);
558
560
  if (warning) console.warn(`[camstack] \u26A0 ${t.label}: ${warning}`);
@@ -569,6 +571,14 @@ async function deployAddon(addonPath, opts) {
569
571
  process.exit(1);
570
572
  }
571
573
  console.log(`[camstack] \u2713 Deployed to ${outcomes.length} target(s)`);
574
+ if (propagating) {
575
+ console.log(
576
+ "[camstack] \u2026 agents are updated in the BACKGROUND and are NOT covered by the \u2713 above."
577
+ );
578
+ console.log(
579
+ "[camstack] Confirm with: hub log line `propagation done` (per-agent success/error)."
580
+ );
581
+ }
572
582
  if (opts.verify !== false) {
573
583
  process.stdout.write("[camstack] Verifying hub health");
574
584
  const healthy = await verifyHubHealthy(opts.serverUrl, { attempts: 20, delayMs: 1500 });
@@ -84835,6 +84835,7 @@ var require_sqlite_settings_addon = __commonJS({
84835
84835
  autoPrimaryKey: false
84836
84836
  });
84837
84837
  }
84838
+ this.restorePersistedDeclarations();
84838
84839
  if (await this.isEmpty({ collection: "system-settings" })) await this.seedDefaults();
84839
84840
  }
84840
84841
  requireDeclared(scoped) {
@@ -85646,6 +85647,74 @@ var require_sqlite_settings_addon = __commonJS({
85646
85647
  kvBlobColumn: isKvShape ? "data" : null
85647
85648
  };
85648
85649
  }
85650
+ /**
85651
+ * Where a declaration LIVES, so it outlives the process that made it.
85652
+ *
85653
+ * `declaredCollections` used to be only an in-memory Map, and the
85654
+ * declarations are made once, by each addon, at that addon's own boot.
85655
+ * On 2026-09-12 this backend died with SIGBUS (a second connection
85656
+ * truncated the WAL under its mmap) and `CrashSupervisor` respawned it: the
85657
+ * new instance knew only the canonical KV collections, every other addon
85658
+ * stayed up and never re-declared, and the cluster failed every settings
85659
+ * read for six hours — the operator could not log in.
85660
+ *
85661
+ * The tables are durable. What the backend knows ABOUT them has to be too,
85662
+ * because nothing in the system re-declares and nothing listens for this
85663
+ * process restarting.
85664
+ */
85665
+ static DECLARATIONS_TABLE = "_collection_declarations";
85666
+ /** Create the meta table. Idempotent; called before anything reads it. */
85667
+ ensureDeclarationsTable() {
85668
+ this.getDb().exec(`CREATE TABLE IF NOT EXISTS "${SqliteSettingsBackend2.DECLARATIONS_TABLE}" (name TEXT PRIMARY KEY NOT NULL, decl TEXT NOT NULL)`);
85669
+ }
85670
+ /** Persist one declaration, replacing any earlier shape for the same table. */
85671
+ persistDeclaration(table, decl) {
85672
+ try {
85673
+ this.ensureDeclarationsTable();
85674
+ this.getDb().prepare(`INSERT INTO "${SqliteSettingsBackend2.DECLARATIONS_TABLE}" (name, decl) VALUES (?, ?) ON CONFLICT(name) DO UPDATE SET decl = excluded.decl`).run(table, JSON.stringify({
85675
+ primaryKey: decl.primaryKey,
85676
+ columns: [...decl.columns],
85677
+ booleanColumns: [...decl.booleanColumns],
85678
+ autoPrimaryKey: decl.autoPrimaryKey
85679
+ }));
85680
+ } catch (err) {
85681
+ this.logger?.warn("settings: could not persist a collection declaration", { meta: {
85682
+ table,
85683
+ error: err instanceof Error ? err.message : String(err)
85684
+ } });
85685
+ }
85686
+ }
85687
+ /**
85688
+ * Reload every persisted declaration. Called at init, BEFORE any addon has
85689
+ * had the chance to re-declare — which on a respawn none of them will.
85690
+ */
85691
+ restorePersistedDeclarations() {
85692
+ try {
85693
+ this.ensureDeclarationsTable();
85694
+ const rows = this.getDb().prepare(`SELECT name, decl FROM "${SqliteSettingsBackend2.DECLARATIONS_TABLE}"`).all();
85695
+ let restored = 0;
85696
+ for (const row of rows) {
85697
+ if (this.getDb().prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?").get(row.name) === void 0) continue;
85698
+ const parsed = JSON.parse(row.decl);
85699
+ if (typeof parsed !== "object" || parsed === null) continue;
85700
+ const d = parsed;
85701
+ if (typeof d.primaryKey !== "string" || !Array.isArray(d.columns)) continue;
85702
+ this.declaredCollections.set(row.name, {
85703
+ primaryKey: d.primaryKey,
85704
+ columns: new Set(d.columns.filter((c) => typeof c === "string")),
85705
+ booleanColumns: new Set(Array.isArray(d.booleanColumns) ? d.booleanColumns.filter((c) => typeof c === "string") : []),
85706
+ autoPrimaryKey: d.autoPrimaryKey === true
85707
+ });
85708
+ restored += 1;
85709
+ }
85710
+ if (restored > 0) this.logger?.info("settings: restored collection declarations from disk", { meta: {
85711
+ restored,
85712
+ total: rows.length
85713
+ } });
85714
+ } catch (err) {
85715
+ this.logger?.warn("settings: could not restore collection declarations", { meta: { error: err instanceof Error ? err.message : String(err) } });
85716
+ }
85717
+ }
85649
85718
  async declareCollection(input) {
85650
85719
  const table = this.scopedName(input.namespace, input.collection);
85651
85720
  const columns = input.columns.some((c) => c.primaryKey === true) ? input.columns : [{
@@ -85672,14 +85741,14 @@ var require_sqlite_settings_addon = __commonJS({
85672
85741
  await this.ensureTable(table, schema);
85673
85742
  const pkCol = columns.find((c) => c.primaryKey === true);
85674
85743
  const primaryKey = pkCol ? pkCol.name : "id";
85675
- const columnNames = new Set(columns.filter((c) => c.name !== primaryKey).map((c) => c.name));
85676
- const booleanColumns = new Set(columns.filter((c) => c.type === "BOOLEAN").map((c) => c.name));
85677
- this.declaredCollections.set(table, {
85744
+ const decl = {
85678
85745
  primaryKey,
85679
- columns: columnNames,
85680
- booleanColumns,
85746
+ columns: new Set(columns.filter((c) => c.name !== primaryKey).map((c) => c.name)),
85747
+ booleanColumns: new Set(columns.filter((c) => c.type === "BOOLEAN").map((c) => c.name)),
85681
85748
  autoPrimaryKey: pkCol?.type === "INTEGER"
85682
- });
85749
+ };
85750
+ this.declaredCollections.set(table, decl);
85751
+ this.persistDeclaration(table, decl);
85683
85752
  }
85684
85753
  /** Serialise per-column values for SQL binding: objects → JSON, booleans → 0/1. */
85685
85754
  serializeColumnValue(v) {
@@ -121242,9 +121311,9 @@ var require_dist2 = __commonJS({
121242
121311
  }
121243
121312
  });
121244
121313
 
121245
- // ../system/dist/manifest-system-deps-DfpwaXkh.js
121246
- var require_manifest_system_deps_DfpwaXkh = __commonJS({
121247
- "../system/dist/manifest-system-deps-DfpwaXkh.js"(exports) {
121314
+ // ../system/dist/manifest-system-deps-YYKKCKSF.js
121315
+ var require_manifest_system_deps_YYKKCKSF = __commonJS({
121316
+ "../system/dist/manifest-system-deps-YYKKCKSF.js"(exports) {
121248
121317
  "use strict";
121249
121318
  var require_chunk = require_chunk_Cek0wNdY();
121250
121319
  require_hub_cap_forward_DmHNjbB0();
@@ -121294,7 +121363,9 @@ var require_manifest_system_deps_DfpwaXkh = __commonJS({
121294
121363
  childrenConnected: listed.length,
121295
121364
  childrenUndeclared: registry.undeclaredChildCount(),
121296
121365
  crossNodeDelivered: crossNode?.delivered ?? 0,
121297
- crossNodeSuppressed: crossNode?.suppressed ?? 0
121366
+ crossNodeSuppressed: crossNode?.suppressed ?? 0,
121367
+ categories: registry.categoryFanout(),
121368
+ categoryOverflow: registry.categoryCensusOverflow()
121298
121369
  };
121299
121370
  } catch {
121300
121371
  return;
@@ -121304,8 +121375,10 @@ var require_manifest_system_deps_DfpwaXkh = __commonJS({
121304
121375
  var EVENT_PLANE_TOP_N = 5;
121305
121376
  var EMPTY_EVENT_PLANE_BASELINE = {
121306
121377
  children: /* @__PURE__ */ new Map(),
121378
+ categories: /* @__PURE__ */ new Map(),
121307
121379
  crossNodeDelivered: 0,
121308
- crossNodeSuppressed: 0
121380
+ crossNodeSuppressed: 0,
121381
+ categoryOverflow: 0
121309
121382
  };
121310
121383
  function diffEventPlane(baseline, current, topN = 5) {
121311
121384
  const deltas = [];
@@ -121323,6 +121396,23 @@ var require_manifest_system_deps_DfpwaXkh = __commonJS({
121323
121396
  suppressed
121324
121397
  });
121325
121398
  }
121399
+ const categoryDeltas = [];
121400
+ let categoryBroadcasts = 0;
121401
+ let orphanBroadcasts = 0;
121402
+ for (const sample of current.categories) {
121403
+ const before = baseline.categories.get(sample.category);
121404
+ const broadcasts = Math.max(0, sample.broadcasts - (before?.broadcasts ?? 0));
121405
+ if (broadcasts === 0) continue;
121406
+ const wanted = Math.max(0, sample.wanted - (before?.wanted ?? 0));
121407
+ categoryBroadcasts += broadcasts;
121408
+ if (wanted === 0) orphanBroadcasts += broadcasts;
121409
+ categoryDeltas.push({
121410
+ category: sample.category,
121411
+ broadcasts,
121412
+ wanted
121413
+ });
121414
+ }
121415
+ const byBroadcasts = categoryDeltas.toSorted((a, b) => b.broadcasts - a.broadcasts || a.category.localeCompare(b.category));
121326
121416
  return {
121327
121417
  udsMode: current.udsMode,
121328
121418
  crossNodeMode: current.crossNodeMode,
@@ -121332,7 +121422,12 @@ var require_manifest_system_deps_DfpwaXkh = __commonJS({
121332
121422
  fanoutSuppressed,
121333
121423
  crossNodeDelivered: Math.max(0, current.crossNodeDelivered - baseline.crossNodeDelivered),
121334
121424
  crossNodeSuppressed: Math.max(0, current.crossNodeSuppressed - baseline.crossNodeSuppressed),
121335
- top: deltas.toSorted((a, b) => b.sent - a.sent || a.childId.localeCompare(b.childId)).slice(0, topN)
121425
+ top: deltas.toSorted((a, b) => b.sent - a.sent || a.childId.localeCompare(b.childId)).slice(0, topN),
121426
+ categoryBroadcasts,
121427
+ orphanBroadcasts,
121428
+ topCategories: byBroadcasts.slice(0, topN),
121429
+ topOrphans: byBroadcasts.filter((c) => c.wanted === 0).slice(0, topN),
121430
+ categoryOverflow: Math.max(0, current.categoryOverflow - baseline.categoryOverflow)
121336
121431
  };
121337
121432
  }
121338
121433
  function createEventPlaneMeter(reader, topN = 5) {
@@ -121343,8 +121438,10 @@ var require_manifest_system_deps_DfpwaXkh = __commonJS({
121343
121438
  const window2 = diffEventPlane(baseline, current, topN);
121344
121439
  baseline = {
121345
121440
  children: new Map(current.children.map((s) => [s.childId, s])),
121441
+ categories: new Map(current.categories.map((c) => [c.category, c])),
121346
121442
  crossNodeDelivered: current.crossNodeDelivered,
121347
- crossNodeSuppressed: current.crossNodeSuppressed
121443
+ crossNodeSuppressed: current.crossNodeSuppressed,
121444
+ categoryOverflow: current.categoryOverflow
121348
121445
  };
121349
121446
  return window2;
121350
121447
  } };
@@ -121352,7 +121449,9 @@ var require_manifest_system_deps_DfpwaXkh = __commonJS({
121352
121449
  function formatEventPlane(window2) {
121353
121450
  if (window2 === void 0) return "";
121354
121451
  const top = window2.top.map((d) => `${d.childId}:${d.sent}/${d.suppressed}`).join(",");
121355
- return ` fanoutMode=uds:${window2.udsMode}/cross-node:${window2.crossNodeMode} children=${window2.childrenConnected} childrenUndeclared=${window2.childrenUndeclared} fanoutSent=${window2.fanoutSent} fanoutSuppressed=${window2.fanoutSuppressed} crossNodeDelivered=${window2.crossNodeDelivered} crossNodeSuppressed=${window2.crossNodeSuppressed}` + (top.length === 0 ? "" : ` fanoutTop=${top}`);
121452
+ const cats = window2.topCategories.map((c) => `${c.category}:${c.broadcasts}/${c.wanted}`).join(",");
121453
+ const orphans = window2.topOrphans.map((c) => `${c.category}:${c.broadcasts}`).join(",");
121454
+ return ` fanoutMode=uds:${window2.udsMode}/cross-node:${window2.crossNodeMode} children=${window2.childrenConnected} childrenUndeclared=${window2.childrenUndeclared} fanoutSent=${window2.fanoutSent} fanoutSuppressed=${window2.fanoutSuppressed} crossNodeDelivered=${window2.crossNodeDelivered} crossNodeSuppressed=${window2.crossNodeSuppressed} fanoutEvents=${window2.categoryBroadcasts} fanoutOrphans=${window2.orphanBroadcasts} fanoutCatsDropped=${window2.categoryOverflow}` + (top.length === 0 ? "" : ` fanoutTop=${top}`) + (cats.length === 0 ? "" : ` fanoutCats=${cats}`) + (orphans.length === 0 ? "" : ` fanoutOrphanTop=${orphans}`);
121356
121455
  }
121357
121456
  var EMPTY_CAP_ACTION_READING = /* @__PURE__ */ new Map();
121358
121457
  var CAP_ACTION_UNLABELLED = "(unlabelled)";
@@ -126423,6 +126522,12 @@ var require_manifest_system_deps_DfpwaXkh = __commonJS({
126423
126522
  fanoutMode = readFanoutMode();
126424
126523
  /** Per-child event fan-out counters (sent / suppressed). */
126425
126524
  childEventStats = /* @__PURE__ */ new Map();
126525
+ /** D467: per-category production census (cumulative). See {@link CategoryFanoutSample}. */
126526
+ categoryStats = /* @__PURE__ */ new Map();
126527
+ /** Broadcasts whose category did not fit in {@link CATEGORY_CENSUS_MAX}. */
126528
+ categoryOverflow = 0;
126529
+ /** One INFO line the first time the census cap trips, never per event. */
126530
+ categoryOverflowLogged = false;
126426
126531
  /** Last pattern-set string logged per child, to dedup the INFO line. */
126427
126532
  loggedPatternSet = /* @__PURE__ */ new Map();
126428
126533
  /**
@@ -126478,6 +126583,25 @@ var require_manifest_system_deps_DfpwaXkh = __commonJS({
126478
126583
  };
126479
126584
  }
126480
126585
  /**
126586
+ * D467: every category this node has broadcast, with its emit count and how
126587
+ * many child subscriptions matched. Cumulative for the life of the process —
126588
+ * the heartbeat differences it, the same discipline as the per-child
126589
+ * counters. Largest `broadcasts` first.
126590
+ */
126591
+ categoryFanout() {
126592
+ const out = [];
126593
+ for (const [category, s] of this.categoryStats) out.push({
126594
+ category,
126595
+ broadcasts: s.broadcasts,
126596
+ wanted: s.wanted
126597
+ });
126598
+ return out.toSorted((a, b) => b.broadcasts - a.broadcasts || a.category.localeCompare(b.category));
126599
+ }
126600
+ /** Broadcasts the census could not attribute because it was full (cumulative). */
126601
+ categoryCensusOverflow() {
126602
+ return this.categoryOverflow;
126603
+ }
126604
+ /**
126481
126605
  * This child's socket traffic — bytes and messages, both directions, split by
126482
126606
  * frame kind — or `null` when the child is gone or its channel keeps no
126483
126607
  * counters (an in-process or test transport).
@@ -126568,6 +126692,17 @@ var require_manifest_system_deps_DfpwaXkh = __commonJS({
126568
126692
  */
126569
126693
  childWantsEvent(entry, category) {
126570
126694
  if (this.fanoutMode === "broadcast") return true;
126695
+ return this.childSubscribesTo(entry, category);
126696
+ }
126697
+ /**
126698
+ * The SUBSCRIPTION answer alone, with no fan-out-mode override.
126699
+ *
126700
+ * {@link childWantsEvent} answers "will this child be sent the event", which
126701
+ * under `broadcast` is `true` for everything and therefore cannot say whether
126702
+ * anyone actually asked. The D467 census needs the second question, so the
126703
+ * two are separate functions and `broadcast` mode overrides only the first.
126704
+ */
126705
+ childSubscribesTo(entry, category) {
126571
126706
  if (entry.eventPatterns === null) return true;
126572
126707
  return entry.eventPatterns.some((p) => matchesPattern(p, category));
126573
126708
  }
@@ -126870,8 +127005,10 @@ var require_manifest_system_deps_DfpwaXkh = __commonJS({
126870
127005
  event,
126871
127006
  sourceNodeId
126872
127007
  };
127008
+ let subscribed = 0;
126873
127009
  for (const entry of this.children.values()) {
126874
127010
  if (entry.childId === exceptChildId) continue;
127011
+ if (this.childSubscribesTo(entry, event.category)) subscribed += 1;
126875
127012
  const wants = this.childWantsEvent(entry, event.category);
126876
127013
  const stats = this.statsFor(entry.childId);
126877
127014
  if (!wants) stats.suppressed++;
@@ -126880,6 +127017,34 @@ var require_manifest_system_deps_DfpwaXkh = __commonJS({
126880
127017
  entry.channel.emit(msg);
126881
127018
  }
126882
127019
  }
127020
+ this.recordCategoryBroadcast(event.category, subscribed);
127021
+ }
127022
+ /**
127023
+ * Record one PRODUCED event against its category. Called once per broadcast,
127024
+ * outside the per-child loop, because production is what the census measures.
127025
+ */
127026
+ recordCategoryBroadcast(category, subscribed) {
127027
+ const existing = this.categoryStats.get(category);
127028
+ if (existing !== void 0) {
127029
+ existing.broadcasts += 1;
127030
+ existing.wanted += subscribed;
127031
+ return;
127032
+ }
127033
+ if (this.categoryStats.size >= 256) {
127034
+ this.categoryOverflow += 1;
127035
+ if (!this.categoryOverflowLogged) {
127036
+ this.categoryOverflowLogged = true;
127037
+ this.logger?.info("event category census full \u2014 further new categories are unattributed", {
127038
+ max: 256,
127039
+ firstDropped: category
127040
+ });
127041
+ }
127042
+ return;
127043
+ }
127044
+ this.categoryStats.set(category, {
127045
+ broadcasts: 1,
127046
+ wanted: subscribed
127047
+ });
126883
127048
  }
126884
127049
  /**
126885
127050
  * E2: Send a `set-log-level` control message to a specific child.
@@ -134414,7 +134579,7 @@ var require_dist3 = __commonJS({
134414
134579
  var require_builtins_winston_logging_index = require_winston_logging();
134415
134580
  var require_event_loop_stall_monitor = require_event_loop_stall_monitor_CAsHdMwP();
134416
134581
  var require_tls$1 = require_tls_BFs4PzoW();
134417
- var require_manifest_system_deps = require_manifest_system_deps_DfpwaXkh();
134582
+ var require_manifest_system_deps = require_manifest_system_deps_YYKKCKSF();
134418
134583
  var require_resource_monitor = require_resource_monitor_LVE1BoLs();
134419
134584
  var require_custom_action_registry = require_custom_action_registry_DvVMHLfJ();
134420
134585
  var zod = require_zod();
@@ -199117,8 +199282,9 @@ ${originalIndentation}`;
199117
199282
  const type2 = resolvePacketType(packet[5]);
199118
199283
  this.emit("data", type2, msg);
199119
199284
  const rest = packet.slice(length);
199120
- this.bufs = rest.length > 0 ? [rest] : [];
199121
- this.bufLen = rest.length;
199285
+ const carried = rest.length > 0 && rest.length * 8 < rest.buffer.byteLength ? Buffer.from(rest) : rest;
199286
+ this.bufs = carried.length > 0 ? [carried] : [];
199287
+ this.bufLen = carried.length;
199122
199288
  }
199123
199289
  cb();
199124
199290
  }
@@ -357688,8 +357854,10 @@ var require_parser2 = __commonJS({
357688
357854
  const type = resolvePacketType(packet[5]);
357689
357855
  this.emit("data", type, msg);
357690
357856
  const rest = packet.slice(length);
357691
- this.bufs = rest.length > 0 ? [rest] : [];
357692
- this.bufLen = rest.length;
357857
+ const pinsTooMuch = rest.length > 0 && rest.length * 8 < rest.buffer.byteLength;
357858
+ const carried = pinsTooMuch ? Buffer.from(rest) : rest;
357859
+ this.bufs = carried.length > 0 ? [carried] : [];
357860
+ this.bufLen = carried.length;
357693
357861
  }
357694
357862
  cb();
357695
357863
  }
@@ -414053,7 +414221,7 @@ var require_agent_addon_delivery = __commonJS({
414053
414221
  exports.AGENT_RELOAD_TIMEOUT_MS = exports.AGENT_DEPLOY_CONTROL_TIMEOUT_MS = void 0;
414054
414222
  exports.isAgentDeployResponse = isAgentDeployResponse;
414055
414223
  exports.deliverAddonToAgent = deliverAddonToAgent;
414056
- exports.AGENT_DEPLOY_CONTROL_TIMEOUT_MS = 3e4;
414224
+ exports.AGENT_DEPLOY_CONTROL_TIMEOUT_MS = 3e5;
414057
414225
  exports.AGENT_RELOAD_TIMEOUT_MS = 6e4;
414058
414226
  function isAgentDeployResponse(value) {
414059
414227
  if (value === null || typeof value !== "object")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "camstack",
3
- "version": "1.2.140",
3
+ "version": "1.2.142",
4
4
  "description": "CLI tool for managing and running CamStack server",
5
5
  "keywords": [
6
6
  "camstack",