@camstack/system 1.2.425 → 1.2.426

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.
Files changed (78) hide show
  1. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  2. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  3. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/alerts/alerts.addon.js +1 -1
  6. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  7. package/dist/builtins/autotrack/index.js +1 -1
  8. package/dist/builtins/autotrack/index.mjs +1 -1
  9. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.d.ts +6 -0
  10. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +22 -2
  11. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +22 -2
  12. package/dist/builtins/calendars/calendars.addon.js +1 -1
  13. package/dist/builtins/calendars/calendars.addon.mjs +1 -1
  14. package/dist/builtins/camera-grid/index.js +1 -1
  15. package/dist/builtins/camera-grid/index.mjs +1 -1
  16. package/dist/builtins/composer/composer.addon.js +2 -2
  17. package/dist/builtins/composer/composer.addon.mjs +2 -2
  18. package/dist/builtins/console-logging/index.js +1 -1
  19. package/dist/builtins/console-logging/index.mjs +1 -1
  20. package/dist/builtins/core-blocks/core-blocks.addon.js +2 -2
  21. package/dist/builtins/core-blocks/core-blocks.addon.mjs +2 -2
  22. package/dist/builtins/device-manager/device-manager.addon.js +2 -2
  23. package/dist/builtins/device-manager/device-manager.addon.mjs +2 -2
  24. package/dist/builtins/hub-forwarder/index.js +1 -1
  25. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  26. package/dist/builtins/keychain/index.js +3 -3
  27. package/dist/builtins/keychain/index.mjs +3 -3
  28. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  29. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  30. package/dist/builtins/local-auth/local-auth.addon.js +2 -2
  31. package/dist/builtins/local-auth/local-auth.addon.mjs +2 -2
  32. package/dist/builtins/local-network/address-book.d.ts +21 -0
  33. package/dist/builtins/local-network/local-network.addon.d.ts +6 -0
  34. package/dist/builtins/local-network/local-network.addon.js +48 -7
  35. package/dist/builtins/local-network/local-network.addon.mjs +48 -7
  36. package/dist/builtins/loki-logging/index.js +1 -1
  37. package/dist/builtins/loki-logging/index.mjs +1 -1
  38. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  39. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  40. package/dist/builtins/platform-probe/index.js +1 -1
  41. package/dist/builtins/platform-probe/index.mjs +1 -1
  42. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  43. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  44. package/dist/builtins/snapshot/index.js +1 -1
  45. package/dist/builtins/snapshot/index.mjs +1 -1
  46. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  47. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  48. package/dist/builtins/sqlite-storage/index.js +1 -1
  49. package/dist/builtins/sqlite-storage/index.mjs +1 -1
  50. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +3 -3
  51. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +3 -3
  52. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  53. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  54. package/dist/builtins/system-config/system-config.addon.js +2 -2
  55. package/dist/builtins/system-config/system-config.addon.mjs +2 -2
  56. package/dist/builtins/winston-logging/index.js +1 -1
  57. package/dist/builtins/winston-logging/index.mjs +1 -1
  58. package/dist/{composition-sources-197keifF.js → composition-sources-60tuHQ9N.js} +1 -1
  59. package/dist/{composition-sources-DmzTSy26.mjs → composition-sources-gxVpghXh.mjs} +1 -1
  60. package/dist/{constants-CkNLi-XW.mjs → constants-B6YTf0v2.mjs} +1 -1
  61. package/dist/{constants-B5Jd8Zch.js → constants-CO5zpJQY.js} +1 -1
  62. package/dist/{dist-DMWeqeTF.mjs → dist-CGz9u2HD.mjs} +37 -1
  63. package/dist/{dist-DnALrQF-.js → dist-K3WFop9Q.js} +37 -1
  64. package/dist/file-host-worker.js +1 -1
  65. package/dist/file-host-worker.mjs +1 -1
  66. package/dist/index.js +272 -198
  67. package/dist/index.mjs +272 -198
  68. package/dist/{jwt-signing-key-DeqG7LD9.js → jwt-signing-key-BdDy9ZJN.js} +1 -1
  69. package/dist/{jwt-signing-key-CCHXAtrs.mjs → jwt-signing-key-C_w5ielw.mjs} +1 -1
  70. package/dist/kernel/moleculer/process-service.d.ts +1 -110
  71. package/dist/kernel/moleculer/runner-heap-flags.d.ts +228 -0
  72. package/dist/{restore-archive-C4UhQOsg.js → restore-archive-BJp4Llea.js} +1 -1
  73. package/dist/{restore-archive-GevpOHw8.mjs → restore-archive-CZyUM8Qf.mjs} +1 -1
  74. package/dist/{retired-settings-keys-0FOsnzIE.js → retired-settings-keys-Dr2AyhsD.js} +1 -1
  75. package/dist/{retired-settings-keys-CdVFl7m2.mjs → retired-settings-keys-Dut6QWh6.mjs} +1 -1
  76. package/dist/{wal-maintenance-BtfyfuT5.mjs → wal-maintenance-BZhzoxqA.mjs} +0 -0
  77. package/dist/{wal-maintenance-DJV1rde2.js → wal-maintenance-Bzm3O2Nj.js} +0 -0
  78. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { i as __require } from "./chunk-CNf5ZN-e.mjs";
2
- import { $t as looseSchema, Ar as expandCapMethods, Dn as scopesAllowAddon, Gr as readinessKey, Hr as parseJsonObject, Ht as isVoidInput, Jr as scopeKey, Jt as lifecycleJobSchema, K as addonSettingsCapability, Kr as resolveCapMount, Lt as isArrayOutputSchema, M as METHOD_ACCESS_MAP, On as scopesAllowDeviceCap, Or as emitDownForOwnedCaps, Qt as logLevelAtMost, Rt as isCollectionArrayMethod, Tr as createEvent$1, Ur as parseJsonUnknown, Ut as kebabToCamel$1, Vt as isObjectInput, br as asNumber, cn as objectInputDeclaresAddonId, cr as errMsg$1, dr as DEVICE_SETTINGS_CONTRIBUTION_METHODS, fr as DEVICE_STATUS_METHOD, gr as ReadinessTimeoutError, hr as ReadinessRegistry, jt as extractNestedAddonId, k as LOG_LEVEL_RANK, ln as objectInputDeclaresRequiredDeviceId, mn as procedureAuthKey, mr as RUNTIME_DEFAULTS, t as ALL_CAPABILITY_DEFINITIONS, ti as EventCategory$1, ur as DATAPLANE_SECRET_HEADER$1, wn as resolveMethodAuth, xr as asString$1, yr as asJsonObject$1, zn as supportedRuntimes } from "./dist-DMWeqeTF.mjs";
3
- import { c as staticJwtSigningKey, i as getProcessJwtSigningKeyState, n as JwtSigningKeyUnavailableError, o as processJwtSigningKey, r as SYSTEM_SECRET_OWNER, t as JWT_SIGNING_KEY_REF } from "./jwt-signing-key-CCHXAtrs.mjs";
2
+ import { $t as looseSchema, Ar as expandCapMethods, Dn as scopesAllowAddon, Gr as readinessKey, Hr as parseJsonObject, Ht as isVoidInput, Jr as scopeKey, Jt as lifecycleJobSchema, K as addonSettingsCapability, Kr as resolveCapMount, Lt as isArrayOutputSchema, M as METHOD_ACCESS_MAP, On as scopesAllowDeviceCap, Or as emitDownForOwnedCaps, Qt as logLevelAtMost, Rt as isCollectionArrayMethod, Tr as createEvent$1, Ur as parseJsonUnknown, Ut as kebabToCamel$1, Vt as isObjectInput, br as asNumber, cn as objectInputDeclaresAddonId, cr as errMsg$1, dr as DEVICE_SETTINGS_CONTRIBUTION_METHODS, fr as DEVICE_STATUS_METHOD, gr as ReadinessTimeoutError, hr as ReadinessRegistry, jt as extractNestedAddonId, k as LOG_LEVEL_RANK, ln as objectInputDeclaresRequiredDeviceId, mn as procedureAuthKey, mr as RUNTIME_DEFAULTS, t as ALL_CAPABILITY_DEFINITIONS, ti as EventCategory$1, ur as DATAPLANE_SECRET_HEADER$1, wn as resolveMethodAuth, xr as asString$1, yr as asJsonObject$1, zn as supportedRuntimes } from "./dist-CGz9u2HD.mjs";
3
+ import { c as staticJwtSigningKey, i as getProcessJwtSigningKeyState, n as JwtSigningKeyUnavailableError, o as processJwtSigningKey, r as SYSTEM_SECRET_OWNER, t as JWT_SIGNING_KEY_REF } from "./jwt-signing-key-C_w5ielw.mjs";
4
4
  import { AlertCenterAddon } from "./builtins/alerts/alerts.addon.mjs";
5
5
  import "./builtins/alerts/index.mjs";
6
6
  import { CalendarsAddon } from "./builtins/calendars/calendars.addon.mjs";
@@ -21,12 +21,12 @@ import "./builtins/local-auth/index.mjs";
21
21
  import { LokiDestination, LokiLoggingAddon } from "./builtins/loki-logging/index.mjs";
22
22
  import NativeMetricsAddon, { a as NativeMetricsProvider, n as nodeProcReader, r as parseProcStat } from "./builtins/native-metrics/native-metrics.addon.mjs";
23
23
  import { FilesystemStorageAddon, t as FilesystemStorageProvider } from "./builtins/sqlite-storage/filesystem-storage.addon.mjs";
24
- import { c as SqliteSettingsBackend, l as prefixRange } from "./wal-maintenance-BtfyfuT5.mjs";
24
+ import { c as SqliteSettingsBackend, l as prefixRange } from "./wal-maintenance-BZhzoxqA.mjs";
25
25
  import { SqliteSettingsAddon } from "./builtins/sqlite-storage/sqlite-settings.addon.mjs";
26
26
  import { ConfigStore, DeviceStore } from "./builtins/sqlite-storage/index.mjs";
27
27
  import { StorageOrchestratorAddon, a as stripForwardedCaller, i as createHubCapForwardService, n as HUB_CAP_FWD_ACTION, o as buildStorageLocationRegistry, r as HUB_CAP_FWD_SERVICE, t as StorageOrchestratorService } from "./builtins/storage-orchestrator/storage-orchestrator.addon.mjs";
28
28
  import "./builtins/storage-orchestrator/index.mjs";
29
- import { _ as getProcessClusterSecretGate, a as MESH_CA_KEY_REF, b as setProcessClusterSecretGate, d as CLUSTER_SECRET_REF, f as CLUSTER_SECRET_REJECTED_EXIT_CODE, g as clusterSecretMatches, h as checkClusterSecret, i as MESH_CA_FIELD, m as CLUSTER_SECRET_UNAVAILABLE_TYPE, n as DEFAULT_HUB_TLS_TCP_PORT, o as MESH_CA_SETTINGS_KEY, p as CLUSTER_SECRET_SETTINGS_KEY, u as CLUSTER_SECRET_MISMATCH_TYPE, v as hashClusterSecret, y as isClusterSecretMismatchError } from "./constants-CkNLi-XW.mjs";
29
+ import { _ as getProcessClusterSecretGate, a as MESH_CA_KEY_REF, b as setProcessClusterSecretGate, d as CLUSTER_SECRET_REF, f as CLUSTER_SECRET_REJECTED_EXIT_CODE, g as clusterSecretMatches, h as checkClusterSecret, i as MESH_CA_FIELD, m as CLUSTER_SECRET_UNAVAILABLE_TYPE, n as DEFAULT_HUB_TLS_TCP_PORT, o as MESH_CA_SETTINGS_KEY, p as CLUSTER_SECRET_SETTINGS_KEY, u as CLUSTER_SECRET_MISMATCH_TYPE, v as hashClusterSecret, y as isClusterSecretMismatchError } from "./constants-B6YTf0v2.mjs";
30
30
  import { A as AGENT_UI_UNLOCK_TTL_MS, B as isMeshAuthMode, C as generateMeshCa, D as loadMeshCa, E as issueMeshLeafFromCsr, F as parseAgentDashboardOrigins, G as setProcessMeshEnforcer, H as requestProcessMeshEnforcement, I as verifyAgentUiUnlockTicket, J as parseCidrList, K as createCidrMatcher, L as MESH_AUTH_MODE_FIELD, M as UnlockTicketJtiStore, N as isAllowedAgentDashboardReturnTo, O as AGENT_DASHBOARD_ORIGINS_FIELD, P as mintAgentUiUnlockTicket, R as getProcessMeshAuthMode, S as exportMeshPrivateKeyPem, SystemConfigAddon, T as issueMeshLeafForNode, U as setProcessMeshAuthMode, V as parseMeshAuthMode, W as setProcessMeshAuthStatusSource, Y as readCidrSetting, _ as getProcessMeshCaState, a as ServiceBroker, b as caFingerprintCompact, c as JoinTokenJtiStore, d as verifyMeshJoinToken, f as getProcessNodeMeshTransport, g as MeshCaKeeper, h as processMeshCrl, i as Errors, j as AGENT_UI_UNLOCK_VERSION, k as AGENT_UI_UNLOCK_DOMAIN, l as mintMeshJoinToken, m as MeshCrl, n as MESH_TLS_PORT_HELLO_FIELD, o as setProcessMeshTlsEnabler, p as setProcessMeshPeerTransportLookup, q as normalizeIp, r as TlsTcpTransporter, s as enrollMeshNode, u as processJoinTokenJtiStore, v as requireProcessMeshCa, w as generateMeshKeyPair, x as createMeshCsrPem, y as caFingerprint, z as getProcessMeshAuthStatus } from "./builtins/system-config/system-config.addon.mjs";
31
31
  import "./builtins/system-config/index.mjs";
32
32
  import { WinstonDestination, WinstonLoggingAddon } from "./builtins/winston-logging/index.mjs";
@@ -34,7 +34,7 @@ import { C as isModelDownloaded, T as revisionWeightUrl, _ as downloadFile, a as
34
34
  import { A as allFamiliesListenHost, C as collectCertIdentity, D as SERVER_AUTH_OID, E as MAX_LEAF_VALIDITY_DAYS, F as registerLanHttpHandler, M as bindPendingLanHttp, N as closeLanHttp, O as evaluateExistingCert, P as readLanHttpState, S as CA_COMMON_NAME, T as LEAF_RENEWAL_WINDOW_DAYS, _ as writeExtraSans, a as validateUploadedTls, b as loadTlsCert, c as registerHttpsListener, d as prepareBootTls, f as UPLOADED_CA_FILE, g as splitPemCertificates, h as readTlsMode, i as storeUploadedCertificate, j as applyLanHttp, k as DEFAULT_LAN_HTTP_PORT, l as reloadHttpsListener, m as readTlsAccessStatus, n as regenerateCertificate, o as clearHttpsListener, p as readExtraSans, r as revertToGeneratedCertificate, s as leafFingerprintOf, t as applyCertificateToListener, u as servedHttpsLeafFingerprint, v as writeTlsMode, w as CA_VALIDITY_DAYS, x as reissueTlsLeaf, y as ensureTlsCert } from "./tls-CaOLZ09W.mjs";
35
35
  import { n as getPlatformKeychainKey, r as setPlatformKeychainKey, t as clearPlatformKeychainKey } from "./platform-key-source-BWlq0jjn.mjs";
36
36
  import { n as getSinglePidStats, t as getPidStats } from "./resource-monitor-Di9daRwI.mjs";
37
- import { c as BackupDecryptError, f as generateBackupKeyPair, h as readBackupEncryptionHeader, l as BackupKeyFormatError, r as prepareRestoreArchive, s as SystemBackupService, t as BackupKeyRequiredError, u as BackupKeyMismatchError } from "./restore-archive-GevpOHw8.mjs";
37
+ import { c as BackupDecryptError, f as generateBackupKeyPair, h as readBackupEncryptionHeader, l as BackupKeyFormatError, r as prepareRestoreArchive, s as SystemBackupService, t as BackupKeyRequiredError, u as BackupKeyMismatchError } from "./restore-archive-CZyUM8Qf.mjs";
38
38
  import { z } from "zod";
39
39
  import * as crypto$1 from "node:crypto";
40
40
  import { createHash, createHmac, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
@@ -25365,6 +25365,273 @@ var CrashSupervisor = class {
25365
25365
  this.crashes.delete(runnerId);
25366
25366
  }
25367
25367
  };
25368
+ //#endregion
25369
+ //#region src/kernel/moleculer/runner-heap-flags.ts
25370
+ var EMPTY_HEAP_DECLARATION = {
25371
+ profile: void 0,
25372
+ maxOldSpaceMb: void 0,
25373
+ rssBudgetMb: void 0,
25374
+ semiSpaceMb: void 0,
25375
+ initialOldSpaceMb: void 0
25376
+ };
25377
+ /**
25378
+ * `execution.semiSpaceMb` is accepted in `[1, 256]` MB. V8 itself clamps the flag to its
25379
+ * own limits (the 64-bit default is 64 MB after Node 24's scavenger change, a 256 MB
25380
+ * semi-space is three times anything measured useful here); below 1 MB the flag is
25381
+ * meaningless. Outside the range the declaration is IGNORED (the runner keeps its class
25382
+ * default) rather than clamped: a clamped number silently becomes a different setting from
25383
+ * the one in the file, and the spawn log would then say something the manifest does not.
25384
+ */
25385
+ var SEMI_SPACE_MB_RANGE = {
25386
+ min: 1,
25387
+ max: 256
25388
+ };
25389
+ /**
25390
+ * `execution.initialOldSpaceMb` is accepted in `[16, 65536]` MB. Below 16 MB V8's default
25391
+ * already starts larger; 64 GB clears any runner in this fleet. The accepted value is then
25392
+ * clamped at spawn to the runner's old-space CEILING (`runnerHeapFlags`) — V8 refuses an
25393
+ * initial size above `--max-old-space-size`, and the ceiling is the one number that belongs
25394
+ * to the runner as a whole.
25395
+ */
25396
+ var INITIAL_OLD_SPACE_MB_RANGE = {
25397
+ min: 16,
25398
+ max: 65536
25399
+ };
25400
+ /** An integer MB inside `range`, else `undefined` — a non-integer is floored first. */
25401
+ function mbInRange(value, range) {
25402
+ if (typeof value !== "number" || !Number.isFinite(value)) return void 0;
25403
+ const mb = Math.floor(value);
25404
+ return mb >= range.min && mb <= range.max ? mb : void 0;
25405
+ }
25406
+ /**
25407
+ * Resolve the declared heap profile + old-space ceiling for one addon by reading its package.json
25408
+ * manifest (`camstack.addons[].execution.{heapProfile,maxOldSpaceMb}`). `addonDir` points at the
25409
+ * addon's built `dist`, so the manifest is one level up; we also try `addonDir` itself for layouts
25410
+ * where it IS the package root.
25411
+ * Result is cached per (dir,id) — spawns are infrequent but a runner may re-spawn on crash/restart.
25412
+ * Returns an empty declaration when the manifest is unreadable → treated as light, default ceiling.
25413
+ */
25414
+ var heapProfileCache = /* @__PURE__ */ new Map();
25415
+ function readAddonHeapDeclaration(spec) {
25416
+ const cacheKey = `${spec.addonDir}::${spec.addonId}`;
25417
+ const cached = heapProfileCache.get(cacheKey);
25418
+ if (cached !== void 0) return cached;
25419
+ let declaration = EMPTY_HEAP_DECLARATION;
25420
+ for (const manifestPath of [path$1.join(spec.addonDir, "package.json"), path$1.join(path$1.dirname(spec.addonDir), "package.json")]) try {
25421
+ const raw = fs.readFileSync(manifestPath, "utf8");
25422
+ const parsed = JSON.parse(raw);
25423
+ const profile = extractHeapProfile(parsed, spec.addonId);
25424
+ const maxOldSpaceMb = extractMaxOldSpaceMb(parsed, spec.addonId);
25425
+ const rssBudgetMb = extractRssBudgetMb(parsed, spec.addonId);
25426
+ const semiSpaceMb = extractSemiSpaceMb(parsed, spec.addonId);
25427
+ const initialOldSpaceMb = extractInitialOldSpaceMb(parsed, spec.addonId);
25428
+ if (profile !== void 0 || maxOldSpaceMb !== void 0 || rssBudgetMb !== void 0 || semiSpaceMb !== void 0 || initialOldSpaceMb !== void 0) {
25429
+ declaration = {
25430
+ profile,
25431
+ maxOldSpaceMb,
25432
+ rssBudgetMb,
25433
+ semiSpaceMb,
25434
+ initialOldSpaceMb
25435
+ };
25436
+ break;
25437
+ }
25438
+ if (manifestHasAddon(parsed, spec.addonId)) break;
25439
+ } catch {}
25440
+ heapProfileCache.set(cacheKey, declaration);
25441
+ return declaration;
25442
+ }
25443
+ /**
25444
+ * Drop the cached heap declaration for each addon so the NEXT `readAddonHeapDeclaration`
25445
+ * re-reads the manifest from disk. Called on operator-driven spawns / restarts
25446
+ * (a deploy may have changed `execution.heapProfile` / `execution.maxOldSpaceMb`); the
25447
+ * crash-respawn fast path deliberately does NOT invalidate — a crashing runner
25448
+ * has the same on-disk manifest, so re-reading it is pure overhead.
25449
+ */
25450
+ function invalidateHeapProfiles(addons) {
25451
+ for (const spec of addons) heapProfileCache.delete(`${spec.addonDir}::${spec.addonId}`);
25452
+ }
25453
+ function manifestHasAddon(parsed, addonId) {
25454
+ return readManifestAddons(parsed).some((a) => a.id === addonId);
25455
+ }
25456
+ function extractHeapProfile(parsed, addonId) {
25457
+ const value = readManifestAddons(parsed).find((a) => a.id === addonId)?.execution?.heapProfile;
25458
+ return value === "heavy" || value === "light" ? value : void 0;
25459
+ }
25460
+ function extractMaxOldSpaceMb(parsed, addonId) {
25461
+ const value = readManifestAddons(parsed).find((a) => a.id === addonId)?.execution?.maxOldSpaceMb;
25462
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.floor(value) : void 0;
25463
+ }
25464
+ /**
25465
+ * `execution.rssBudgetMb` for one addon. Only a POSITIVE finite number is a budget: unlike
25466
+ * `maxOldSpaceMb`, `0` here is not an escape hatch that lifts a cap, it is an absent budget —
25467
+ * and an absent budget is reported as "unwatched", never rounded into a default.
25468
+ */
25469
+ function extractRssBudgetMb(parsed, addonId) {
25470
+ const value = readManifestAddons(parsed).find((a) => a.id === addonId)?.execution?.rssBudgetMb;
25471
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : void 0;
25472
+ }
25473
+ /** `execution.semiSpaceMb` for one addon — only a value inside {@link SEMI_SPACE_MB_RANGE}. */
25474
+ function extractSemiSpaceMb(parsed, addonId) {
25475
+ return mbInRange(readManifestAddons(parsed).find((a) => a.id === addonId)?.execution?.semiSpaceMb, SEMI_SPACE_MB_RANGE);
25476
+ }
25477
+ /** `execution.initialOldSpaceMb` for one addon — only a value inside {@link INITIAL_OLD_SPACE_MB_RANGE}. */
25478
+ function extractInitialOldSpaceMb(parsed, addonId) {
25479
+ return mbInRange(readManifestAddons(parsed).find((a) => a.id === addonId)?.execution?.initialOldSpaceMb, INITIAL_OLD_SPACE_MB_RANGE);
25480
+ }
25481
+ function readManifestAddons(parsed) {
25482
+ if (typeof parsed !== "object" || parsed === null) return [];
25483
+ const camstack = parsed.camstack;
25484
+ if (typeof camstack !== "object" || camstack === null) return [];
25485
+ const addons = camstack.addons;
25486
+ return Array.isArray(addons) ? addons : [];
25487
+ }
25488
+ /**
25489
+ * A runner is heavy if ANY co-located addon declares `heapProfile: 'heavy'` in its manifest.
25490
+ * Everything else (the un-annotated default) is a light control-plane runner. No hard-coded names.
25491
+ */
25492
+ function isHeavyRunner(addons) {
25493
+ return addons.some((a) => readAddonHeapDeclaration(a).profile === "heavy");
25494
+ }
25495
+ /**
25496
+ * Default old-space ceiling for a HEAVY runner, in MB.
25497
+ *
25498
+ * ## Why a heavy runner needs a ceiling at all
25499
+ *
25500
+ * `heapProfile: 'heavy'` used to mean "V8 defaults", i.e. no `--max-old-space-size` — which on this
25501
+ * host is V8's own memory-derived default (multiple GB). Nothing then bounds a burst, and V8 does
25502
+ * not hand committed pages back (the long argument for that is in `kernel/heap-watch.ts`), so a
25503
+ * one-off burst becomes the runner's permanent RSS.
25504
+ *
25505
+ * Measured on the live hub, 2026-08-17: the `recorder` runner reaches **2122 MB RSS** and stays
25506
+ * there. The same addon on two agent nodes carrying the same code and a smaller archive sits at
25507
+ * **58 MB**.
25508
+ *
25509
+ * ⚠ This paragraph used to attribute that ~2 GB to "walk garbage" from the deferred archive walk,
25510
+ * and to argue from a ~21 MB `RecordingIndex`. Both were wrong, and the error cost a session on
25511
+ * 2026-08-27. The deferred walk had NO CALLER and never ran (it has since been deleted). And the
25512
+ * index is not 21 MB: a 2026-08-27 profile of the live recorder read `heapUsed = 1606 MB` against
25513
+ * `external = 8 MB`, with an `smaps` census putting 1510 MB in V8's 256 KB page ranges against
25514
+ * 54 MB of native heap — LIVE JavaScript retainers, not high-water garbage. The retained set is
25515
+ * ~7.1 M `SegmentRow`s plus their path strings and Map entries (13 cameras x 2 profiles x 8 640
25516
+ * segments/day x ~30 days), reseeded at boot by `SegmentHourLedger.hydrateIndex`.
25517
+ *
25518
+ * The lesson for anyone sizing this flag: a heap number needs a RETAINER, and "garbage the GC has
25519
+ * no reason to collect" is the explanation to reach for last, not first.
25520
+ *
25521
+ * ## Why 1024
25522
+ *
25523
+ * From the same live topology snapshot, every heavy runner's FULL RSS — which is a strict
25524
+ * over-estimate of its old space, since it also contains native allocations, `external` and
25525
+ * `arrayBuffers`, none of which `--max-old-space-size` bounds:
25526
+ *
25527
+ * detection 471 MB · provider-reolink 507 MB · pipeline-orchestrator 238 MB · ml 218 MB ·
25528
+ * snapshot 185 MB · decoder-nodeav 174 MB · audio-analyzer 170 MB
25529
+ *
25530
+ * 1024 MB clears the largest of those by ~2x while cutting the recorder's observed ratchet by
25531
+ * more than half. It is a CEILING, not a target: a runner that fits today keeps behaving exactly
25532
+ * as it does now, because V8 only collects harder as it approaches the limit.
25533
+ *
25534
+ * A runner whose working set genuinely does not fit declares its own number in its manifest
25535
+ * (`execution.maxOldSpaceMb`) — the number belongs next to the addon that knows its working set.
25536
+ * `recorder` (3072 MB; peak 2341 MB heapTotal) is the one shipped addon that does. `stream-broker` does NOT, despite 1585 MB peak RSS: ~1 GB of that is
25537
+ * `arrayBuffers` (47 RTP rings), which this flag does not bound and never will — its actual old
25538
+ * space peaks at 224 MB. Size the declaration from `heapTotal` in the `[mem]` heartbeat, never
25539
+ * from RSS.
25540
+ *
25541
+ * ## What this flag CANNOT do
25542
+ *
25543
+ * It bounds old space and nothing else — not `external`, not `arrayBuffers`, not what a native
25544
+ * addon mallocs. `hub/pipeline-analytics` reached 7334 MB RSS with this flag set to 1024 the
25545
+ * whole time, because 96% of it was native. That half is bounded in `runner-native-allocator.ts`.
25546
+ * Reaching for a bigger number here when RSS is the complaint is the standard wrong move.
25547
+ */
25548
+ var HEAVY_MAX_OLD_MB_DEFAULT = 1024;
25549
+ /**
25550
+ * The runner's old-space ceiling in MB, or 0 for "uncapped".
25551
+ *
25552
+ * The runner takes the MAXIMUM ceiling any co-located addon declares — co-location must never
25553
+ * silence the hungriest member (D2/D29: a runner is respawned as a whole, so an OOM caused by
25554
+ * under-sizing one member kills every addon in the group). An addon declaring `0` therefore lifts
25555
+ * the cap for the whole runner, which is the intended escape hatch.
25556
+ */
25557
+ function runnerMaxOldSpaceMb(addons, heavy) {
25558
+ const envDefault = heavy ? HEAVY_MAX_OLD_MB_DEFAULT : 0;
25559
+ const declared = addons.map((a) => readAddonHeapDeclaration(a).maxOldSpaceMb).filter((mb) => mb !== void 0);
25560
+ if (declared.length === 0) return envDefault;
25561
+ if (declared.includes(0)) return 0;
25562
+ return Math.max(...declared);
25563
+ }
25564
+ /**
25565
+ * The runner's declared RSS budget in MB, or `undefined` when NOBODY declared one.
25566
+ *
25567
+ * Same rule as the old-space ceiling, for the same reason (D2/D29): the runner takes the MAXIMUM
25568
+ * any co-located addon declares, because a runner is respawned as a whole and co-location must
25569
+ * never silence the hungriest member. It would be strictly worse here than for a heap ceiling —
25570
+ * an under-sized budget does not kill anything, it just cries wolf, and a watchdog that cries
25571
+ * wolf is a watchdog that gets muted.
25572
+ *
25573
+ * `undefined` propagates as an ABSENT env var, and the child then announces itself unwatched.
25574
+ * There is deliberately no class default: see `AddonExecution.rssBudgetMb`.
25575
+ */
25576
+ function runnerRssBudgetMb(addons) {
25577
+ const declared = addons.map((a) => readAddonHeapDeclaration(a).rssBudgetMb).filter((mb) => mb !== void 0);
25578
+ return declared.length === 0 ? void 0 : Math.max(...declared);
25579
+ }
25580
+ /**
25581
+ * Per-placement V8 flags (Block D). LIGHT runners cap their young generation
25582
+ * (`--max-semi-space-size`) to reclaim V8's over-reservation — a measured ~15 MB/runner win — bound
25583
+ * V8's platform thread pool (`--v8-pool-size`, see {@link LIGHT_V8_POOL_SIZE_DEFAULT}: a measured
25584
+ * ~58 threads across the hub's 29 light runners, against a cgroup `pids.max` this host has already
25585
+ * hit) and carry no old-space cap unless a manifest declares one. HEAVY
25586
+ * runners keep V8's default young generation but ALWAYS carry an old-space ceiling; see
25587
+ * {@link HEAVY_MAX_OLD_MB_DEFAULT} for why, and how a runner declares its own.
25588
+ *
25589
+ * The per-addon overrides are the manifest's declared `maxOldSpaceMb` (`0` = uncapped),
25590
+ * `semiSpaceMb` (`--max-semi-space-size`, replacing the class default) and
25591
+ * `initialOldSpaceMb` (`--initial-old-space-size`, clamped to the ceiling) — each the MAXIMUM
25592
+ * across a runner's co-located addons. Shipped for `stream-broker` alone (8 / 512): with the
25593
+ * heavy class default the hub's broker ran ~166 major GCs a minute and not one scavenge, at
25594
+ * 45–60 % of a core. The environment overrides and the `CAMSTACK_RUNNER_HEAP_TUNING=off` kill
25595
+ * switch were removed on 2026-10-06 — nothing had set them since the rollout. The flags must
25596
+ * be on the spawn command line: `v8.setFlagsFromString` at runtime changes neither space.
25597
+ */
25598
+ function runnerHeapFlags(addons) {
25599
+ const heavy = isHeavyRunner(addons);
25600
+ const flags = [];
25601
+ const semiMb = runnerSemiSpaceMb(addons) ?? (heavy ? void 0 : 2);
25602
+ if (semiMb !== void 0) flags.push(`--max-semi-space-size=${semiMb}`);
25603
+ if (!heavy) flags.push(`--v8-pool-size=2`);
25604
+ const oldMb = runnerMaxOldSpaceMb(addons, heavy);
25605
+ if (oldMb > 0) flags.push(`--max-old-space-size=${oldMb}`);
25606
+ const initialOldMb = runnerInitialOldSpaceMb(addons);
25607
+ if (initialOldMb !== void 0) {
25608
+ const bounded = oldMb > 0 ? Math.min(initialOldMb, oldMb) : initialOldMb;
25609
+ flags.push(`--initial-old-space-size=${bounded}`);
25610
+ }
25611
+ return flags;
25612
+ }
25613
+ /**
25614
+ * The runner's declared young-generation semi-space in MB, or `undefined` when no
25615
+ * co-located addon declares one (→ the class default applies).
25616
+ *
25617
+ * The MAXIMUM across the group, like every other declaration here (D2/D29): the
25618
+ * member that measured its churn away must not be handed a smaller young
25619
+ * generation because a quieter co-tenant declared one.
25620
+ */
25621
+ function runnerSemiSpaceMb(addons) {
25622
+ const declared = addons.map((a) => readAddonHeapDeclaration(a).semiSpaceMb).filter((mb) => mb !== void 0);
25623
+ return declared.length === 0 ? void 0 : Math.max(...declared);
25624
+ }
25625
+ /**
25626
+ * The runner's declared initial old space in MB, or `undefined` when nobody declares
25627
+ * one (→ V8 grows it from the live set). MAXIMUM across the group, same rule and same
25628
+ * reason as {@link runnerSemiSpaceMb}; the clamp to the runner's ceiling happens in
25629
+ * {@link runnerHeapFlags}, where the ceiling is known.
25630
+ */
25631
+ function runnerInitialOldSpaceMb(addons) {
25632
+ const declared = addons.map((a) => readAddonHeapDeclaration(a).initialOldSpaceMb).filter((mb) => mb !== void 0);
25633
+ return declared.length === 0 ? void 0 : Math.max(...declared);
25634
+ }
25368
25635
  /**
25369
25636
  * Resolve the native-allocator env for a child runner from the parent's env.
25370
25637
  *
@@ -25694,199 +25961,6 @@ async function getPidStats$1(pids) {
25694
25961
  });
25695
25962
  return result;
25696
25963
  }
25697
- var EMPTY_HEAP_DECLARATION = {
25698
- profile: void 0,
25699
- maxOldSpaceMb: void 0,
25700
- rssBudgetMb: void 0
25701
- };
25702
- /**
25703
- * Resolve the declared heap profile + old-space ceiling for one addon by reading its package.json
25704
- * manifest (`camstack.addons[].execution.{heapProfile,maxOldSpaceMb}`). `addonDir` points at the
25705
- * addon's built `dist`, so the manifest is one level up; we also try `addonDir` itself for layouts
25706
- * where it IS the package root.
25707
- * Result is cached per (dir,id) — spawns are infrequent but a runner may re-spawn on crash/restart.
25708
- * Returns an empty declaration when the manifest is unreadable → treated as light, default ceiling.
25709
- */
25710
- var heapProfileCache = /* @__PURE__ */ new Map();
25711
- function readAddonHeapDeclaration(spec) {
25712
- const cacheKey = `${spec.addonDir}::${spec.addonId}`;
25713
- const cached = heapProfileCache.get(cacheKey);
25714
- if (cached !== void 0) return cached;
25715
- let declaration = EMPTY_HEAP_DECLARATION;
25716
- for (const manifestPath of [path$1.join(spec.addonDir, "package.json"), path$1.join(path$1.dirname(spec.addonDir), "package.json")]) try {
25717
- const raw = fs.readFileSync(manifestPath, "utf8");
25718
- const parsed = JSON.parse(raw);
25719
- const profile = extractHeapProfile(parsed, spec.addonId);
25720
- const maxOldSpaceMb = extractMaxOldSpaceMb(parsed, spec.addonId);
25721
- const rssBudgetMb = extractRssBudgetMb(parsed, spec.addonId);
25722
- if (profile !== void 0 || maxOldSpaceMb !== void 0 || rssBudgetMb !== void 0) {
25723
- declaration = {
25724
- profile,
25725
- maxOldSpaceMb,
25726
- rssBudgetMb
25727
- };
25728
- break;
25729
- }
25730
- if (manifestHasAddon(parsed, spec.addonId)) break;
25731
- } catch {}
25732
- heapProfileCache.set(cacheKey, declaration);
25733
- return declaration;
25734
- }
25735
- /**
25736
- * Drop the cached heap declaration for each addon so the NEXT `readAddonHeapDeclaration`
25737
- * re-reads the manifest from disk. Called on operator-driven spawns / restarts
25738
- * (a deploy may have changed `execution.heapProfile` / `execution.maxOldSpaceMb`); the
25739
- * crash-respawn fast path deliberately does NOT invalidate — a crashing runner
25740
- * has the same on-disk manifest, so re-reading it is pure overhead.
25741
- */
25742
- function invalidateHeapProfiles(addons) {
25743
- for (const spec of addons) heapProfileCache.delete(`${spec.addonDir}::${spec.addonId}`);
25744
- }
25745
- function manifestHasAddon(parsed, addonId) {
25746
- return readManifestAddons(parsed).some((a) => a.id === addonId);
25747
- }
25748
- function extractHeapProfile(parsed, addonId) {
25749
- const value = readManifestAddons(parsed).find((a) => a.id === addonId)?.execution?.heapProfile;
25750
- return value === "heavy" || value === "light" ? value : void 0;
25751
- }
25752
- function extractMaxOldSpaceMb(parsed, addonId) {
25753
- const value = readManifestAddons(parsed).find((a) => a.id === addonId)?.execution?.maxOldSpaceMb;
25754
- return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.floor(value) : void 0;
25755
- }
25756
- /**
25757
- * `execution.rssBudgetMb` for one addon. Only a POSITIVE finite number is a budget: unlike
25758
- * `maxOldSpaceMb`, `0` here is not an escape hatch that lifts a cap, it is an absent budget —
25759
- * and an absent budget is reported as "unwatched", never rounded into a default.
25760
- */
25761
- function extractRssBudgetMb(parsed, addonId) {
25762
- const value = readManifestAddons(parsed).find((a) => a.id === addonId)?.execution?.rssBudgetMb;
25763
- return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : void 0;
25764
- }
25765
- function readManifestAddons(parsed) {
25766
- if (typeof parsed !== "object" || parsed === null) return [];
25767
- const camstack = parsed.camstack;
25768
- if (typeof camstack !== "object" || camstack === null) return [];
25769
- const addons = camstack.addons;
25770
- return Array.isArray(addons) ? addons : [];
25771
- }
25772
- /**
25773
- * A runner is heavy if ANY co-located addon declares `heapProfile: 'heavy'` in its manifest.
25774
- * Everything else (the un-annotated default) is a light control-plane runner. No hard-coded names.
25775
- */
25776
- function isHeavyRunner(addons) {
25777
- return addons.some((a) => readAddonHeapDeclaration(a).profile === "heavy");
25778
- }
25779
- /**
25780
- * Default old-space ceiling for a HEAVY runner, in MB.
25781
- *
25782
- * ## Why a heavy runner needs a ceiling at all
25783
- *
25784
- * `heapProfile: 'heavy'` used to mean "V8 defaults", i.e. no `--max-old-space-size` — which on this
25785
- * host is V8's own memory-derived default (multiple GB). Nothing then bounds a burst, and V8 does
25786
- * not hand committed pages back (the long argument for that is in `kernel/heap-watch.ts`), so a
25787
- * one-off burst becomes the runner's permanent RSS.
25788
- *
25789
- * Measured on the live hub, 2026-08-17: the `recorder` runner reaches **2122 MB RSS** and stays
25790
- * there. The same addon on two agent nodes carrying the same code and a smaller archive sits at
25791
- * **58 MB**.
25792
- *
25793
- * ⚠ This paragraph used to attribute that ~2 GB to "walk garbage" from the deferred archive walk,
25794
- * and to argue from a ~21 MB `RecordingIndex`. Both were wrong, and the error cost a session on
25795
- * 2026-08-27. The deferred walk had NO CALLER and never ran (it has since been deleted). And the
25796
- * index is not 21 MB: a 2026-08-27 profile of the live recorder read `heapUsed = 1606 MB` against
25797
- * `external = 8 MB`, with an `smaps` census putting 1510 MB in V8's 256 KB page ranges against
25798
- * 54 MB of native heap — LIVE JavaScript retainers, not high-water garbage. The retained set is
25799
- * ~7.1 M `SegmentRow`s plus their path strings and Map entries (13 cameras x 2 profiles x 8 640
25800
- * segments/day x ~30 days), reseeded at boot by `SegmentHourLedger.hydrateIndex`.
25801
- *
25802
- * The lesson for anyone sizing this flag: a heap number needs a RETAINER, and "garbage the GC has
25803
- * no reason to collect" is the explanation to reach for last, not first.
25804
- *
25805
- * ## Why 1024
25806
- *
25807
- * From the same live topology snapshot, every heavy runner's FULL RSS — which is a strict
25808
- * over-estimate of its old space, since it also contains native allocations, `external` and
25809
- * `arrayBuffers`, none of which `--max-old-space-size` bounds:
25810
- *
25811
- * detection 471 MB · provider-reolink 507 MB · pipeline-orchestrator 238 MB · ml 218 MB ·
25812
- * snapshot 185 MB · decoder-nodeav 174 MB · audio-analyzer 170 MB
25813
- *
25814
- * 1024 MB clears the largest of those by ~2x while cutting the recorder's observed ratchet by
25815
- * more than half. It is a CEILING, not a target: a runner that fits today keeps behaving exactly
25816
- * as it does now, because V8 only collects harder as it approaches the limit.
25817
- *
25818
- * A runner whose working set genuinely does not fit declares its own number in its manifest
25819
- * (`execution.maxOldSpaceMb`) — the number belongs next to the addon that knows its working set.
25820
- * `recorder` (3072 MB; peak 2341 MB heapTotal) is the one shipped addon that does. `stream-broker` does NOT, despite 1585 MB peak RSS: ~1 GB of that is
25821
- * `arrayBuffers` (47 RTP rings), which this flag does not bound and never will — its actual old
25822
- * space peaks at 224 MB. Size the declaration from `heapTotal` in the `[mem]` heartbeat, never
25823
- * from RSS.
25824
- *
25825
- * ## What this flag CANNOT do
25826
- *
25827
- * It bounds old space and nothing else — not `external`, not `arrayBuffers`, not what a native
25828
- * addon mallocs. `hub/pipeline-analytics` reached 7334 MB RSS with this flag set to 1024 the
25829
- * whole time, because 96% of it was native. That half is bounded in `runner-native-allocator.ts`.
25830
- * Reaching for a bigger number here when RSS is the complaint is the standard wrong move.
25831
- */
25832
- var HEAVY_MAX_OLD_MB_DEFAULT = 1024;
25833
- /** Light runners' young-generation semi-space, in MB. */
25834
- var LIGHT_SEMI_SPACE_MB = 2;
25835
- /**
25836
- * The runner's old-space ceiling in MB, or 0 for "uncapped".
25837
- *
25838
- * The runner takes the MAXIMUM ceiling any co-located addon declares — co-location must never
25839
- * silence the hungriest member (D2/D29: a runner is respawned as a whole, so an OOM caused by
25840
- * under-sizing one member kills every addon in the group). An addon declaring `0` therefore lifts
25841
- * the cap for the whole runner, which is the intended escape hatch.
25842
- */
25843
- function runnerMaxOldSpaceMb(addons, heavy) {
25844
- const envDefault = heavy ? HEAVY_MAX_OLD_MB_DEFAULT : 0;
25845
- const declared = addons.map((a) => readAddonHeapDeclaration(a).maxOldSpaceMb).filter((mb) => mb !== void 0);
25846
- if (declared.length === 0) return envDefault;
25847
- if (declared.includes(0)) return 0;
25848
- return Math.max(...declared);
25849
- }
25850
- /**
25851
- * The runner's declared RSS budget in MB, or `undefined` when NOBODY declared one.
25852
- *
25853
- * Same rule as the old-space ceiling, for the same reason (D2/D29): the runner takes the MAXIMUM
25854
- * any co-located addon declares, because a runner is respawned as a whole and co-location must
25855
- * never silence the hungriest member. It would be strictly worse here than for a heap ceiling —
25856
- * an under-sized budget does not kill anything, it just cries wolf, and a watchdog that cries
25857
- * wolf is a watchdog that gets muted.
25858
- *
25859
- * `undefined` propagates as an ABSENT env var, and the child then announces itself unwatched.
25860
- * There is deliberately no class default: see `AddonExecution.rssBudgetMb`.
25861
- */
25862
- function runnerRssBudgetMb(addons) {
25863
- const declared = addons.map((a) => readAddonHeapDeclaration(a).rssBudgetMb).filter((mb) => mb !== void 0);
25864
- return declared.length === 0 ? void 0 : Math.max(...declared);
25865
- }
25866
- /**
25867
- * Per-placement V8 flags (Block D). LIGHT runners cap their young generation
25868
- * (`--max-semi-space-size`) to reclaim V8's over-reservation — a measured ~15 MB/runner win — bound
25869
- * V8's platform thread pool (`--v8-pool-size`, see {@link LIGHT_V8_POOL_SIZE_DEFAULT}: a measured
25870
- * ~58 threads across the hub's 29 light runners, against a cgroup `pids.max` this host has already
25871
- * hit) and carry no old-space cap unless a manifest declares one. HEAVY
25872
- * runners keep V8's default young generation but ALWAYS carry an old-space ceiling; see
25873
- * {@link HEAVY_MAX_OLD_MB_DEFAULT} for why, and how a runner declares its own.
25874
- *
25875
- * The per-addon override is the manifest's declared `maxOldSpaceMb` (`0` = uncapped); the
25876
- * environment overrides and the `CAMSTACK_RUNNER_HEAP_TUNING=off` kill switch were removed on
25877
- * 2026-10-06 — nothing had set them since the rollout.
25878
- */
25879
- function runnerHeapFlags(addons) {
25880
- const heavy = isHeavyRunner(addons);
25881
- const flags = [];
25882
- if (!heavy) {
25883
- flags.push(`--max-semi-space-size=${LIGHT_SEMI_SPACE_MB}`);
25884
- flags.push(`--v8-pool-size=2`);
25885
- }
25886
- const oldMb = runnerMaxOldSpaceMb(addons, heavy);
25887
- if (oldMb > 0) flags.push(`--max-old-space-size=${oldMb}`);
25888
- return flags;
25889
- }
25890
25964
  var MAX_BACKOFF_MS = 3e4;
25891
25965
  var CRASH_WINDOW_MS = 6e4;
25892
25966
  var MAX_CRASHES_IN_WINDOW = 5;
@@ -1,4 +1,4 @@
1
- const require_dist = require("./dist-DnALrQF-.js");
1
+ const require_dist = require("./dist-K3WFop9Q.js");
2
2
  let node_crypto = require("node:crypto");
3
3
  //#region src/auth/jwt-signing-key.ts
4
4
  /**
@@ -1,4 +1,4 @@
1
- import { Sr as buildSecretRef } from "./dist-DMWeqeTF.mjs";
1
+ import { Sr as buildSecretRef } from "./dist-CGz9u2HD.mjs";
2
2
  import { randomBytes } from "node:crypto";
3
3
  //#region src/auth/jwt-signing-key.ts
4
4
  /**
@@ -1,116 +1,7 @@
1
1
  import { type ChildProcess, type SpawnOptions } from "node:child_process";
2
2
  import type { ServiceSchema } from "moleculer";
3
+ import { type RunnerAddonSpec } from "./runner-heap-flags.js";
3
4
  import { type ProcessGroupOps } from "./runner-process-group.js";
4
- /** One addon hosted inside a runner subprocess. */
5
- interface RunnerAddonSpec {
6
- readonly addonId: string;
7
- readonly addonDir: string;
8
- }
9
- /**
10
- * Default old-space ceiling for a HEAVY runner, in MB.
11
- *
12
- * ## Why a heavy runner needs a ceiling at all
13
- *
14
- * `heapProfile: 'heavy'` used to mean "V8 defaults", i.e. no `--max-old-space-size` — which on this
15
- * host is V8's own memory-derived default (multiple GB). Nothing then bounds a burst, and V8 does
16
- * not hand committed pages back (the long argument for that is in `kernel/heap-watch.ts`), so a
17
- * one-off burst becomes the runner's permanent RSS.
18
- *
19
- * Measured on the live hub, 2026-08-17: the `recorder` runner reaches **2122 MB RSS** and stays
20
- * there. The same addon on two agent nodes carrying the same code and a smaller archive sits at
21
- * **58 MB**.
22
- *
23
- * ⚠ This paragraph used to attribute that ~2 GB to "walk garbage" from the deferred archive walk,
24
- * and to argue from a ~21 MB `RecordingIndex`. Both were wrong, and the error cost a session on
25
- * 2026-08-27. The deferred walk had NO CALLER and never ran (it has since been deleted). And the
26
- * index is not 21 MB: a 2026-08-27 profile of the live recorder read `heapUsed = 1606 MB` against
27
- * `external = 8 MB`, with an `smaps` census putting 1510 MB in V8's 256 KB page ranges against
28
- * 54 MB of native heap — LIVE JavaScript retainers, not high-water garbage. The retained set is
29
- * ~7.1 M `SegmentRow`s plus their path strings and Map entries (13 cameras x 2 profiles x 8 640
30
- * segments/day x ~30 days), reseeded at boot by `SegmentHourLedger.hydrateIndex`.
31
- *
32
- * The lesson for anyone sizing this flag: a heap number needs a RETAINER, and "garbage the GC has
33
- * no reason to collect" is the explanation to reach for last, not first.
34
- *
35
- * ## Why 1024
36
- *
37
- * From the same live topology snapshot, every heavy runner's FULL RSS — which is a strict
38
- * over-estimate of its old space, since it also contains native allocations, `external` and
39
- * `arrayBuffers`, none of which `--max-old-space-size` bounds:
40
- *
41
- * detection 471 MB · provider-reolink 507 MB · pipeline-orchestrator 238 MB · ml 218 MB ·
42
- * snapshot 185 MB · decoder-nodeav 174 MB · audio-analyzer 170 MB
43
- *
44
- * 1024 MB clears the largest of those by ~2x while cutting the recorder's observed ratchet by
45
- * more than half. It is a CEILING, not a target: a runner that fits today keeps behaving exactly
46
- * as it does now, because V8 only collects harder as it approaches the limit.
47
- *
48
- * A runner whose working set genuinely does not fit declares its own number in its manifest
49
- * (`execution.maxOldSpaceMb`) — the number belongs next to the addon that knows its working set.
50
- * `recorder` (3072 MB; peak 2341 MB heapTotal) is the one shipped addon that does. `stream-broker` does NOT, despite 1585 MB peak RSS: ~1 GB of that is
51
- * `arrayBuffers` (47 RTP rings), which this flag does not bound and never will — its actual old
52
- * space peaks at 224 MB. Size the declaration from `heapTotal` in the `[mem]` heartbeat, never
53
- * from RSS.
54
- *
55
- * ## What this flag CANNOT do
56
- *
57
- * It bounds old space and nothing else — not `external`, not `arrayBuffers`, not what a native
58
- * addon mallocs. `hub/pipeline-analytics` reached 7334 MB RSS with this flag set to 1024 the
59
- * whole time, because 96% of it was native. That half is bounded in `runner-native-allocator.ts`.
60
- * Reaching for a bigger number here when RSS is the complaint is the standard wrong move.
61
- */
62
- export declare const HEAVY_MAX_OLD_MB_DEFAULT = 1024;
63
- /**
64
- * V8 platform worker threads for a LIGHT runner (`--v8-pool-size`).
65
- *
66
- * ## What this bounds
67
- *
68
- * Node gives every process a V8 platform thread pool — background compilation,
69
- * parallel marking and sweeping — defaulted to 4. It is not the libuv pool
70
- * (`UV_THREADPOOL_SIZE`, which serves `fs`/`dns`/`zlib`/`crypto`) and it is
71
- * deliberately not touched here; see below.
72
- *
73
- * ## Why it is worth a flag
74
- *
75
- * A container cgroup counts THREADS, not processes, and this cluster forks a
76
- * runner per addon (D2). Measured on the live hub 2026-09-18 (20 cores,
77
- * `pids.max=2048`), after 22 fork refusals in 45 minutes earlier that day:
78
- *
79
- * pids.current 991 · 47 node processes holding 469 threads
80
- * of those, 29 LIGHT runners holding 274 (mean 9.4 threads each)
81
- *
82
- * A light runner is a control-plane addon with a 2 MB young generation. It does
83
- * not generate the background GC work four marking threads exist for, and it
84
- * pays 4 threads of the cgroup's pid budget for them anyway. Two returns ~58
85
- * threads — ~6% of the container's live thread count — for background GC that
86
- * is, on these heaps, a few milliseconds a cycle.
87
- *
88
- * ## Why TWO and never ZERO
89
- *
90
- * `--v8-pool-size=0` does not disable the pool: it tells V8 to size it from the
91
- * detected parallelism. Measured locally 2026-09-18 on a node process holding
92
- * the libuv pool open:
93
- *
94
- * default 11 threads
95
- * --v8-pool-size=2 9 threads
96
- * --v8-pool-size=0 18 threads
97
- *
98
- * So the flag is only ever passed with a value of at least 1 — the same trap as
99
- * `MALLOC_ARENA_MAX=0` in `runner-native-allocator.ts`.
100
- *
101
- * ## What is deliberately NOT bounded
102
- *
103
- * HEAVY runners keep V8's default: they hold real heaps (the recorder declares
104
- * 3072 MB) where parallel marking is the difference between a pause and a stall.
105
- *
106
- * `UV_THREADPOOL_SIZE` is left alone on every runner. Its 4 threads are lazily
107
- * spawned, and the measurement above says most light runners have spawned all
108
- * four — i.e. they really do run concurrent `fs` work. Halving that would trade
109
- * ~58 more threads for serialized filesystem I/O in runners whose I/O profile
110
- * has not been measured, on a media system. A thread saving is not worth a
111
- * media gap.
112
- */
113
- export declare const LIGHT_V8_POOL_SIZE_DEFAULT = 2;
114
5
  interface SpawnedProcess {
115
6
  readonly pid: number;
116
7
  readonly nodeId: string;