@camstack/system 1.2.83 → 1.2.85
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/addon-runner.js +1 -1
- package/dist/addon-runner.mjs +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
- package/dist/builtins/alerts/alerts.addon.js +1 -1
- package/dist/builtins/alerts/alerts.addon.mjs +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
- package/dist/builtins/device-manager/device-manager.addon.js +32 -2
- package/dist/builtins/device-manager/device-manager.addon.mjs +32 -2
- package/dist/builtins/device-manager/device-meta-actions.d.ts +12 -0
- package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
- package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
- package/dist/builtins/local-auth/local-auth.addon.js +1 -1
- package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
- package/dist/builtins/local-network/local-network.addon.js +1 -1
- package/dist/builtins/local-network/local-network.addon.mjs +1 -1
- package/dist/builtins/loki-logging/index.js +1 -1
- package/dist/builtins/loki-logging/index.mjs +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
- package/dist/builtins/platform-probe/index.js +1 -1
- package/dist/builtins/platform-probe/index.mjs +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
- package/dist/builtins/snapshot/index.js +66 -11
- package/dist/builtins/snapshot/index.mjs +66 -11
- package/dist/builtins/snapshot/snapshot.addon.d.ts +39 -3
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +30 -10
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +31 -11
- package/dist/builtins/sqlite-storage/path-guard.d.ts +8 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +1 -1
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +1 -1
- package/dist/builtins/storage-orchestrator/index.d.ts +5 -6
- package/dist/builtins/storage-orchestrator/index.mjs +1 -1
- package/dist/builtins/storage-orchestrator/provider-discovery.d.ts +1 -1
- package/dist/builtins/storage-orchestrator/storage-migration-coordinator.d.ts +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.d.ts +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1252 -1191
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1253 -1192
- package/dist/builtins/system-config/system-config.addon.js +1 -1
- package/dist/builtins/system-config/system-config.addon.mjs +1 -1
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{dist-EAvNJD24.mjs → dist-DWuNENzg.mjs} +527 -11
- package/dist/{dist-CqiMPPs0.js → dist-DiScsb8j.js} +527 -11
- package/dist/index.js +83 -18
- package/dist/index.mjs +84 -19
- package/dist/kernel/addon-installer.d.ts +58 -6
- package/dist/kernel/moleculer/event-bus-core.d.ts +47 -1
- package/dist/kernel/transport/child-cap-protocol.d.ts +32 -0
- package/dist/kernel/transport/hub-cap-forward.d.ts +8 -0
- package/dist/kernel/transport/parent-unowned-call.d.ts +19 -0
- package/dist/{manifest-python-deps-DG_LLcZF.js → manifest-python-deps-C0zkl1Ae.js} +133 -53
- package/dist/{manifest-python-deps-C1DMs1J9.mjs → manifest-python-deps-fWzdEasI.mjs} +133 -53
- package/package.json +6 -1
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_chunk = require("./chunk-Cek0wNdY.js");
|
|
3
|
-
const require_dist = require("./dist-
|
|
3
|
+
const require_dist = require("./dist-DiScsb8j.js");
|
|
4
4
|
const require_builtins_alerts_alerts_addon = require("./builtins/alerts/alerts.addon.js");
|
|
5
5
|
require("./builtins/alerts/index.js");
|
|
6
6
|
const require_formatter = require("./formatter-DqAKDlvN.js");
|
|
@@ -25,7 +25,7 @@ const require_builtins_system_config_system_config_addon = require("./builtins/s
|
|
|
25
25
|
require("./builtins/system-config/index.js");
|
|
26
26
|
const require_builtins_winston_logging_index = require("./builtins/winston-logging/index.js");
|
|
27
27
|
const require_file_data_plane = require("./file-data-plane-DUHPHa-Y.js");
|
|
28
|
-
const require_manifest_python_deps = require("./manifest-python-deps-
|
|
28
|
+
const require_manifest_python_deps = require("./manifest-python-deps-C0zkl1Ae.js");
|
|
29
29
|
const require_resource_monitor = require("./resource-monitor-CdnzxBLP.js");
|
|
30
30
|
const require_custom_action_registry = require("./custom-action-registry-jY0NOZK8.js");
|
|
31
31
|
let zod = require("zod");
|
|
@@ -4228,8 +4228,22 @@ var AddonInstaller = class AddonInstaller {
|
|
|
4228
4228
|
* defaults to npm in production.
|
|
4229
4229
|
*
|
|
4230
4230
|
* @param packages — optional custom package list (default: REQUIRED_PACKAGES)
|
|
4231
|
+
* @param pins — optional `name → exact version` map. A named package is
|
|
4232
|
+
* fetched at THAT version instead of the registry's `latest`.
|
|
4233
|
+
*
|
|
4234
|
+
* Preparation for [D45](../../../../docs/decisions/adr-0045.md) task 32: the
|
|
4235
|
+
* image seed answers first today, so `latest` has never been the version a
|
|
4236
|
+
* node actually got. Once the image stops baking an addon tree under `/opt`
|
|
4237
|
+
* this call IS the answer, and it has to be the version the running closure was
|
|
4238
|
+
* published with — otherwise a node fetches latest-of-everything and its
|
|
4239
|
+
* addon set depends on the minute it first booted. Passing nothing keeps
|
|
4240
|
+
* exactly today's behaviour, and `launcher.ts` passes nothing yet.
|
|
4241
|
+
*
|
|
4242
|
+
* A pin never triggers a re-install: bootstrap stays seed-only, so a copy
|
|
4243
|
+
* already under the addon root is left alone whatever its version. Replacing
|
|
4244
|
+
* it would be an un-deploy ([D90](../../../../docs/decisions/adr-0090.md)).
|
|
4231
4245
|
*/
|
|
4232
|
-
async ensureRequiredPackages(packages) {
|
|
4246
|
+
async ensureRequiredPackages(packages, pins) {
|
|
4233
4247
|
const pkgList = packages ?? AddonInstaller.REQUIRED_PACKAGES;
|
|
4234
4248
|
ensureDir(this.addonsDir);
|
|
4235
4249
|
const isLocal = this.installSource !== "npm" && this.workspaceDir != null;
|
|
@@ -4257,7 +4271,7 @@ var AddonInstaller = class AddonInstaller {
|
|
|
4257
4271
|
}
|
|
4258
4272
|
this.logger.info(`${packageName} — not found locally, trying npm`);
|
|
4259
4273
|
}
|
|
4260
|
-
await this.installFromNpm(packageName);
|
|
4274
|
+
await this.installFromNpm(packageName, pins?.[packageName]);
|
|
4261
4275
|
} catch (err) {
|
|
4262
4276
|
const msg = require_dist.errMsg(err);
|
|
4263
4277
|
if (packageName === "@camstack/system") throw new Error(`Required package ${packageName} failed to install: ${msg}`, { cause: err });
|
|
@@ -4651,6 +4665,61 @@ var AddonInstaller = class AddonInstaller {
|
|
|
4651
4665
|
throw new Error(`${packageName} — runtime dependency install failed: ${require_dist.errMsg(err)}`, { cause: err });
|
|
4652
4666
|
}
|
|
4653
4667
|
}
|
|
4668
|
+
/**
|
|
4669
|
+
* Make `pkgDir`'s NATIVE modules loadable on THIS host, before it is swapped
|
|
4670
|
+
* in: install whatever `camstack.nativeDependencies` declares, then ensure
|
|
4671
|
+
* every traditional native present in the tree actually carries a compiled
|
|
4672
|
+
* `.node` for the running ABI.
|
|
4673
|
+
*
|
|
4674
|
+
* ONE implementation, for the same reason as {@link installRuntimeDependencies}
|
|
4675
|
+
* — every path that materialises an addon tree calls it, so no call site can
|
|
4676
|
+
* forget it. `installFromTgz` (deploy/upload) always ran these two steps; the
|
|
4677
|
+
* OTA staged-swap path ran NEITHER even after the runtime-dep fix (2026-08-12,
|
|
4678
|
+
* task #15, which named this gap explicitly). Same outage class one layer
|
|
4679
|
+
* down: an addon with native modules was broken by its FIRST OTA update, its
|
|
4680
|
+
* dist landing without an ABI-matched binary, and every load dying at the
|
|
4681
|
+
* binding (`Could not locate the bindings file`) — from the outside
|
|
4682
|
+
* indistinguishable from a bad bundle.
|
|
4683
|
+
*
|
|
4684
|
+
* **Two distinct steps, both load-bearing.**
|
|
4685
|
+
* `installManifestNativeDeps` fetches the DECLARED natives (prebuilt host copy
|
|
4686
|
+
* first — offline — then npm + an ABI rebuild), and is idempotent through a
|
|
4687
|
+
* content-hash marker. `ensureAddonNativePrebuilds` is the post-install pass:
|
|
4688
|
+
* npm can leave a traditional native (`better-sqlite3`) SOURCE-ONLY on a
|
|
4689
|
+
* toolchain-less host, so it re-fetches the prebuild for the running ABI and
|
|
4690
|
+
* THROWS naming module + addon if the binary is still absent. No bundled
|
|
4691
|
+
* source is available on this path (unlike `installCopy`) — fetch only.
|
|
4692
|
+
*
|
|
4693
|
+
* **Failure is FATAL**, mirroring the tgz path: callers complete the tree
|
|
4694
|
+
* BEFORE they swap, so an abort simply means the swap never happens and the
|
|
4695
|
+
* previous version keeps serving. Shipping a native-less copy instead would
|
|
4696
|
+
* trade one broken version for a crash-loop.
|
|
4697
|
+
*
|
|
4698
|
+
* A missing/unreadable manifest is logged and skipped — `installRuntimeDependencies`
|
|
4699
|
+
* has already said so loudly, and a tree with no readable package.json
|
|
4700
|
+
* declares no natives.
|
|
4701
|
+
*/
|
|
4702
|
+
async installNativeDependencies(pkgDir, packageName) {
|
|
4703
|
+
const pkgJsonPath = node_path.join(pkgDir, "package.json");
|
|
4704
|
+
const pkgView = readPackageJson(pkgJsonPath);
|
|
4705
|
+
if (pkgView == null) {
|
|
4706
|
+
this.logger.error(`${packageName} — no readable package.json in the prepared tree; native dependencies NOT installed`, { meta: { pkgJsonPath } });
|
|
4707
|
+
return;
|
|
4708
|
+
}
|
|
4709
|
+
try {
|
|
4710
|
+
await require_manifest_python_deps.installManifestNativeDeps(pkgDir, pkgView.raw, this.logger, this.registry, this.npmCacheDir);
|
|
4711
|
+
} catch (nativeErr) {
|
|
4712
|
+
this.logger.error(`${packageName} — native dependency install FAILED; install ABORTED (a swapped-in copy would fail every load at the binding)`, { meta: {
|
|
4713
|
+
pkgDir,
|
|
4714
|
+
error: require_dist.errMsg(nativeErr)
|
|
4715
|
+
} });
|
|
4716
|
+
throw new Error(`${packageName} — native deps install failed: ${require_dist.errMsg(nativeErr)}`, { cause: nativeErr });
|
|
4717
|
+
}
|
|
4718
|
+
await ensureAddonNativePrebuilds(pkgDir, {
|
|
4719
|
+
addonName: packageName,
|
|
4720
|
+
logger: this.logger
|
|
4721
|
+
});
|
|
4722
|
+
}
|
|
4654
4723
|
/** Install addon from a tgz file (uploaded or downloaded) */
|
|
4655
4724
|
async installFromTgz(tgzPath) {
|
|
4656
4725
|
const stagingRoot = node_path.join(this.addonsDir, ".staging");
|
|
@@ -4675,15 +4744,7 @@ var AddonInstaller = class AddonInstaller {
|
|
|
4675
4744
|
if (!pkgView.camstackAddons) throw new Error(`Package ${pkgView.name} has no camstack.addons manifest`);
|
|
4676
4745
|
const targetDir = node_path.join(this.addonsDir, pkgView.name);
|
|
4677
4746
|
await this.installRuntimeDependencies(pkgDir, pkgView.name);
|
|
4678
|
-
|
|
4679
|
-
await require_manifest_python_deps.installManifestNativeDeps(pkgDir, pkgView.raw, this.logger, this.registry, this.npmCacheDir);
|
|
4680
|
-
} catch (nativeErr) {
|
|
4681
|
-
throw new Error(`${pkgView.name} — native deps install failed: ${require_dist.errMsg(nativeErr)}`, { cause: nativeErr });
|
|
4682
|
-
}
|
|
4683
|
-
await ensureAddonNativePrebuilds(pkgDir, {
|
|
4684
|
-
addonName: pkgView.name,
|
|
4685
|
-
logger: this.logger
|
|
4686
|
-
});
|
|
4747
|
+
await this.installNativeDependencies(pkgDir, pkgView.name);
|
|
4687
4748
|
await this.evictInstallDir(targetDir);
|
|
4688
4749
|
ensureDir(node_path.dirname(targetDir));
|
|
4689
4750
|
await node_fs.promises.rename(pkgDir, targetDir);
|
|
@@ -4788,9 +4849,12 @@ var AddonInstaller = class AddonInstaller {
|
|
|
4788
4849
|
*
|
|
4789
4850
|
* "Skips npm entirely" is true only of the REGISTRY round-trip: the staged
|
|
4790
4851
|
* tree is the raw unpacked tarball, so the addon's own
|
|
4791
|
-
* `camstack.runtimeDependencies` still have
|
|
4792
|
-
* as `installFromTgz` does. Skipping
|
|
4793
|
-
* addon on its first OTA update
|
|
4852
|
+
* `camstack.runtimeDependencies` AND `camstack.nativeDependencies` still have
|
|
4853
|
+
* to be installed into it, exactly as `installFromTgz` does. Skipping the
|
|
4854
|
+
* first broke every runtime-dep addon on its first OTA update; skipping the
|
|
4855
|
+
* second broke every addon with a native module the same way, one layer down
|
|
4856
|
+
* (`Could not locate the bindings file` instead of `Cannot find module`) —
|
|
4857
|
+
* see {@link installRuntimeDependencies} and {@link installNativeDependencies}.
|
|
4794
4858
|
*
|
|
4795
4859
|
* Order matters and is `installFromTgz`'s: the tree is COMPLETED FIRST, and
|
|
4796
4860
|
* the live install is only then backed up and replaced. So the addon keeps
|
|
@@ -4803,8 +4867,8 @@ var AddonInstaller = class AddonInstaller {
|
|
|
4803
4867
|
* restored and the error is rethrown — identical recovery to `applyUpdate`.
|
|
4804
4868
|
*
|
|
4805
4869
|
* Throws if the package is not tracked in the manifest, if `addonDir`
|
|
4806
|
-
* is missing (same preconditions as `applyUpdate`), or if the runtime-dep
|
|
4807
|
-
* install fails.
|
|
4870
|
+
* is missing (same preconditions as `applyUpdate`), or if the runtime-dep /
|
|
4871
|
+
* native-dep install fails.
|
|
4808
4872
|
*/
|
|
4809
4873
|
async applyUpdateFromStaged(packageName, version, stagedPath) {
|
|
4810
4874
|
const current = this.manifest.get(packageName);
|
|
@@ -4812,6 +4876,7 @@ var AddonInstaller = class AddonInstaller {
|
|
|
4812
4876
|
const addonDir = node_path.join(this.addonsDir, packageName);
|
|
4813
4877
|
if (!node_fs.existsSync(addonDir)) throw new Error(`Cannot update ${packageName}: install dir ${addonDir} missing`);
|
|
4814
4878
|
await this.installRuntimeDependencies(stagedPath, packageName);
|
|
4879
|
+
await this.installNativeDependencies(stagedPath, packageName);
|
|
4815
4880
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
4816
4881
|
const backupRoot = node_path.join(this.addonsDir, ".backups", packageName);
|
|
4817
4882
|
ensureDir(backupRoot);
|
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as __toCommonJS, i as __require, n as __esmMin, o as __toESM$1, r as __exportAll, t as __commonJSMin$1 } from "./chunk-CNf5ZN-e.mjs";
|
|
2
|
-
import { $ as objectInputDeclaresAddonId, At as emitDownForOwnedCaps, B as isArrayOutputSchema, Bt as scopeKey, Ct as ReadinessRegistry, Dt as asNumber, Et as asJsonObject$1, G as kebabToCamel, H as isObjectInput, Ht as EventCategory$1, It as parseJsonObject, K as lifecycleJobSchema, Lt as parseJsonUnknown$1, Mt as expandCapMethods, Ot as asString$1, R as extractNestedAddonId, Rt as readinessKey, V as isCollectionArrayMethod, W as isVoidInput, X as looseSchema, Y as logLevelAtMost, _t as DATAPLANE_SECRET_HEADER$1, d as METHOD_ACCESS_MAP, f as RUNTIME_DEFAULTS, ht as errMsg$1, it as scopesAllowDeviceCap, kt as createEvent, nt as procedureAuthKey, rt as scopesAllowAddon, t as ALL_CAPABILITY_DEFINITIONS, vt as DEVICE_SETTINGS_CONTRIBUTION_METHODS, wt as ReadinessTimeoutError, y as addonSettingsCapability, yt as DEVICE_STATUS_METHOD, zt as resolveCapMount } from "./dist-
|
|
2
|
+
import { $ as objectInputDeclaresAddonId, At as emitDownForOwnedCaps, B as isArrayOutputSchema, Bt as scopeKey, Ct as ReadinessRegistry, Dt as asNumber, Et as asJsonObject$1, G as kebabToCamel, H as isObjectInput, Ht as EventCategory$1, It as parseJsonObject, K as lifecycleJobSchema, Lt as parseJsonUnknown$1, Mt as expandCapMethods, Ot as asString$1, R as extractNestedAddonId, Rt as readinessKey, V as isCollectionArrayMethod, W as isVoidInput, X as looseSchema, Y as logLevelAtMost, _t as DATAPLANE_SECRET_HEADER$1, d as METHOD_ACCESS_MAP, f as RUNTIME_DEFAULTS, ht as errMsg$1, it as scopesAllowDeviceCap, kt as createEvent, nt as procedureAuthKey, rt as scopesAllowAddon, t as ALL_CAPABILITY_DEFINITIONS, vt as DEVICE_SETTINGS_CONTRIBUTION_METHODS, wt as ReadinessTimeoutError, y as addonSettingsCapability, yt as DEVICE_STATUS_METHOD, zt as resolveCapMount } from "./dist-DWuNENzg.mjs";
|
|
3
3
|
import { AlertCenterAddon } from "./builtins/alerts/alerts.addon.mjs";
|
|
4
4
|
import "./builtins/alerts/index.mjs";
|
|
5
5
|
import { t as formatLogLine } from "./formatter-B7qW8bPJ.mjs";
|
|
@@ -18,13 +18,13 @@ import NativeMetricsAddon, { t as NativeMetricsProvider } from "./builtins/nativ
|
|
|
18
18
|
import { FilesystemStorageAddon, t as FilesystemStorageProvider } from "./builtins/sqlite-storage/filesystem-storage.addon.mjs";
|
|
19
19
|
import { SqliteSettingsAddon, o as SqliteSettingsBackend } from "./builtins/sqlite-storage/sqlite-settings.addon.mjs";
|
|
20
20
|
import { ConfigStore, DeviceStore } from "./builtins/sqlite-storage/index.mjs";
|
|
21
|
-
import { StorageOrchestratorAddon,
|
|
21
|
+
import { StorageOrchestratorAddon, t as StorageOrchestratorService } from "./builtins/storage-orchestrator/storage-orchestrator.addon.mjs";
|
|
22
22
|
import "./builtins/storage-orchestrator/index.mjs";
|
|
23
23
|
import { SystemConfigAddon } from "./builtins/system-config/system-config.addon.mjs";
|
|
24
24
|
import "./builtins/system-config/index.mjs";
|
|
25
25
|
import { WinstonDestination, WinstonLoggingAddon } from "./builtins/winston-logging/index.mjs";
|
|
26
26
|
import { a as parseTokenizedUrl, c as collectModelFiles, d as downloadModel, f as ensureModel, h as isModelDownloaded, i as parseRangeHeader, l as deleteModelFromDisk, m as getModelFilePath, n as contentTypeFor, o as resolveFilePath, p as fetchJson, r as createAuthenticatedFileServer, s as ModelDownloadService, t as createFileDataPlaneHandler, u as downloadFile } from "./file-data-plane-CuE_hBli.mjs";
|
|
27
|
-
import { $ as buildNativeCapProxy, A as createHubCapForwardService, B as AGENT_CAP_FWD_ACTION, C as brokerTransportLink, Ct as runNpm, D as localProviderLink, E as ipcParentLink, F as createUdsLogger, G as callWithServiceDiscovery, H as CapRouteResolver, I as createUdsLoggerWithControl, J as UdsLocalTransportServer, K as createLocalTransport, L as LocalChildClient, M as createUdsEventBridge, N as createUdsEventBus, O as HUB_CAP_FWD_ACTION, P as udsChildLogToWorkerEntry, Q as encodeFrame, R as LocalChildRegistry, S as brokerCallForCap, St as resolveNpmInvocation, T as ipcChildLink, U as CapRouteError, V as AGENT_CAP_FWD_SERVICE, W as classifyCapRoute, X as localEndpointPath, Y as SocketChannel, Z as FrameDecoder, _ as createKernelHwAccel, _t as CapabilityHandle, a as getWorkerDeviceRegistry, b as __resetCapUsageRegistryForTests, bt as installManifestNativeDeps, c as setHubConnected, ct as NATIVE_PROVIDER_SERVICE_INFIX, d as getBrokerEventBus, dt as capBareAction, et as buildUdsNativeCapProxy, f as getMoleculerEventStats, ft as capServiceName, g as AddonDepsManager, gt as DeviceRegistry, h as subscribePassthrough, ht as serializeTypedArrays, i as createUdsAddonContext, it as mountNativeCapService, j as createParentUnownedCallHandler, k as HUB_CAP_FWD_SERVICE, l as EVENT_TOPIC_PREFIX, lt as capActionName, m as setNodeEventInterest, mt as deserializeTypedArrays, n as adaptBrokerToCluster, o as getOrInitReadinessRegistry, ot as createAddonService, p as registerEventBusService, pt as parseCapAction, q as UdsLocalTransportClient, r as createAddonContext, s as getOrInitReadinessRegistryForClient, st as validateProviderRegistrations, t as installManifestPythonDeps, tt as createBrokerDeviceManagerApi, u as clusterEventTopic, ut as capActionSuffix, v as resolveHwAccel, vt as CapabilityUnavailableError, w as buildLinkChain, wt as createAddonDataPlaneFacility, x as getCapUsageRegistry, xt as resolveAddonClass, y as CapUsageRegistry, yt as copyBundledNativeModules, z as UDS_NO_ROUTE_PREFIX } from "./manifest-python-deps-
|
|
27
|
+
import { $ as buildNativeCapProxy, A as createHubCapForwardService, B as AGENT_CAP_FWD_ACTION, C as brokerTransportLink, Ct as runNpm, D as localProviderLink, E as ipcParentLink, F as createUdsLogger, G as callWithServiceDiscovery, H as CapRouteResolver, I as createUdsLoggerWithControl, J as UdsLocalTransportServer, K as createLocalTransport, L as LocalChildClient, M as createUdsEventBridge, N as createUdsEventBus, O as HUB_CAP_FWD_ACTION, P as udsChildLogToWorkerEntry, Q as encodeFrame, R as LocalChildRegistry, S as brokerCallForCap, St as resolveNpmInvocation, T as ipcChildLink, U as CapRouteError, V as AGENT_CAP_FWD_SERVICE, W as classifyCapRoute, X as localEndpointPath, Y as SocketChannel, Z as FrameDecoder, _ as createKernelHwAccel, _t as CapabilityHandle, a as getWorkerDeviceRegistry, b as __resetCapUsageRegistryForTests, bt as installManifestNativeDeps, c as setHubConnected, ct as NATIVE_PROVIDER_SERVICE_INFIX, d as getBrokerEventBus, dt as capBareAction, et as buildUdsNativeCapProxy, f as getMoleculerEventStats, ft as capServiceName, g as AddonDepsManager, gt as DeviceRegistry, h as subscribePassthrough, ht as serializeTypedArrays, i as createUdsAddonContext, it as mountNativeCapService, j as createParentUnownedCallHandler, k as HUB_CAP_FWD_SERVICE, l as EVENT_TOPIC_PREFIX, lt as capActionName, m as setNodeEventInterest, mt as deserializeTypedArrays, n as adaptBrokerToCluster, o as getOrInitReadinessRegistry, ot as createAddonService, p as registerEventBusService, pt as parseCapAction, q as UdsLocalTransportClient, r as createAddonContext, s as getOrInitReadinessRegistryForClient, st as validateProviderRegistrations, t as installManifestPythonDeps, tt as createBrokerDeviceManagerApi, u as clusterEventTopic, ut as capActionSuffix, v as resolveHwAccel, vt as CapabilityUnavailableError, w as buildLinkChain, wt as createAddonDataPlaneFacility, x as getCapUsageRegistry, xt as resolveAddonClass, y as CapUsageRegistry, yt as copyBundledNativeModules, z as UDS_NO_ROUTE_PREFIX } from "./manifest-python-deps-fWzdEasI.mjs";
|
|
28
28
|
import { n as getSinglePidStats, t as getPidStats } from "./resource-monitor-BWmQ5i-o.mjs";
|
|
29
29
|
import { t as CustomActionRegistry } from "./custom-action-registry-F__gp_VX.mjs";
|
|
30
30
|
import { z } from "zod";
|
|
@@ -4220,8 +4220,22 @@ var AddonInstaller = class AddonInstaller {
|
|
|
4220
4220
|
* defaults to npm in production.
|
|
4221
4221
|
*
|
|
4222
4222
|
* @param packages — optional custom package list (default: REQUIRED_PACKAGES)
|
|
4223
|
+
* @param pins — optional `name → exact version` map. A named package is
|
|
4224
|
+
* fetched at THAT version instead of the registry's `latest`.
|
|
4225
|
+
*
|
|
4226
|
+
* Preparation for [D45](../../../../docs/decisions/adr-0045.md) task 32: the
|
|
4227
|
+
* image seed answers first today, so `latest` has never been the version a
|
|
4228
|
+
* node actually got. Once the image stops baking an addon tree under `/opt`
|
|
4229
|
+
* this call IS the answer, and it has to be the version the running closure was
|
|
4230
|
+
* published with — otherwise a node fetches latest-of-everything and its
|
|
4231
|
+
* addon set depends on the minute it first booted. Passing nothing keeps
|
|
4232
|
+
* exactly today's behaviour, and `launcher.ts` passes nothing yet.
|
|
4233
|
+
*
|
|
4234
|
+
* A pin never triggers a re-install: bootstrap stays seed-only, so a copy
|
|
4235
|
+
* already under the addon root is left alone whatever its version. Replacing
|
|
4236
|
+
* it would be an un-deploy ([D90](../../../../docs/decisions/adr-0090.md)).
|
|
4223
4237
|
*/
|
|
4224
|
-
async ensureRequiredPackages(packages) {
|
|
4238
|
+
async ensureRequiredPackages(packages, pins) {
|
|
4225
4239
|
const pkgList = packages ?? AddonInstaller.REQUIRED_PACKAGES;
|
|
4226
4240
|
ensureDir(this.addonsDir);
|
|
4227
4241
|
const isLocal = this.installSource !== "npm" && this.workspaceDir != null;
|
|
@@ -4249,7 +4263,7 @@ var AddonInstaller = class AddonInstaller {
|
|
|
4249
4263
|
}
|
|
4250
4264
|
this.logger.info(`${packageName} — not found locally, trying npm`);
|
|
4251
4265
|
}
|
|
4252
|
-
await this.installFromNpm(packageName);
|
|
4266
|
+
await this.installFromNpm(packageName, pins?.[packageName]);
|
|
4253
4267
|
} catch (err) {
|
|
4254
4268
|
const msg = errMsg$1(err);
|
|
4255
4269
|
if (packageName === "@camstack/system") throw new Error(`Required package ${packageName} failed to install: ${msg}`, { cause: err });
|
|
@@ -4643,6 +4657,61 @@ var AddonInstaller = class AddonInstaller {
|
|
|
4643
4657
|
throw new Error(`${packageName} — runtime dependency install failed: ${errMsg$1(err)}`, { cause: err });
|
|
4644
4658
|
}
|
|
4645
4659
|
}
|
|
4660
|
+
/**
|
|
4661
|
+
* Make `pkgDir`'s NATIVE modules loadable on THIS host, before it is swapped
|
|
4662
|
+
* in: install whatever `camstack.nativeDependencies` declares, then ensure
|
|
4663
|
+
* every traditional native present in the tree actually carries a compiled
|
|
4664
|
+
* `.node` for the running ABI.
|
|
4665
|
+
*
|
|
4666
|
+
* ONE implementation, for the same reason as {@link installRuntimeDependencies}
|
|
4667
|
+
* — every path that materialises an addon tree calls it, so no call site can
|
|
4668
|
+
* forget it. `installFromTgz` (deploy/upload) always ran these two steps; the
|
|
4669
|
+
* OTA staged-swap path ran NEITHER even after the runtime-dep fix (2026-08-12,
|
|
4670
|
+
* task #15, which named this gap explicitly). Same outage class one layer
|
|
4671
|
+
* down: an addon with native modules was broken by its FIRST OTA update, its
|
|
4672
|
+
* dist landing without an ABI-matched binary, and every load dying at the
|
|
4673
|
+
* binding (`Could not locate the bindings file`) — from the outside
|
|
4674
|
+
* indistinguishable from a bad bundle.
|
|
4675
|
+
*
|
|
4676
|
+
* **Two distinct steps, both load-bearing.**
|
|
4677
|
+
* `installManifestNativeDeps` fetches the DECLARED natives (prebuilt host copy
|
|
4678
|
+
* first — offline — then npm + an ABI rebuild), and is idempotent through a
|
|
4679
|
+
* content-hash marker. `ensureAddonNativePrebuilds` is the post-install pass:
|
|
4680
|
+
* npm can leave a traditional native (`better-sqlite3`) SOURCE-ONLY on a
|
|
4681
|
+
* toolchain-less host, so it re-fetches the prebuild for the running ABI and
|
|
4682
|
+
* THROWS naming module + addon if the binary is still absent. No bundled
|
|
4683
|
+
* source is available on this path (unlike `installCopy`) — fetch only.
|
|
4684
|
+
*
|
|
4685
|
+
* **Failure is FATAL**, mirroring the tgz path: callers complete the tree
|
|
4686
|
+
* BEFORE they swap, so an abort simply means the swap never happens and the
|
|
4687
|
+
* previous version keeps serving. Shipping a native-less copy instead would
|
|
4688
|
+
* trade one broken version for a crash-loop.
|
|
4689
|
+
*
|
|
4690
|
+
* A missing/unreadable manifest is logged and skipped — `installRuntimeDependencies`
|
|
4691
|
+
* has already said so loudly, and a tree with no readable package.json
|
|
4692
|
+
* declares no natives.
|
|
4693
|
+
*/
|
|
4694
|
+
async installNativeDependencies(pkgDir, packageName) {
|
|
4695
|
+
const pkgJsonPath = path$39.join(pkgDir, "package.json");
|
|
4696
|
+
const pkgView = readPackageJson(pkgJsonPath);
|
|
4697
|
+
if (pkgView == null) {
|
|
4698
|
+
this.logger.error(`${packageName} — no readable package.json in the prepared tree; native dependencies NOT installed`, { meta: { pkgJsonPath } });
|
|
4699
|
+
return;
|
|
4700
|
+
}
|
|
4701
|
+
try {
|
|
4702
|
+
await installManifestNativeDeps(pkgDir, pkgView.raw, this.logger, this.registry, this.npmCacheDir);
|
|
4703
|
+
} catch (nativeErr) {
|
|
4704
|
+
this.logger.error(`${packageName} — native dependency install FAILED; install ABORTED (a swapped-in copy would fail every load at the binding)`, { meta: {
|
|
4705
|
+
pkgDir,
|
|
4706
|
+
error: errMsg$1(nativeErr)
|
|
4707
|
+
} });
|
|
4708
|
+
throw new Error(`${packageName} — native deps install failed: ${errMsg$1(nativeErr)}`, { cause: nativeErr });
|
|
4709
|
+
}
|
|
4710
|
+
await ensureAddonNativePrebuilds(pkgDir, {
|
|
4711
|
+
addonName: packageName,
|
|
4712
|
+
logger: this.logger
|
|
4713
|
+
});
|
|
4714
|
+
}
|
|
4646
4715
|
/** Install addon from a tgz file (uploaded or downloaded) */
|
|
4647
4716
|
async installFromTgz(tgzPath) {
|
|
4648
4717
|
const stagingRoot = path$39.join(this.addonsDir, ".staging");
|
|
@@ -4667,15 +4736,7 @@ var AddonInstaller = class AddonInstaller {
|
|
|
4667
4736
|
if (!pkgView.camstackAddons) throw new Error(`Package ${pkgView.name} has no camstack.addons manifest`);
|
|
4668
4737
|
const targetDir = path$39.join(this.addonsDir, pkgView.name);
|
|
4669
4738
|
await this.installRuntimeDependencies(pkgDir, pkgView.name);
|
|
4670
|
-
|
|
4671
|
-
await installManifestNativeDeps(pkgDir, pkgView.raw, this.logger, this.registry, this.npmCacheDir);
|
|
4672
|
-
} catch (nativeErr) {
|
|
4673
|
-
throw new Error(`${pkgView.name} — native deps install failed: ${errMsg$1(nativeErr)}`, { cause: nativeErr });
|
|
4674
|
-
}
|
|
4675
|
-
await ensureAddonNativePrebuilds(pkgDir, {
|
|
4676
|
-
addonName: pkgView.name,
|
|
4677
|
-
logger: this.logger
|
|
4678
|
-
});
|
|
4739
|
+
await this.installNativeDependencies(pkgDir, pkgView.name);
|
|
4679
4740
|
await this.evictInstallDir(targetDir);
|
|
4680
4741
|
ensureDir(path$39.dirname(targetDir));
|
|
4681
4742
|
await fs$17.promises.rename(pkgDir, targetDir);
|
|
@@ -4780,9 +4841,12 @@ var AddonInstaller = class AddonInstaller {
|
|
|
4780
4841
|
*
|
|
4781
4842
|
* "Skips npm entirely" is true only of the REGISTRY round-trip: the staged
|
|
4782
4843
|
* tree is the raw unpacked tarball, so the addon's own
|
|
4783
|
-
* `camstack.runtimeDependencies` still have
|
|
4784
|
-
* as `installFromTgz` does. Skipping
|
|
4785
|
-
* addon on its first OTA update
|
|
4844
|
+
* `camstack.runtimeDependencies` AND `camstack.nativeDependencies` still have
|
|
4845
|
+
* to be installed into it, exactly as `installFromTgz` does. Skipping the
|
|
4846
|
+
* first broke every runtime-dep addon on its first OTA update; skipping the
|
|
4847
|
+
* second broke every addon with a native module the same way, one layer down
|
|
4848
|
+
* (`Could not locate the bindings file` instead of `Cannot find module`) —
|
|
4849
|
+
* see {@link installRuntimeDependencies} and {@link installNativeDependencies}.
|
|
4786
4850
|
*
|
|
4787
4851
|
* Order matters and is `installFromTgz`'s: the tree is COMPLETED FIRST, and
|
|
4788
4852
|
* the live install is only then backed up and replaced. So the addon keeps
|
|
@@ -4795,8 +4859,8 @@ var AddonInstaller = class AddonInstaller {
|
|
|
4795
4859
|
* restored and the error is rethrown — identical recovery to `applyUpdate`.
|
|
4796
4860
|
*
|
|
4797
4861
|
* Throws if the package is not tracked in the manifest, if `addonDir`
|
|
4798
|
-
* is missing (same preconditions as `applyUpdate`), or if the runtime-dep
|
|
4799
|
-
* install fails.
|
|
4862
|
+
* is missing (same preconditions as `applyUpdate`), or if the runtime-dep /
|
|
4863
|
+
* native-dep install fails.
|
|
4800
4864
|
*/
|
|
4801
4865
|
async applyUpdateFromStaged(packageName, version, stagedPath) {
|
|
4802
4866
|
const current = this.manifest.get(packageName);
|
|
@@ -4804,6 +4868,7 @@ var AddonInstaller = class AddonInstaller {
|
|
|
4804
4868
|
const addonDir = path$39.join(this.addonsDir, packageName);
|
|
4805
4869
|
if (!fs$17.existsSync(addonDir)) throw new Error(`Cannot update ${packageName}: install dir ${addonDir} missing`);
|
|
4806
4870
|
await this.installRuntimeDependencies(stagedPath, packageName);
|
|
4871
|
+
await this.installNativeDependencies(stagedPath, packageName);
|
|
4807
4872
|
const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
4808
4873
|
const backupRoot = path$39.join(this.addonsDir, ".backups", packageName);
|
|
4809
4874
|
ensureDir(backupRoot);
|
|
@@ -187,8 +187,22 @@ export declare class AddonInstaller {
|
|
|
187
187
|
* defaults to npm in production.
|
|
188
188
|
*
|
|
189
189
|
* @param packages — optional custom package list (default: REQUIRED_PACKAGES)
|
|
190
|
+
* @param pins — optional `name → exact version` map. A named package is
|
|
191
|
+
* fetched at THAT version instead of the registry's `latest`.
|
|
192
|
+
*
|
|
193
|
+
* Preparation for [D45](../../../../docs/decisions/adr-0045.md) task 32: the
|
|
194
|
+
* image seed answers first today, so `latest` has never been the version a
|
|
195
|
+
* node actually got. Once the image stops baking an addon tree under `/opt`
|
|
196
|
+
* this call IS the answer, and it has to be the version the running closure was
|
|
197
|
+
* published with — otherwise a node fetches latest-of-everything and its
|
|
198
|
+
* addon set depends on the minute it first booted. Passing nothing keeps
|
|
199
|
+
* exactly today's behaviour, and `launcher.ts` passes nothing yet.
|
|
200
|
+
*
|
|
201
|
+
* A pin never triggers a re-install: bootstrap stays seed-only, so a copy
|
|
202
|
+
* already under the addon root is left alone whatever its version. Replacing
|
|
203
|
+
* it would be an un-deploy ([D90](../../../../docs/decisions/adr-0090.md)).
|
|
190
204
|
*/
|
|
191
|
-
ensureRequiredPackages(packages?: readonly string[]): Promise<void>;
|
|
205
|
+
ensureRequiredPackages(packages?: readonly string[], pins?: Readonly<Record<string, string>>): Promise<void>;
|
|
192
206
|
/**
|
|
193
207
|
* Reconcile the install manifest against the addons actually present on
|
|
194
208
|
* disk: register seeded addons, correct a version that disagrees with the
|
|
@@ -341,6 +355,41 @@ export declare class AddonInstaller {
|
|
|
341
355
|
* package entirely).
|
|
342
356
|
*/
|
|
343
357
|
private installRuntimeDependencies;
|
|
358
|
+
/**
|
|
359
|
+
* Make `pkgDir`'s NATIVE modules loadable on THIS host, before it is swapped
|
|
360
|
+
* in: install whatever `camstack.nativeDependencies` declares, then ensure
|
|
361
|
+
* every traditional native present in the tree actually carries a compiled
|
|
362
|
+
* `.node` for the running ABI.
|
|
363
|
+
*
|
|
364
|
+
* ONE implementation, for the same reason as {@link installRuntimeDependencies}
|
|
365
|
+
* — every path that materialises an addon tree calls it, so no call site can
|
|
366
|
+
* forget it. `installFromTgz` (deploy/upload) always ran these two steps; the
|
|
367
|
+
* OTA staged-swap path ran NEITHER even after the runtime-dep fix (2026-08-12,
|
|
368
|
+
* task #15, which named this gap explicitly). Same outage class one layer
|
|
369
|
+
* down: an addon with native modules was broken by its FIRST OTA update, its
|
|
370
|
+
* dist landing without an ABI-matched binary, and every load dying at the
|
|
371
|
+
* binding (`Could not locate the bindings file`) — from the outside
|
|
372
|
+
* indistinguishable from a bad bundle.
|
|
373
|
+
*
|
|
374
|
+
* **Two distinct steps, both load-bearing.**
|
|
375
|
+
* `installManifestNativeDeps` fetches the DECLARED natives (prebuilt host copy
|
|
376
|
+
* first — offline — then npm + an ABI rebuild), and is idempotent through a
|
|
377
|
+
* content-hash marker. `ensureAddonNativePrebuilds` is the post-install pass:
|
|
378
|
+
* npm can leave a traditional native (`better-sqlite3`) SOURCE-ONLY on a
|
|
379
|
+
* toolchain-less host, so it re-fetches the prebuild for the running ABI and
|
|
380
|
+
* THROWS naming module + addon if the binary is still absent. No bundled
|
|
381
|
+
* source is available on this path (unlike `installCopy`) — fetch only.
|
|
382
|
+
*
|
|
383
|
+
* **Failure is FATAL**, mirroring the tgz path: callers complete the tree
|
|
384
|
+
* BEFORE they swap, so an abort simply means the swap never happens and the
|
|
385
|
+
* previous version keeps serving. Shipping a native-less copy instead would
|
|
386
|
+
* trade one broken version for a crash-loop.
|
|
387
|
+
*
|
|
388
|
+
* A missing/unreadable manifest is logged and skipped — `installRuntimeDependencies`
|
|
389
|
+
* has already said so loudly, and a tree with no readable package.json
|
|
390
|
+
* declares no natives.
|
|
391
|
+
*/
|
|
392
|
+
private installNativeDependencies;
|
|
344
393
|
/** Install addon from a tgz file (uploaded or downloaded) */
|
|
345
394
|
installFromTgz(tgzPath: string): Promise<{
|
|
346
395
|
name: string;
|
|
@@ -381,9 +430,12 @@ export declare class AddonInstaller {
|
|
|
381
430
|
*
|
|
382
431
|
* "Skips npm entirely" is true only of the REGISTRY round-trip: the staged
|
|
383
432
|
* tree is the raw unpacked tarball, so the addon's own
|
|
384
|
-
* `camstack.runtimeDependencies` still have
|
|
385
|
-
* as `installFromTgz` does. Skipping
|
|
386
|
-
* addon on its first OTA update
|
|
433
|
+
* `camstack.runtimeDependencies` AND `camstack.nativeDependencies` still have
|
|
434
|
+
* to be installed into it, exactly as `installFromTgz` does. Skipping the
|
|
435
|
+
* first broke every runtime-dep addon on its first OTA update; skipping the
|
|
436
|
+
* second broke every addon with a native module the same way, one layer down
|
|
437
|
+
* (`Could not locate the bindings file` instead of `Cannot find module`) —
|
|
438
|
+
* see {@link installRuntimeDependencies} and {@link installNativeDependencies}.
|
|
387
439
|
*
|
|
388
440
|
* Order matters and is `installFromTgz`'s: the tree is COMPLETED FIRST, and
|
|
389
441
|
* the live install is only then backed up and replaced. So the addon keeps
|
|
@@ -396,8 +448,8 @@ export declare class AddonInstaller {
|
|
|
396
448
|
* restored and the error is rethrown — identical recovery to `applyUpdate`.
|
|
397
449
|
*
|
|
398
450
|
* Throws if the package is not tracked in the manifest, if `addonDir`
|
|
399
|
-
* is missing (same preconditions as `applyUpdate`), or if the runtime-dep
|
|
400
|
-
* install fails.
|
|
451
|
+
* is missing (same preconditions as `applyUpdate`), or if the runtime-dep /
|
|
452
|
+
* native-dep install fails.
|
|
401
453
|
*/
|
|
402
454
|
applyUpdateFromStaged(packageName: string, version: string, stagedPath: string): Promise<{
|
|
403
455
|
name: string;
|
|
@@ -2,7 +2,12 @@ import { EventFilter, SystemEvent } from '@camstack/types';
|
|
|
2
2
|
export interface SubscriberEntry {
|
|
3
3
|
/** Original filter — kept whole so non-category dimensions are honoured per-event. */
|
|
4
4
|
readonly filter: EventFilter | string | undefined;
|
|
5
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* Pre-computed category pattern for the cheap pre-filter — the FIRST of
|
|
7
|
+
* {@link extractCategoryPatterns} when the filter names several. Diagnostic
|
|
8
|
+
* only: the authority on which keys an entry occupies is the handler map
|
|
9
|
+
* itself, written by {@link registerSubscriber}.
|
|
10
|
+
*/
|
|
6
11
|
readonly categoryPattern: string;
|
|
7
12
|
readonly handler: (event: SystemEvent) => void;
|
|
8
13
|
}
|
|
@@ -47,7 +52,48 @@ export interface SharedBusState {
|
|
|
47
52
|
*/
|
|
48
53
|
export declare function createSharedBusState(retainRecent?: boolean): SharedBusState;
|
|
49
54
|
export declare function matchesPattern(pattern: string, category: string): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* The FIRST category pattern of a filter.
|
|
57
|
+
*
|
|
58
|
+
* Kept for the diagnostic `SubscriberEntry.categoryPattern` field and for
|
|
59
|
+
* callers that genuinely want one string. It is NOT the registration path:
|
|
60
|
+
* a filter naming several categories must occupy several map keys, which is
|
|
61
|
+
* what {@link extractCategoryPatterns} + {@link registerSubscriber} do. Using
|
|
62
|
+
* this function to key a subscriber is the bug described on
|
|
63
|
+
* `extractCategoryPatterns`.
|
|
64
|
+
*/
|
|
50
65
|
export declare function extractCategoryPattern(filter: EventFilter | string): string;
|
|
66
|
+
/**
|
|
67
|
+
* EVERY category pattern a filter asks for — the registration key set.
|
|
68
|
+
*
|
|
69
|
+
* `EventFilter.category` has always accepted an array, and until 2026-08-12
|
|
70
|
+
* the delivery path kept only `cat[0]`. Inside a forked runner that was not
|
|
71
|
+
* merely a local filter: `createUdsEventBus` reports its handler-map keys to
|
|
72
|
+
* the parent as this child's forwarding interest, so the categories that never
|
|
73
|
+
* became keys were never forwarded across the process boundary at all. A
|
|
74
|
+
* ten-category subscribe received one category, silently.
|
|
75
|
+
*
|
|
76
|
+
* Duplicates are collapsed so a filter that names the same category twice does
|
|
77
|
+
* not put the entry in one key twice.
|
|
78
|
+
*/
|
|
79
|
+
export declare function extractCategoryPatterns(filter: EventFilter | string): readonly string[];
|
|
80
|
+
/**
|
|
81
|
+
* Register one handler under every category pattern its filter names, and
|
|
82
|
+
* return the unsubscribe that removes it from all of them.
|
|
83
|
+
*
|
|
84
|
+
* The ONE place a subscriber enters `state.handlers` — both bus
|
|
85
|
+
* implementations (broker and UDS) call it, so "which keys does this
|
|
86
|
+
* subscriber occupy" has a single answer. An emptied key is DELETED rather
|
|
87
|
+
* than left as an empty `Set`: on a child bus the key set is what the parent
|
|
88
|
+
* is told to forward, and an empty set left behind pins the parent to forward
|
|
89
|
+
* a category nobody wants.
|
|
90
|
+
*
|
|
91
|
+
* @param onKeysChanged - Called after the key set may have changed (both on
|
|
92
|
+
* registration and on unsubscribe), for buses that mirror their key set
|
|
93
|
+
* somewhere — the UDS child's parent report, the broker's local-interest
|
|
94
|
+
* surface. Receives the patterns this call touched.
|
|
95
|
+
*/
|
|
96
|
+
export declare function registerSubscriber(state: SharedBusState, filter: EventFilter | string, handler: (event: SystemEvent) => void, onKeysChanged?: (patterns: readonly string[], added: boolean) => void): () => void;
|
|
51
97
|
/**
|
|
52
98
|
* Match an event against the full `EventFilter`. The Map-keyed subscribe path
|
|
53
99
|
* only handles the category pattern (deciding which handler set to fan out to);
|
|
@@ -62,6 +62,32 @@ export interface CapCallOutMessage {
|
|
|
62
62
|
* `platform-probe` hardware). Set via `ctx.api`'s `onNode(nodeId)` modifier.
|
|
63
63
|
*/
|
|
64
64
|
readonly nodeId?: string;
|
|
65
|
+
/**
|
|
66
|
+
* "Resolve the per-device NATIVE provider for this cap, never a wrapper."
|
|
67
|
+
* Set by `ctx.getNativeProvider(...)` in a forked runner.
|
|
68
|
+
*
|
|
69
|
+
* ## Why a flag and not just the cap name
|
|
70
|
+
*
|
|
71
|
+
* A wrapper and its natives share ONE cap name. As long as the wrapper lived
|
|
72
|
+
* in hub-main that was harmless — the wrapper resolved its native through the
|
|
73
|
+
* hub's `CapabilityRegistry`, which knows the difference. A wrapper in a
|
|
74
|
+
* runner has no such registry, and every generic route back out of the
|
|
75
|
+
* process resolves `capName` the way every OTHER caller does: to the wrapper.
|
|
76
|
+
* `localProviderLink` answers it from the runner's own provider before the
|
|
77
|
+
* call ever reaches the socket, and `resolveChildId` would hand it back to
|
|
78
|
+
* the wrapper's own child if it did. Either way the wrapper calls itself:
|
|
79
|
+
* the second entry joins the first's single flight and waits on a promise
|
|
80
|
+
* only it can settle.
|
|
81
|
+
*
|
|
82
|
+
* So the intent has to travel. `native: true` makes the parent skip
|
|
83
|
+
* capName-based resolution entirely and ask the ONE native authority
|
|
84
|
+
* (`CapabilityRegistry.getNativeProvider`, which covers in-process natives,
|
|
85
|
+
* hub-local UDS vendor children and remote agents alike) for `(cap, device)`.
|
|
86
|
+
* With no native for the pair the call FAILS — it never degrades into the
|
|
87
|
+
* wrapper — with a message a wrapper can read as "absent" and fall through to
|
|
88
|
+
* its own strategy.
|
|
89
|
+
*/
|
|
90
|
+
readonly native?: boolean;
|
|
65
91
|
}
|
|
66
92
|
/** Child → parent: a system event produced by the child to be forwarded to the hub event bus. */
|
|
67
93
|
export interface ChildEventMessage {
|
|
@@ -110,6 +136,12 @@ export interface CapCallMessage {
|
|
|
110
136
|
* the child dispatch falls back to its sole provider for the cap.
|
|
111
137
|
*/
|
|
112
138
|
readonly addonId?: string;
|
|
139
|
+
/**
|
|
140
|
+
* Native-only routing hint — see {@link CapCallOutMessage.native}. Carried
|
|
141
|
+
* here because `CapCallInput` (the type every parent-side router takes) is
|
|
142
|
+
* this message minus its discriminant; a child DISPATCH ignores it.
|
|
143
|
+
*/
|
|
144
|
+
readonly native?: boolean;
|
|
113
145
|
}
|
|
114
146
|
/** Parent → child: a system event delivered by the hub for the child to handle locally. */
|
|
115
147
|
export interface ParentEventMessage {
|
|
@@ -16,6 +16,14 @@ export interface HubCapForwardParams {
|
|
|
16
16
|
readonly args: unknown;
|
|
17
17
|
readonly deviceId?: number;
|
|
18
18
|
readonly nodeId?: string;
|
|
19
|
+
/**
|
|
20
|
+
* Native-only routing hint (see `CapCallOutMessage.native`). An agent has no
|
|
21
|
+
* native authority of its own, so a forked wrapper on an agent asking for a
|
|
22
|
+
* per-device native forwards the question here. It MUST survive the envelope:
|
|
23
|
+
* without it the hub resolves the cap NAME, i.e. the wrapper the caller is
|
|
24
|
+
* trying to skip.
|
|
25
|
+
*/
|
|
26
|
+
readonly native?: boolean;
|
|
19
27
|
}
|
|
20
28
|
/**
|
|
21
29
|
* Handler that routes an agent-originated cap call through the hub's own
|
|
@@ -183,6 +183,25 @@ export interface ParentUnownedCallDeps {
|
|
|
183
183
|
* exactly: the miss falls straight to the broker fallback with no retry.
|
|
184
184
|
*/
|
|
185
185
|
readonly isCoreServiceCap?: (capName: string) => boolean;
|
|
186
|
+
/**
|
|
187
|
+
* Resolve the per-device NATIVE provider for `(capName, deviceId)` — the
|
|
188
|
+
* `native: true` route (see {@link CapCallInput.native}).
|
|
189
|
+
*
|
|
190
|
+
* Wired to this node's `CapabilityRegistry.getNativeProvider`, which is the
|
|
191
|
+
* SINGLE authority on the question: it already covers in-process natives,
|
|
192
|
+
* hub-local UDS vendor children (through the hub's `setNativeFallback`
|
|
193
|
+
* resolver) and remote agents. Nothing here re-derives that — a rule
|
|
194
|
+
* implemented twice is a rule that drifts, and this one previously drifted
|
|
195
|
+
* into a wrapper answering on behalf of the native it was supposed to call.
|
|
196
|
+
*
|
|
197
|
+
* Returns null when no addon publishes the cap for that device. The handler
|
|
198
|
+
* then FAILS the call rather than falling back to capName routing: falling
|
|
199
|
+
* back is how a wrapper ends up calling itself.
|
|
200
|
+
*
|
|
201
|
+
* Omitted ⇒ a `native: true` call is forwarded to the hub (agents) or
|
|
202
|
+
* rejected (no forwarder) — never silently downgraded to wrapper routing.
|
|
203
|
+
*/
|
|
204
|
+
readonly resolveNative?: (capName: string, deviceId: number) => Record<string, (input: unknown) => Promise<unknown>> | null;
|
|
186
205
|
/** Optional logger for the broker-fallback diagnostic line. */
|
|
187
206
|
readonly logger?: ParentUnownedCallLogger;
|
|
188
207
|
}
|