@camstack/server 1.2.592 → 1.2.594
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-root-inventory.js +11 -50
- package/dist/agent/addon-scan.js +6 -44
- package/dist/agent/agent-addon-runtime.js +3 -8
- package/dist/agent/agent-addon-updates.js +12 -52
- package/dist/agent/agent-cap-dispatch-service.js +3 -5
- package/dist/agent/agent-config.js +7 -42
- package/dist/agent/agent-deploy-swap.js +5 -41
- package/dist/agent/agent-group-runner.js +8 -47
- package/dist/agent/agent-http-auth.js +10 -22
- package/dist/agent/agent-http.js +69 -115
- package/dist/agent/agent-hub-addon-update.js +10 -16
- package/dist/agent/agent-hub-registration.js +4 -7
- package/dist/agent/agent-infra-capabilities.js +5 -9
- package/dist/agent/agent-json-file.js +5 -42
- package/dist/agent/agent-mesh-gate.js +5 -8
- package/dist/agent/agent-node-certificates.js +25 -28
- package/dist/agent/agent-pairing-local.js +10 -47
- package/dist/agent/agent-pairing-routes.js +16 -20
- package/dist/agent/agent-pairing-store.js +18 -58
- package/dist/agent/agent-reconnect.js +1 -4
- package/dist/agent/agent-service.js +42 -78
- package/dist/agent/agent-sniffing-listener.js +11 -48
- package/dist/agent/agent-undeploy.js +5 -41
- package/dist/agent/agent-update-routes.js +14 -19
- package/dist/agent/agent-update-service.js +38 -80
- package/dist/agent/apply-model-distribution.js +1 -4
- package/dist/agent/apply-stage-dev-uploads.js +6 -9
- package/dist/agent/derive-hub-url.js +3 -8
- package/dist/agent/fetch-bundle-from-hub.js +6 -9
- package/dist/agent/hubs-found.js +6 -43
- package/dist/agent/infra-boot-guard.js +1 -4
- package/dist/agent/infra-boot-plan.js +2 -6
- package/dist/agent/main.js +158 -186
- package/dist/agent/register-agent-cap-dispatch.js +3 -6
- package/dist/agent/serial-queue.js +2 -7
- package/dist/agent-status-page.js +1 -4
- package/dist/api/addon-upload.js +22 -61
- package/dist/api/agent-addon-delivery.js +6 -11
- package/dist/api/auth-whoami.js +1 -4
- package/dist/api/backup-download-pump.js +5 -9
- package/dist/api/backup-download.js +7 -10
- package/dist/api/core/addon-settings.router.js +16 -19
- package/dist/api/core/agent-pairing.router.js +76 -79
- package/dist/api/core/agents.router.js +21 -24
- package/dist/api/core/auth-admin-tokens.procedures.js +48 -52
- package/dist/api/core/auth-device-cert.procedures.js +62 -67
- package/dist/api/core/auth-limits.js +16 -25
- package/dist/api/core/auth-reauth.procedures.js +32 -37
- package/dist/api/core/auth-revoke-credentials.procedures.js +22 -26
- package/dist/api/core/auth-sessions.procedures.js +52 -56
- package/dist/api/core/auth.router.js +300 -304
- package/dist/api/core/cap-providers.js +74 -121
- package/dist/api/core/capabilities.router.js +34 -37
- package/dist/api/core/capability-preference-writes.js +10 -17
- package/dist/api/core/certificates.router.js +106 -109
- package/dist/api/core/cluster-nodes.router.js +15 -18
- package/dist/api/core/collection-preference.js +2 -6
- package/dist/api/core/event-bus-proxy.router.js +34 -37
- package/dist/api/core/failure-contributions.js +4 -8
- package/dist/api/core/hwaccel.router.js +17 -20
- package/dist/api/core/integration-secrets.js +8 -12
- package/dist/api/core/lifecycle-job-runner.js +13 -49
- package/dist/api/core/live-events.router.js +29 -32
- package/dist/api/core/load-contributions.js +4 -8
- package/dist/api/core/logging-settings.js +30 -43
- package/dist/api/core/logs.router.js +40 -43
- package/dist/api/core/notifications.router.js +15 -18
- package/dist/api/core/public-event-filter.js +4 -8
- package/dist/api/core/repl.router.js +18 -21
- package/dist/api/core/request-census-settings.js +11 -16
- package/dist/api/core/server-management.provider.js +1 -4
- package/dist/api/core/server-name.js +9 -17
- package/dist/api/core/site-location.js +16 -24
- package/dist/api/core/stream-probe.router.js +21 -24
- package/dist/api/core/system-events.router.js +23 -26
- package/dist/api/cors/cors-policy.js +20 -29
- package/dist/api/cors/register-cors-policy.js +9 -14
- package/dist/api/deploy-stage-registry.js +6 -10
- package/dist/api/dev-uploads-bundle.js +12 -50
- package/dist/api/health/adaptive-probe.routes.js +9 -13
- package/dist/api/health/health.routes.js +8 -13
- package/dist/api/hub-cluster-url.js +3 -9
- package/dist/api/model-distributor.js +12 -50
- package/dist/api/oauth2/consent-page.js +10 -13
- package/dist/api/oauth2/oauth2-routes.js +17 -25
- package/dist/api/oauth2/private-host.js +1 -4
- package/dist/api/server-upload-propagate.js +15 -51
- package/dist/api/server-upload.js +13 -50
- package/dist/api/sse-no-compression.js +1 -4
- package/dist/api/static/frame-policy.js +4 -9
- package/dist/api/static/precompressed-asset.js +15 -57
- package/dist/api/static/spa-mount.js +8 -47
- package/dist/api/static/spa-static.js +5 -12
- package/dist/api/tarball-manifest.js +7 -44
- package/dist/api/trpc/cap-mount-helpers.js +7 -12
- package/dist/api/trpc/cap-route-error-formatter.js +10 -13
- package/dist/api/trpc/cap-router-runtime.js +40 -51
- package/dist/api/trpc/client-ip.js +9 -20
- package/dist/api/trpc/core-cap-bridge.js +32 -42
- package/dist/api/trpc/device-collection-dispatch.js +23 -27
- package/dist/api/trpc/device-config-secret-redaction.js +24 -40
- package/dist/api/trpc/generated-cap-mounts.js +219 -221
- package/dist/api/trpc/generated-cap-routers.js +4878 -4726
- package/dist/api/trpc/http-request-census-hook.js +8 -11
- package/dist/api/trpc/http-request-census.js +34 -41
- package/dist/api/trpc/http-request-census.singleton.js +4 -8
- package/dist/api/trpc/localized-schema-paths.js +1 -4
- package/dist/api/trpc/principal-visibility.js +14 -22
- package/dist/api/trpc/remote-addon-update-grant.js +1 -4
- package/dist/api/trpc/scope-access.js +10 -15
- package/dist/api/trpc/server-provided-caps.js +3 -7
- package/dist/api/trpc/share-view-access.js +6 -14
- package/dist/api/trpc/trpc-error-device-tags.js +3 -6
- package/dist/api/trpc/trpc-error-principal.js +2 -6
- package/dist/api/trpc/trpc.context.js +20 -25
- package/dist/api/trpc/trpc.middleware.js +38 -46
- package/dist/api/trpc/trpc.router.js +58 -99
- package/dist/api/trpc/user-error.js +5 -8
- package/dist/api/trpc/ws-request-census.js +17 -24
- package/dist/api/trpc/ws-slow-consumer-guard.js +11 -16
- package/dist/api/upload-auth.js +5 -9
- package/dist/auth/addon-route-jwt-gate.js +4 -7
- package/dist/auth/addon-route-share-gate.js +7 -12
- package/dist/auth/app-confirm-page.js +17 -22
- package/dist/auth/app-confirm-routes.js +26 -31
- package/dist/auth/app-confirmations.js +7 -44
- package/dist/auth/app-page-shell.js +17 -30
- package/dist/auth/app-page-strings.js +11 -17
- package/dist/auth/app-passkey-page.js +16 -21
- package/dist/auth/app-passkey-routes.js +37 -41
- package/dist/auth/app-refused-route.js +13 -18
- package/dist/auth/auth-network-policy.js +11 -15
- package/dist/auth/auth-provider-gate.js +4 -9
- package/dist/auth/auth-rate-limit.js +3 -7
- package/dist/auth/client-address.js +6 -9
- package/dist/auth/device-enroll-proofs.js +6 -43
- package/dist/auth/external-link-intents.js +3 -7
- package/dist/auth/hub-sessions.js +6 -12
- package/dist/auth/native-link-completions.js +7 -11
- package/dist/auth/native-return.js +9 -13
- package/dist/auth/passkey-provider.js +26 -32
- package/dist/auth/second-factors.js +6 -10
- package/dist/auth/session-connections.js +6 -12
- package/dist/auth/session-cookie.js +12 -23
- package/dist/auth/session-lifetimes.js +8 -12
- package/dist/auth/session-mint.js +4 -11
- package/dist/auth/sso-finish-app.js +13 -16
- package/dist/auth/sso-finish-deps.js +21 -27
- package/dist/auth/sso-finish.js +28 -38
- package/dist/auth/sso-routes.js +22 -25
- package/dist/auth/used-bridge-ids.js +1 -5
- package/dist/boot/boot-config.js +16 -55
- package/dist/boot/init-sequence.js +1 -4
- package/dist/boot/integration-id-backfill.js +3 -8
- package/dist/boot/platform-keychain-key.js +5 -42
- package/dist/boot/post-boot.service.js +14 -18
- package/dist/boot/reconcile-lifecycle-jobs.js +3 -6
- package/dist/boot/retired-public-url-env.js +4 -8
- package/dist/bootstrap-packages.js +1 -4
- package/dist/closure-node-modules.js +2 -38
- package/dist/closure-patches/patch-reach.json +6 -2
- package/dist/core/addon/addon-call-gateway.js +1 -5
- package/dist/core/addon/addon-package.service.js +83 -123
- package/dist/core/addon/addon-process-view.js +2 -6
- package/dist/core/addon/addon-realign-plan.js +1 -4
- package/dist/core/addon/addon-registry.service.js +254 -197
- package/dist/core/addon/addon-row-manifest.js +1 -4
- package/dist/core/addon/addon-search.service.js +3 -7
- package/dist/core/addon/addon-settings-provider.js +1 -3
- package/dist/core/addon/addon-uninstall.js +4 -11
- package/dist/core/addon/addon.tokens.js +2 -5
- package/dist/core/addon/device-meta-mirror.js +8 -13
- package/dist/core/addon/forked-custom-actions.js +6 -11
- package/dist/core/addon/integration-visibility.js +3 -6
- package/dist/core/addon/materialize-route-path.js +1 -4
- package/dist/core/addon/package-dir-utils.js +8 -45
- package/dist/core/addon/prune-misplaced-addons.js +5 -43
- package/dist/core/addon/runner-convergence.js +1 -4
- package/dist/core/addon/runner-serialization.js +1 -4
- package/dist/core/addon/runner-spawn-fanout.js +3 -8
- package/dist/core/addon/settings-door-wiring.js +5 -8
- package/dist/core/addon/single-flight-refresh.js +1 -4
- package/dist/core/addon-bridge/addon-bridge.service.js +7 -44
- package/dist/core/addon-pages/addon-pages.service.js +3 -40
- package/dist/core/addon-widgets/addon-widgets.service.js +4 -42
- package/dist/core/agent/agent-addon-backfill.js +9 -17
- package/dist/core/agent/agent-delivery-ledger.js +3 -7
- package/dist/core/agent/agent-liveness-event.js +2 -6
- package/dist/core/agent/agent-pairing-http.js +41 -45
- package/dist/core/agent/agent-registry.service.js +53 -92
- package/dist/core/agent/cluster-node-history-mirror.js +3 -7
- package/dist/core/agent/cluster-node-history-store.js +113 -58
- package/dist/core/agent/hub-agent-pairing.js +1 -5
- package/dist/core/agent/node-certificates-client.js +3 -6
- package/dist/core/agent/node-directory.js +1 -4
- package/dist/core/app-links/well-known-app-links.js +2 -6
- package/dist/core/auth/auth.service.js +3 -8
- package/dist/core/auth/handoff-code.service.js +11 -49
- package/dist/core/auth/record-kinds.js +35 -0
- package/dist/core/auth/share-token.service.js +174 -122
- package/dist/core/capability/capability.service.js +1 -5
- package/dist/core/config/config.schema.js +1 -6
- package/dist/core/config/config.service.js +2 -6
- package/dist/core/events/event-bus.service.js +3 -7
- package/dist/core/feature/feature.service.js +2 -6
- package/dist/core/lifecycle/lifecycle-runner.singleton.js +5 -10
- package/dist/core/lifecycle/lifecycle-state-machine.js +1 -5
- package/dist/core/logging/log-ring-buffer.js +1 -5
- package/dist/core/logging/logging.service.js +5 -9
- package/dist/core/logging/scoped-logger.js +1 -5
- package/dist/core/moleculer/agent-readiness-pull.js +1 -4
- package/dist/core/moleculer/agent-registration-request.js +3 -7
- package/dist/core/moleculer/cap-call-fn.js +39 -5
- package/dist/core/moleculer/cap-route-authority.js +3 -8
- package/dist/core/moleculer/hub-mesh-guard.js +5 -9
- package/dist/core/moleculer/moleculer.service.js +108 -119
- package/dist/core/network/network-quality.service.js +2 -6
- package/dist/core/notification/notification-wrapper.service.js +3 -7
- package/dist/core/notification/toast-wrapper.service.js +3 -7
- package/dist/core/provider/provider.tokens.js +1 -4
- package/dist/core/repl/repl-engine.service.js +4 -8
- package/dist/core/runner-health/runner-boot-check.js +7 -15
- package/dist/core/runner-health/update-preflight.js +9 -14
- package/dist/core/server-update/server-update.service.js +12 -49
- package/dist/core/server-update/system-apply-closure-patches.js +6 -42
- package/dist/core/server-update/system-ensure-prebuilds.js +3 -6
- package/dist/core/server-update/system-exec-npm.js +2 -5
- package/dist/core/storage/fs-storage-backend.js +1 -5
- package/dist/core/storage/storage-location-manager.js +1 -5
- package/dist/core/storage/storage.service.js +2 -6
- package/dist/core/streaming/ffmpeg-stream-info.js +3 -8
- package/dist/core/streaming/stream-probe.service.js +12 -16
- package/dist/core/streaming/ttl-cache.js +1 -5
- package/dist/core/topology/topology-emitter.service.js +19 -23
- package/dist/core/update-availability-emitter.js +3 -7
- package/dist/core/update-availability-store.js +8 -45
- package/dist/core/updates/agent-installed-packages.js +5 -9
- package/dist/core/updates/restart-marker.js +3 -8
- package/dist/core/updates/update-check-scheduler.js +13 -18
- package/dist/core/updates/wrapper-update-checker.js +3 -7
- package/dist/first-boot-addon-plan.js +2 -6
- package/dist/framework-nodepath.js +2 -38
- package/dist/i18n/request-lang.js +7 -11
- package/dist/launcher.js +30 -65
- package/dist/lifecycle-journal-path.js +2 -38
- package/dist/main.js +287 -324
- package/dist/manual-boot.js +120 -157
- package/dist/node-role.js +1 -4
- package/dist/package-inventory.js +7 -48
- package/dist/pending-restore.js +7 -43
- package/dist/single-copy-cleanup-runner.js +9 -45
- package/dist/single-copy-cleanup.js +7 -15
- package/dist/sqlite-restore.js +9 -48
- package/dist/starter.js +4 -39
- package/dist/ui-serving/lazy-ui-serving.js +3 -39
- package/package.json +13 -13
|
@@ -1,65 +1,26 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access -- pre-existing lint debt across this 2200-line orchestration class. The flagged sites (StorageService.setLocationManager / setSettingsBackend, LoggingService.addDestination, RouteRegistry, etc.) are typed as `unknown` by their owning services to break circular construction-order dependencies; runtime contracts are validated structurally. Tracked separately; do not amend in unrelated edits. */
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
};
|
|
26
|
-
return ownKeys(o);
|
|
27
|
-
};
|
|
28
|
-
return function (mod) {
|
|
29
|
-
if (mod && mod.__esModule) return mod;
|
|
30
|
-
var result = {};
|
|
31
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
-
__setModuleDefault(result, mod);
|
|
33
|
-
return result;
|
|
34
|
-
};
|
|
35
|
-
})();
|
|
36
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
-
exports.AddonRegistryService = void 0;
|
|
38
|
-
exports.shouldEvictMissingOnDisk = shouldEvictMissingOnDisk;
|
|
39
|
-
exports.addonUpdatePayload = addonUpdatePayload;
|
|
40
|
-
exports.parseDataPlaneEndpoints = parseDataPlaneEndpoints;
|
|
41
|
-
exports.shouldEmitProviderRegisteredReady = shouldEmitProviderRegisteredReady;
|
|
42
|
-
const addon_process_view_js_1 = require("./addon-process-view.js");
|
|
43
|
-
const node_crypto_1 = require("node:crypto");
|
|
44
|
-
const fs = __importStar(require("node:fs"));
|
|
45
|
-
const os = __importStar(require("node:os"));
|
|
46
|
-
const path = __importStar(require("node:path"));
|
|
47
|
-
const system_1 = require("@camstack/system");
|
|
48
|
-
const types_1 = require("@camstack/types");
|
|
49
|
-
const client_1 = require("@trpc/client");
|
|
50
|
-
const addon_call_gateway_js_1 = require("./addon-call-gateway.js");
|
|
51
|
-
const addon_row_manifest_1 = require("./addon-row-manifest");
|
|
52
|
-
const addon_settings_provider_js_1 = require("./addon-settings-provider.js");
|
|
53
|
-
const device_meta_mirror_js_1 = require("./device-meta-mirror.js");
|
|
54
|
-
const forked_custom_actions_js_1 = require("./forked-custom-actions.js");
|
|
55
|
-
const integration_visibility_js_1 = require("./integration-visibility.js");
|
|
56
|
-
const materialize_route_path_js_1 = require("./materialize-route-path.js");
|
|
57
|
-
const package_dir_utils_1 = require("./package-dir-utils");
|
|
58
|
-
const prune_misplaced_addons_js_1 = require("./prune-misplaced-addons.js");
|
|
59
|
-
const runner_convergence_1 = require("./runner-convergence");
|
|
60
|
-
const runner_serialization_1 = require("./runner-serialization");
|
|
61
|
-
const runner_spawn_fanout_js_1 = require("./runner-spawn-fanout.js");
|
|
62
|
-
const settings_door_wiring_js_1 = require("./settings-door-wiring.js");
|
|
2
|
+
import { describeAddonProcess, NEVER_LOADED_PROCESS, } from './addon-process-view.js';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import * as fs from 'node:fs';
|
|
5
|
+
import * as os from 'node:os';
|
|
6
|
+
import * as path from 'node:path';
|
|
7
|
+
import { AddonDepsManager, AddonHealthMonitor, AddonLoader, adaptBrokerToCluster, addonSettingsCapability, brokerTransportLink, CapabilityHandle, CapabilityRegistry, CustomActionRegistry, canServeDataPlane, createAddonDataPlaneFacility, createAddonPeerBytesFacility, createAddonService, createBrokerDeviceManagerApi, createKernelHwAccel, DeviceRegistry, describeProviderKindDrift, catalogNamespaceOf, deviceCapResolverLink, getCatalogRegistry, INFRA_CAPABILITIES, IntegrationRegistry, clusterAgentRecordKinds, clusterNodeHistoryRecordKind, integrationRegistryRecordKinds, localProviderLink, partitionIsolatedBuiltinIds, runHubAddonBoot, scopeKey, validateProviderRegistrations, waitUntilReady, asBrokerLike, } from '@camstack/system';
|
|
8
|
+
import { ALL_CAPABILITY_DEFINITIONS, createDeviceProxy, createRecordsAccess, createSecretsAccess, DisposerChain, EventCategory, errMsg, isAgentOnlyPlacement, isIsolatedBuiltin, normalizeAddonInitResult, resolveAddonPlacement, resolveRunnerId, SHARE_VIEW_KINDS, } from '@camstack/types';
|
|
9
|
+
import { createTRPCClient } from '@trpc/client';
|
|
10
|
+
import { AddonCallGateway } from './addon-call-gateway.js';
|
|
11
|
+
import { overlayDeclaration } from './addon-row-manifest';
|
|
12
|
+
import { createAddonSettingsProvider } from './addon-settings-provider.js';
|
|
13
|
+
import { DeviceMetaMirror } from './device-meta-mirror.js';
|
|
14
|
+
import { childCatalogsFingerprint, summarizeChildCatalogs, toForkedCustomActionsSpec, } from './forked-custom-actions.js';
|
|
15
|
+
import { createFilteredIntegrationRegistry } from './integration-visibility.js';
|
|
16
|
+
import { coreAuthRecordKinds } from '../auth/record-kinds.js';
|
|
17
|
+
import { materializeRoutePath } from './materialize-route-path.js';
|
|
18
|
+
import { resolveHubClosurePackageDir } from './package-dir-utils';
|
|
19
|
+
import { decidePrune, prunePlacementLeftovers } from './prune-misplaced-addons.js';
|
|
20
|
+
import { planRunnerConvergence } from './runner-convergence';
|
|
21
|
+
import { createRunnerSerializer } from './runner-serialization';
|
|
22
|
+
import { spawnPlanConcurrently } from './runner-spawn-fanout.js';
|
|
23
|
+
import { wireSettingsDoor } from './settings-door-wiring.js';
|
|
63
24
|
/**
|
|
64
25
|
* Decide whether an in-memory addon entry should be evicted because it is no
|
|
65
26
|
* longer on disk (the uninstall path in `loadNewAddons`).
|
|
@@ -71,7 +32,7 @@ const settings_door_wiring_js_1 = require("./settings-door-wiring.js");
|
|
|
71
32
|
* let a deploy of an unrelated addon silently unregister loki-logging on the
|
|
72
33
|
* live hub.
|
|
73
34
|
*/
|
|
74
|
-
function shouldEvictMissingOnDisk(entry, id, onDiskIds) {
|
|
35
|
+
export function shouldEvictMissingOnDisk(entry, id, onDiskIds) {
|
|
75
36
|
return (entry.source === 'installed' && entry.packageName !== '@camstack/system' && !onDiskIds.has(id));
|
|
76
37
|
}
|
|
77
38
|
/**
|
|
@@ -90,12 +51,18 @@ function shouldEvictMissingOnDisk(entry, id, onDiskIds) {
|
|
|
90
51
|
* settings save, which emits the same category carrying no versions at all
|
|
91
52
|
* (`parseAddonUpdateComplete`).
|
|
92
53
|
*/
|
|
93
|
-
function addonUpdatePayload(fromVersion, toVersion) {
|
|
54
|
+
export function addonUpdatePayload(fromVersion, toVersion) {
|
|
94
55
|
return {
|
|
95
56
|
...(fromVersion !== null ? { fromVersion } : {}),
|
|
96
57
|
toVersion,
|
|
97
58
|
};
|
|
98
59
|
}
|
|
60
|
+
function isAddonDeclaration(value) {
|
|
61
|
+
if (typeof value !== 'object' || value === null)
|
|
62
|
+
return false;
|
|
63
|
+
const id = Reflect.get(value, 'id');
|
|
64
|
+
return typeof id === 'string' && id.length > 0;
|
|
65
|
+
}
|
|
99
66
|
/**
|
|
100
67
|
* Type predicate: true when an `ISettingsBackend` also satisfies the
|
|
101
68
|
* sync `ISettingsStore` contract (system/addon/device key-value ops)
|
|
@@ -110,10 +77,6 @@ function isSettingsStore(backend) {
|
|
|
110
77
|
'getAllSystem',
|
|
111
78
|
'getAllAddon',
|
|
112
79
|
'setAllAddon',
|
|
113
|
-
'getAllProvider',
|
|
114
|
-
'setProvider',
|
|
115
|
-
'getAllDevice',
|
|
116
|
-
'setDevice',
|
|
117
80
|
'getDeviceRuntimeState',
|
|
118
81
|
'setDeviceRuntimeState',
|
|
119
82
|
'clearDeviceRuntimeState',
|
|
@@ -195,7 +158,7 @@ function parseSerializableRouteDescriptors(raw) {
|
|
|
195
158
|
* into typed data-plane endpoint descriptors (`prefix/access/baseUrl/secret`).
|
|
196
159
|
* Pure data — the addon's `ctx.dataPlane` facility produced them.
|
|
197
160
|
*/
|
|
198
|
-
function parseDataPlaneEndpoints(raw) {
|
|
161
|
+
export function parseDataPlaneEndpoints(raw) {
|
|
199
162
|
if (!Array.isArray(raw)) {
|
|
200
163
|
throw new Error('data-planes: child returned a non-array endpoint set');
|
|
201
164
|
}
|
|
@@ -236,7 +199,7 @@ function parseDataPlaneShare(value) {
|
|
|
236
199
|
});
|
|
237
200
|
}
|
|
238
201
|
function isShareViewKind(value) {
|
|
239
|
-
return typeof value === 'string' &&
|
|
202
|
+
return typeof value === 'string' && SHARE_VIEW_KINDS.includes(value);
|
|
240
203
|
}
|
|
241
204
|
// Phase 11 (settings redesign): `stripValue` helper and the legacy
|
|
242
205
|
// `getAddonConfigSchema / getAddonConfig / updateAddonConfig` adapter
|
|
@@ -253,7 +216,7 @@ function isShareViewKind(value) {
|
|
|
253
216
|
* accelerator-blind results. The addon emits `starting`/`ready`/`down`
|
|
254
217
|
* itself (`autoEmitReadiness=false`, ready on probe-done).
|
|
255
218
|
*/
|
|
256
|
-
function shouldEmitProviderRegisteredReady(capability) {
|
|
219
|
+
export function shouldEmitProviderRegisteredReady(capability) {
|
|
257
220
|
return capability !== 'platform-probe';
|
|
258
221
|
}
|
|
259
222
|
/**
|
|
@@ -264,11 +227,11 @@ function shouldEmitProviderRegisteredReady(capability) {
|
|
|
264
227
|
* invisible for a week. Built once — `ALL_CAPABILITY_DEFINITIONS` is a frozen
|
|
265
228
|
* generated array.
|
|
266
229
|
*/
|
|
267
|
-
const DEVICE_SCOPED_CAP_NAMES = new Set(
|
|
230
|
+
const DEVICE_SCOPED_CAP_NAMES = new Set(ALL_CAPABILITY_DEFINITIONS.filter((def) => def.scope === 'device').map((def) => def.name));
|
|
268
231
|
function isDeviceScopedCapName(capName) {
|
|
269
232
|
return DEVICE_SCOPED_CAP_NAMES.has(capName);
|
|
270
233
|
}
|
|
271
|
-
class AddonRegistryService {
|
|
234
|
+
export class AddonRegistryService {
|
|
272
235
|
loggingService;
|
|
273
236
|
eventBusService;
|
|
274
237
|
configService;
|
|
@@ -283,7 +246,7 @@ class AddonRegistryService {
|
|
|
283
246
|
// Task 7.1: hub-wide registry of addon custom actions. Populated on
|
|
284
247
|
// each addon's initialize() (in-process path); Task 7.2 will dispatch
|
|
285
248
|
// through this from the `api.addons.custom` tRPC procedure.
|
|
286
|
-
customActionRegistry = new
|
|
249
|
+
customActionRegistry = new CustomActionRegistry();
|
|
287
250
|
/**
|
|
288
251
|
* Fingerprint of the custom-action catalogs each UDS child last DESCRIBED
|
|
289
252
|
* (keyed by child id), and the addon ids registered from that description.
|
|
@@ -337,9 +300,9 @@ class AddonRegistryService {
|
|
|
337
300
|
const resolver = {
|
|
338
301
|
getByName: (capName) => reg.getSingleton(capName) ?? reg.getAllProviders(capName)[0] ?? null,
|
|
339
302
|
};
|
|
340
|
-
const client =
|
|
303
|
+
const client = createTRPCClient({
|
|
341
304
|
links: [
|
|
342
|
-
|
|
305
|
+
localProviderLink(resolver),
|
|
343
306
|
// Device-scoped caps go through the hub's CapRouteResolver, which is
|
|
344
307
|
// the only authority that can route `(cap, deviceId)` into a FORKED
|
|
345
308
|
// child — and forked children are broker-less, so the Moleculer
|
|
@@ -348,8 +311,8 @@ class AddonRegistryService {
|
|
|
348
311
|
// `Service 'ptz.ptz.getOptions' is not found`, which is how the
|
|
349
312
|
// autotrack builtin came up switched on and unable to move a camera
|
|
350
313
|
// (2026-09-18).
|
|
351
|
-
|
|
352
|
-
|
|
314
|
+
deviceCapResolverLink((capName, method, input) => this.moleculer.callCapabilityOnNode(this.moleculer.broker.nodeID, capName, method, input), isDeviceScopedCapName),
|
|
315
|
+
brokerTransportLink(this.moleculer.broker),
|
|
353
316
|
],
|
|
354
317
|
});
|
|
355
318
|
this.brokerApi = client;
|
|
@@ -364,9 +327,12 @@ class AddonRegistryService {
|
|
|
364
327
|
activeStorageProvider = null;
|
|
365
328
|
activeSettingsBackend = null;
|
|
366
329
|
integrationRegistry = null;
|
|
330
|
+
clusterNodeHistoryStore = null;
|
|
331
|
+
clusterAgentRecords = null;
|
|
332
|
+
shareTokenService = null;
|
|
367
333
|
// Device architecture — in-memory registry of live IDevice instances.
|
|
368
334
|
// Persistence is owned by the `device-manager` capability addon.
|
|
369
|
-
deviceRegistry = new
|
|
335
|
+
deviceRegistry = new DeviceRegistry();
|
|
370
336
|
/**
|
|
371
337
|
* Typed accessor for the Moleculer broker. The Moleculer `index.d.ts`
|
|
372
338
|
* chains through `eventemitter2`, whose package.json has no `types`
|
|
@@ -379,7 +345,7 @@ class AddonRegistryService {
|
|
|
379
345
|
* `this.broker.call(...)` / `this.broker.nodeID` lint clean.
|
|
380
346
|
*/
|
|
381
347
|
get broker() {
|
|
382
|
-
return this.moleculer.broker;
|
|
348
|
+
return asBrokerLike(this.moleculer.broker);
|
|
383
349
|
}
|
|
384
350
|
constructor(loggingService, eventBusService, configService, storageService, capabilityService, moleculer, streamProbe) {
|
|
385
351
|
this.loggingService = loggingService;
|
|
@@ -390,8 +356,8 @@ class AddonRegistryService {
|
|
|
390
356
|
this.moleculer = moleculer;
|
|
391
357
|
this.streamProbe = streamProbe;
|
|
392
358
|
this.logger = this.loggingService.createLogger('AddonRegistry');
|
|
393
|
-
this.addonLoader = new
|
|
394
|
-
this.deviceMirror = new
|
|
359
|
+
this.addonLoader = new AddonLoader(this.loggingService.createLogger('AddonLoader'));
|
|
360
|
+
this.deviceMirror = new DeviceMetaMirror(async (deviceIds) => this.getBrokerApi().deviceManager.listAll.query({
|
|
395
361
|
projection: 'slim',
|
|
396
362
|
...(deviceIds === null ? {} : { deviceIds: [...deviceIds] }),
|
|
397
363
|
}), {
|
|
@@ -406,13 +372,13 @@ class AddonRegistryService {
|
|
|
406
372
|
// reattempt failed addons. Operator visibility comes through
|
|
407
373
|
// AddonLoadFailed / AddonLoadRecovered events consumed by
|
|
408
374
|
// AlertCenter (see addon-health-monitor.ts spec).
|
|
409
|
-
this.healthMonitor = new
|
|
375
|
+
this.healthMonitor = new AddonHealthMonitor({
|
|
410
376
|
eventBus: this.eventBusService,
|
|
411
377
|
logger: this.loggingService.createLogger('AddonHealthMonitor'),
|
|
412
378
|
retryFn: (packageName) => this.tryReloadPackage(packageName),
|
|
413
379
|
});
|
|
414
380
|
// Create capability registry with config reader for singleton preferences
|
|
415
|
-
this.capabilityRegistry = new
|
|
381
|
+
this.capabilityRegistry = new CapabilityRegistry(this.loggingService.createLogger('CapabilityRegistry'), this.eventBusService);
|
|
416
382
|
// The preference readers answer "no preference" while the persisted
|
|
417
383
|
// preferences cannot be read yet — before the settings door exists, or
|
|
418
384
|
// while its one boot read is in flight (D675: `get` then THROWS rather
|
|
@@ -425,7 +391,7 @@ class AddonRegistryService {
|
|
|
425
391
|
}
|
|
426
392
|
catch (err) {
|
|
427
393
|
this.logger.debug('capability preference not readable yet — default applies until replay', {
|
|
428
|
-
meta: { capability, error:
|
|
394
|
+
meta: { capability, error: errMsg(err) },
|
|
429
395
|
});
|
|
430
396
|
return undefined;
|
|
431
397
|
}
|
|
@@ -438,7 +404,7 @@ class AddonRegistryService {
|
|
|
438
404
|
catch (err) {
|
|
439
405
|
this.logger.debug('capability preference not readable yet — default applies', {
|
|
440
406
|
tags: { nodeId },
|
|
441
|
-
meta: { capability, error:
|
|
407
|
+
meta: { capability, error: errMsg(err) },
|
|
442
408
|
});
|
|
443
409
|
return undefined;
|
|
444
410
|
}
|
|
@@ -464,7 +430,7 @@ class AddonRegistryService {
|
|
|
464
430
|
}
|
|
465
431
|
catch (err) {
|
|
466
432
|
this.logger.debug('collection preference not readable yet, or malformed', {
|
|
467
|
-
meta: { capability, error:
|
|
433
|
+
meta: { capability, error: errMsg(err) },
|
|
468
434
|
});
|
|
469
435
|
return undefined;
|
|
470
436
|
}
|
|
@@ -497,7 +463,7 @@ class AddonRegistryService {
|
|
|
497
463
|
// Register the addon-settings singleton provider — replaces the
|
|
498
464
|
// former `$addonHost` Moleculer service. The provider resolves
|
|
499
465
|
// addonId → local addon instance (hub) or remote Moleculer call.
|
|
500
|
-
this.capabilityRegistry.declareCapability(
|
|
466
|
+
this.capabilityRegistry.declareCapability(addonSettingsCapability);
|
|
501
467
|
// The SINGLE router for addon-level calls (routes / custom / settings).
|
|
502
468
|
// It classifies in-process | hub-local-child (UDS) | remote-agent
|
|
503
469
|
// (Moleculer) in ONE place — `resolveNode` reports only the BASE node
|
|
@@ -507,7 +473,7 @@ class AddonRegistryService {
|
|
|
507
473
|
// down a dead `<addonId>.settings.*` Moleculer path because the old
|
|
508
474
|
// `resolveNode` marked them non-local; now they route over UDS like
|
|
509
475
|
// their routes + custom-actions already do.
|
|
510
|
-
this.addonCallGateway = new
|
|
476
|
+
this.addonCallGateway = new AddonCallGateway({
|
|
511
477
|
hubNodeId: this.broker.nodeID,
|
|
512
478
|
resolveNode: (addonId) => {
|
|
513
479
|
const entry = this.addonEntries.get(addonId);
|
|
@@ -524,7 +490,7 @@ class AddonRegistryService {
|
|
|
524
490
|
// active-singleton preference can select a grouped addon (whose child id
|
|
525
491
|
// is its `execution.group`, not its own id). Identity for ungrouped addons.
|
|
526
492
|
this.moleculer.setRunnerIdResolver((addonId) => this.childIdForAddon(addonId));
|
|
527
|
-
const settingsProvider =
|
|
493
|
+
const settingsProvider = createAddonSettingsProvider({
|
|
528
494
|
getAddon: (addonId) => {
|
|
529
495
|
const entry = this.addonEntries.get(addonId);
|
|
530
496
|
return entry?.addon ?? null;
|
|
@@ -566,7 +532,7 @@ class AddonRegistryService {
|
|
|
566
532
|
// `syncSystemAddonsCopy` race. Non-framework addons still load from the
|
|
567
533
|
// addons dir unchanged. The addons-dir `@camstack/system` copy (if present,
|
|
568
534
|
// seeded by the image entrypoint) is SKIPPED to avoid a double-load.
|
|
569
|
-
const closureSystemDir =
|
|
535
|
+
const closureSystemDir = resolveHubClosurePackageDir('@camstack/system');
|
|
570
536
|
if (closureSystemDir) {
|
|
571
537
|
try {
|
|
572
538
|
await this.addonLoader.loadFromAddonDir(closureSystemDir);
|
|
@@ -595,10 +561,10 @@ class AddonRegistryService {
|
|
|
595
561
|
// FAILED forever on a node where it cannot run. Fixing the installer was
|
|
596
562
|
// necessary and not sufficient — nothing removed what was already there,
|
|
597
563
|
// and a permanent red entry teaches people to ignore red entries.
|
|
598
|
-
|
|
564
|
+
prunePlacementLeftovers(decidePrune(loadedAddons.map((a) => ({
|
|
599
565
|
packageName: a.packageName,
|
|
600
566
|
dir: path.join(addonsDir, a.packageName),
|
|
601
|
-
placement:
|
|
567
|
+
placement: resolveAddonPlacement(a.declaration),
|
|
602
568
|
})), 'hub'), path.join(addonsDir, '@camstack'), {
|
|
603
569
|
info: (msg, meta) => this.logger.info(msg, { meta }),
|
|
604
570
|
warn: (msg, meta) => this.logger.warn(msg, { meta }),
|
|
@@ -606,7 +572,7 @@ class AddonRegistryService {
|
|
|
606
572
|
for (const registered of loadedAddons) {
|
|
607
573
|
// Skip agent-only addons — they never run on the hub, only on remote
|
|
608
574
|
// agents that opt in via `execution.placement: 'agent-only'`.
|
|
609
|
-
if (
|
|
575
|
+
if (isAgentOnlyPlacement(registered.declaration)) {
|
|
610
576
|
this.logger.info('Skipping agent-only addon on hub', {
|
|
611
577
|
tags: { addonId: registered.declaration.id },
|
|
612
578
|
meta: { packageName: registered.packageName },
|
|
@@ -646,7 +612,7 @@ class AddonRegistryService {
|
|
|
646
612
|
this.loadAddonCatalogs(registered.packageName, this.addonEntries.get(registered.declaration.id)?.addonDir);
|
|
647
613
|
}
|
|
648
614
|
catch (err) {
|
|
649
|
-
const msg =
|
|
615
|
+
const msg = errMsg(err);
|
|
650
616
|
this.logger.error('Failed to create instance of addon', {
|
|
651
617
|
tags: { addonId: registered.declaration.id },
|
|
652
618
|
meta: { error: msg },
|
|
@@ -692,7 +658,7 @@ class AddonRegistryService {
|
|
|
692
658
|
// genuinely has no cross-process provider and the fallback returns null — wrappers
|
|
693
659
|
// cleanly fall through to their own secondary strategy.
|
|
694
660
|
{
|
|
695
|
-
const { buildNativeCapProxy } = await
|
|
661
|
+
const { buildNativeCapProxy } = await import('@camstack/system');
|
|
696
662
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- moleculer types unresolvable; see `broker` getter docstring
|
|
697
663
|
const broker = this.moleculer.broker;
|
|
698
664
|
this.capabilityRegistry.setNativeFallback((capName, deviceId) => {
|
|
@@ -790,7 +756,7 @@ class AddonRegistryService {
|
|
|
790
756
|
// that IS load-bearing is BETWEEN the waves in `runHubAddonBoot`, not
|
|
791
757
|
// inside one.
|
|
792
758
|
const spawnRunnerPlan = async (plan) => {
|
|
793
|
-
await
|
|
759
|
+
await spawnPlanConcurrently(plan, async (runnerId, runnerAddons) => {
|
|
794
760
|
try {
|
|
795
761
|
await this.initializeAddonGroup(runnerId, runnerAddons);
|
|
796
762
|
for (const { addonId } of runnerAddons) {
|
|
@@ -798,7 +764,7 @@ class AddonRegistryService {
|
|
|
798
764
|
}
|
|
799
765
|
}
|
|
800
766
|
catch (error) {
|
|
801
|
-
const msg =
|
|
767
|
+
const msg = errMsg(error);
|
|
802
768
|
for (const { addonId } of runnerAddons) {
|
|
803
769
|
this.emitAddonLifecycleEvent('addon.error', addonId, {
|
|
804
770
|
error: msg,
|
|
@@ -820,10 +786,10 @@ class AddonRegistryService {
|
|
|
820
786
|
const entry = this.addonEntries.get(id);
|
|
821
787
|
return (entry?.packageName === '@camstack/system' &&
|
|
822
788
|
entry.declaration !== undefined &&
|
|
823
|
-
|
|
789
|
+
isIsolatedBuiltin(entry.declaration));
|
|
824
790
|
});
|
|
825
791
|
const isolatedBuiltins = new Set(isolatedBuiltinIds);
|
|
826
|
-
const { infra: isolatedInfraIds, consumers: isolatedConsumerIds } =
|
|
792
|
+
const { infra: isolatedInfraIds, consumers: isolatedConsumerIds } = partitionIsolatedBuiltinIds(isolatedBuiltinIds, (id) => {
|
|
827
793
|
const entry = this.addonEntries.get(id);
|
|
828
794
|
return entry?.declaredCapabilities ?? [];
|
|
829
795
|
});
|
|
@@ -843,7 +809,7 @@ class AddonRegistryService {
|
|
|
843
809
|
// still mint the named hostname (Cloudflare 530, 2026-08-25).
|
|
844
810
|
// `runHubAddonBoot` is the only sequence; a timeout on the engine wait
|
|
845
811
|
// must NOT throw (1.2.151 crash-looped the starter).
|
|
846
|
-
await
|
|
812
|
+
await runHubAddonBoot({
|
|
847
813
|
spawnIsolatedInfra: async () => {
|
|
848
814
|
if (isolatedInfraIds.length === 0)
|
|
849
815
|
return;
|
|
@@ -856,7 +822,7 @@ class AddonRegistryService {
|
|
|
856
822
|
if (isolatedInfraIds.length === 0)
|
|
857
823
|
return;
|
|
858
824
|
try {
|
|
859
|
-
await
|
|
825
|
+
await waitUntilReady(() => this.capabilityRegistry.getCollection('data-store-provider').length > 0, {
|
|
860
826
|
timeoutMs: 30_000,
|
|
861
827
|
intervalMs: 50,
|
|
862
828
|
what: 'isolated data-store-provider engine',
|
|
@@ -864,14 +830,14 @@ class AddonRegistryService {
|
|
|
864
830
|
}
|
|
865
831
|
catch (err) {
|
|
866
832
|
this.logger.error('Isolated data-store-provider engine did not register in time', {
|
|
867
|
-
meta: { error:
|
|
833
|
+
meta: { error: errMsg(err), addonIds: isolatedInfraIds },
|
|
868
834
|
});
|
|
869
835
|
}
|
|
870
836
|
},
|
|
871
837
|
bootInProcessInfra: async () => {
|
|
872
838
|
// Pass 1 — infrastructure builtins. A failed REQUIRED infra cap
|
|
873
839
|
// aborts boot: nothing downstream can run without storage/settings.
|
|
874
|
-
for (const infra of
|
|
840
|
+
for (const infra of INFRA_CAPABILITIES) {
|
|
875
841
|
const addonId = this.findAddonForCapability(infra.name, allIds);
|
|
876
842
|
if (addonId) {
|
|
877
843
|
const entry = this.addonEntries.get(addonId);
|
|
@@ -885,7 +851,7 @@ class AddonRegistryService {
|
|
|
885
851
|
this.wireCapabilities(addonId);
|
|
886
852
|
}
|
|
887
853
|
catch (error) {
|
|
888
|
-
const msg =
|
|
854
|
+
const msg = errMsg(error);
|
|
889
855
|
this.emitAddonLifecycleEvent('addon.error', addonId, {
|
|
890
856
|
error: msg,
|
|
891
857
|
phase: 'init',
|
|
@@ -912,7 +878,7 @@ class AddonRegistryService {
|
|
|
912
878
|
bootInProcessConsumers: async () => {
|
|
913
879
|
// Pass 2 — remaining core builtins, in capability-dependency order.
|
|
914
880
|
const bootOrder = this.capabilityRegistry.getBootOrder();
|
|
915
|
-
const infraNames = new Set(
|
|
881
|
+
const infraNames = new Set(INFRA_CAPABILITIES.map((c) => c.name));
|
|
916
882
|
for (const capName of bootOrder) {
|
|
917
883
|
if (infraNames.has(capName))
|
|
918
884
|
continue; // Handled in pass 1
|
|
@@ -928,7 +894,7 @@ class AddonRegistryService {
|
|
|
928
894
|
this.wireCapabilities(id);
|
|
929
895
|
}
|
|
930
896
|
catch (error) {
|
|
931
|
-
const msg =
|
|
897
|
+
const msg = errMsg(error);
|
|
932
898
|
this.emitAddonLifecycleEvent('addon.error', id, {
|
|
933
899
|
error: msg,
|
|
934
900
|
phase: 'init',
|
|
@@ -949,7 +915,7 @@ class AddonRegistryService {
|
|
|
949
915
|
this.wireCapabilities(id);
|
|
950
916
|
}
|
|
951
917
|
catch (error) {
|
|
952
|
-
const msg =
|
|
918
|
+
const msg = errMsg(error);
|
|
953
919
|
this.emitAddonLifecycleEvent('addon.error', id, {
|
|
954
920
|
error: msg,
|
|
955
921
|
phase: 'init',
|
|
@@ -1007,7 +973,7 @@ class AddonRegistryService {
|
|
|
1007
973
|
for (const [id, entry] of this.addonEntries.entries()) {
|
|
1008
974
|
if (!entry.declaration)
|
|
1009
975
|
continue;
|
|
1010
|
-
const runnerId =
|
|
976
|
+
const runnerId = resolveRunnerId(entry.declaration, id);
|
|
1011
977
|
if (`${this.broker.nodeID}/${runnerId}` !== nodeId)
|
|
1012
978
|
continue;
|
|
1013
979
|
// Skip operator-initiated restarts — `restartAddon` already
|
|
@@ -1029,7 +995,7 @@ class AddonRegistryService {
|
|
|
1029
995
|
for (const [id, entry] of this.addonEntries.entries()) {
|
|
1030
996
|
if (!entry.declaration)
|
|
1031
997
|
continue;
|
|
1032
|
-
const runnerId =
|
|
998
|
+
const runnerId = resolveRunnerId(entry.declaration, id);
|
|
1033
999
|
if (`${this.broker.nodeID}/${runnerId}` !== nodeId)
|
|
1034
1000
|
continue;
|
|
1035
1001
|
this.healthMonitor.recordSuccess(entry.packageName, id);
|
|
@@ -1037,10 +1003,10 @@ class AddonRegistryService {
|
|
|
1037
1003
|
});
|
|
1038
1004
|
}
|
|
1039
1005
|
this.eventBusService.emit({
|
|
1040
|
-
id:
|
|
1006
|
+
id: randomUUID(),
|
|
1041
1007
|
timestamp: new Date(),
|
|
1042
1008
|
source: { type: 'core', id: 'addon-registry' },
|
|
1043
|
-
category:
|
|
1009
|
+
category: EventCategory.SystemAddonsReady,
|
|
1044
1010
|
data: { activeAddons: initializedIds },
|
|
1045
1011
|
});
|
|
1046
1012
|
}
|
|
@@ -1085,7 +1051,7 @@ class AddonRegistryService {
|
|
|
1085
1051
|
this.healthMonitor.recordSuccess(packageName, id);
|
|
1086
1052
|
}
|
|
1087
1053
|
catch (err) {
|
|
1088
|
-
errors.push(`${id}: ${
|
|
1054
|
+
errors.push(`${id}: ${errMsg(err)}`);
|
|
1089
1055
|
this.healthMonitor.recordFailure(packageName, err, id);
|
|
1090
1056
|
}
|
|
1091
1057
|
}
|
|
@@ -1164,15 +1130,56 @@ class AddonRegistryService {
|
|
|
1164
1130
|
* values (D787). Never handed to an addon.
|
|
1165
1131
|
*/
|
|
1166
1132
|
secretsForOwner(owner) {
|
|
1167
|
-
return
|
|
1133
|
+
return createSecretsAccess({
|
|
1168
1134
|
owner,
|
|
1169
1135
|
transport: this.inProcessKeychainTransport(owner),
|
|
1170
1136
|
eventBus: this.eventBusService,
|
|
1171
1137
|
});
|
|
1172
1138
|
}
|
|
1139
|
+
/**
|
|
1140
|
+
* A hub in-process records handle acting for `owner` (D881): the provider is
|
|
1141
|
+
* reached straight, with the caller STATED - the one place a claim is trusted,
|
|
1142
|
+
* as for the keychain. Only a system builtin's `ctx.records.forOwner` ever
|
|
1143
|
+
* reaches a different owner.
|
|
1144
|
+
*/
|
|
1145
|
+
recordsForOwner(owner, secrets) {
|
|
1146
|
+
return createRecordsAccess({
|
|
1147
|
+
owner,
|
|
1148
|
+
transport: this.inProcessRecordsTransport(owner),
|
|
1149
|
+
transportForOwner: (other) => this.inProcessRecordsTransport(other),
|
|
1150
|
+
secrets,
|
|
1151
|
+
});
|
|
1152
|
+
}
|
|
1153
|
+
inProcessRecordsTransport(owner) {
|
|
1154
|
+
const provider = () => {
|
|
1155
|
+
const p = this.capabilityRegistry.getSingleton('records');
|
|
1156
|
+
if (p === null || p === undefined)
|
|
1157
|
+
throw new Error('records provider is not registered');
|
|
1158
|
+
return p;
|
|
1159
|
+
};
|
|
1160
|
+
const as = (input) => Object.assign({}, input, { callerAddonId: owner });
|
|
1161
|
+
return {
|
|
1162
|
+
declareKinds: (input) => provider().declareKinds(as(input)),
|
|
1163
|
+
get: (input) => provider().get(as(input)),
|
|
1164
|
+
getMany: (input) => provider().getMany(as(input)),
|
|
1165
|
+
secretRefs: (input) => provider().secretRefs(as(input)),
|
|
1166
|
+
put: (input) => provider().put(as(input)),
|
|
1167
|
+
putMany: (input) => provider().putMany(as(input)),
|
|
1168
|
+
patch: (input) => provider().patch(as(input)),
|
|
1169
|
+
delete: (input) => provider().delete(as(input)),
|
|
1170
|
+
deleteMany: (input) => provider().deleteMany(as(input)),
|
|
1171
|
+
query: (input) => provider().query(as(input)),
|
|
1172
|
+
count: (input) => provider().count(as(input)),
|
|
1173
|
+
histogram: (input) => provider().histogram(as(input)),
|
|
1174
|
+
aggregate: (input) => provider().aggregate(as(input)),
|
|
1175
|
+
deleteWhere: (input) => provider().deleteWhere(as(input)),
|
|
1176
|
+
deleteDevice: (input) => provider().deleteDevice(as(input)),
|
|
1177
|
+
allocateId: (input) => provider().allocateId(as(input)),
|
|
1178
|
+
};
|
|
1179
|
+
}
|
|
1173
1180
|
childIdForAddon(addonId) {
|
|
1174
1181
|
const decl = this.addonEntries.get(addonId)?.declaration;
|
|
1175
|
-
return decl ?
|
|
1182
|
+
return decl ? resolveRunnerId(decl, addonId) : addonId;
|
|
1176
1183
|
}
|
|
1177
1184
|
async mountAddonDataPlanes(addonId) {
|
|
1178
1185
|
const registry = this.dataPlaneRegistry;
|
|
@@ -1195,7 +1202,7 @@ class AddonRegistryService {
|
|
|
1195
1202
|
// Dropping is the safe direction: a data-plane that is absent from the
|
|
1196
1203
|
// registry falls through to the control-route path, while a stale one
|
|
1197
1204
|
// sends live traffic at a dead socket.
|
|
1198
|
-
if (!
|
|
1205
|
+
if (!canServeDataPlane({
|
|
1199
1206
|
hasEntry: entry !== undefined,
|
|
1200
1207
|
isForked: entry !== undefined && this.isForkedAddonEntry(entry),
|
|
1201
1208
|
childKnown: childRegistry !== null && childRegistry.isChildKnown(this.childIdForAddon(addonId)),
|
|
@@ -1280,16 +1287,16 @@ class AddonRegistryService {
|
|
|
1280
1287
|
*/
|
|
1281
1288
|
wireDeviceParentMirror() {
|
|
1282
1289
|
for (const category of [
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1290
|
+
EventCategory.DeviceMetaChanged,
|
|
1291
|
+
EventCategory.DeviceRegistered,
|
|
1292
|
+
EventCategory.DeviceUnregistered,
|
|
1293
|
+
EventCategory.DeviceProvisioned,
|
|
1287
1294
|
]) {
|
|
1288
1295
|
this.eventBusService.subscribe({ category }, (event) => {
|
|
1289
1296
|
this.deviceMirror.onDeviceEvent(event.data);
|
|
1290
1297
|
});
|
|
1291
1298
|
}
|
|
1292
|
-
this.eventBusService.subscribe({ category:
|
|
1299
|
+
this.eventBusService.subscribe({ category: EventCategory.SystemAddonsReady }, () => {
|
|
1293
1300
|
void this.deviceMirror.refreshAll();
|
|
1294
1301
|
});
|
|
1295
1302
|
}
|
|
@@ -1297,6 +1304,43 @@ class AddonRegistryService {
|
|
|
1297
1304
|
loadCollectionPreferences() {
|
|
1298
1305
|
// TODO: implement CapabilityRegistry.loadDisabledProviders() to restore persisted preferences
|
|
1299
1306
|
}
|
|
1307
|
+
/** Durable cluster-node history (A1) — bind records kind after orphan declare. */
|
|
1308
|
+
setClusterNodeHistoryStore(store) {
|
|
1309
|
+
this.clusterNodeHistoryStore = store;
|
|
1310
|
+
this.bindClusterNodeHistoryRecordsKind();
|
|
1311
|
+
}
|
|
1312
|
+
bindClusterNodeHistoryRecordsKind() {
|
|
1313
|
+
const clusterStore = this.clusterNodeHistoryStore;
|
|
1314
|
+
const records = this.clusterAgentRecords;
|
|
1315
|
+
if (clusterStore === null || records === null)
|
|
1316
|
+
return;
|
|
1317
|
+
const kind = records.kind(clusterNodeHistoryRecordKind);
|
|
1318
|
+
clusterStore.bindRecordsKind(() => kind);
|
|
1319
|
+
}
|
|
1320
|
+
setShareTokenService(service) {
|
|
1321
|
+
this.shareTokenService = service;
|
|
1322
|
+
}
|
|
1323
|
+
async wireHubMainOrphanRecords(store) {
|
|
1324
|
+
const recordsCap = this.capabilityRegistry.getSingleton('records');
|
|
1325
|
+
if (recordsCap === null || recordsCap === undefined)
|
|
1326
|
+
return;
|
|
1327
|
+
const integrationRecords = this.recordsForOwner('integration-registry', this.secretsForOwner('integration-registry'));
|
|
1328
|
+
await integrationRecords.declare(integrationRegistryRecordKinds);
|
|
1329
|
+
this.integrationRegistry = new IntegrationRegistry(store, IntegrationRegistry.recordStoresOf(integrationRecords));
|
|
1330
|
+
await this.integrationRegistry.initialize();
|
|
1331
|
+
this.logger.info('IntegrationRegistry initialized (records)', { meta: { phase: 'v2' } });
|
|
1332
|
+
const clusterRecords = this.recordsForOwner('cluster-agent', this.secretsForOwner('cluster-agent'));
|
|
1333
|
+
await clusterRecords.declare(clusterAgentRecordKinds);
|
|
1334
|
+
this.clusterAgentRecords = clusterRecords;
|
|
1335
|
+
this.bindClusterNodeHistoryRecordsKind();
|
|
1336
|
+
const shareTokens = this.shareTokenService;
|
|
1337
|
+
if (shareTokens !== null) {
|
|
1338
|
+
const coreRecords = this.recordsForOwner('core-auth', this.secretsForOwner('core-auth'));
|
|
1339
|
+
await coreRecords.declare(coreAuthRecordKinds);
|
|
1340
|
+
const shareKind = coreRecords.kind(coreAuthRecordKinds['share-view-tokens']);
|
|
1341
|
+
shareTokens.bindShareTokensKind(() => shareKind);
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1300
1344
|
/**
|
|
1301
1345
|
* Returns the IntegrationRegistry filtered to exclude orphaned integrations
|
|
1302
1346
|
* (where the addon is not currently installed). Orphaned data stays in DB
|
|
@@ -1328,7 +1372,7 @@ class AddonRegistryService {
|
|
|
1328
1372
|
* `integration-visibility.ts`, on its own, with its own tests.
|
|
1329
1373
|
*/
|
|
1330
1374
|
createFilteredRegistry(raw) {
|
|
1331
|
-
return
|
|
1375
|
+
return createFilteredIntegrationRegistry({
|
|
1332
1376
|
raw,
|
|
1333
1377
|
// The LIVE map, not a snapshot: an addon registered after this wrapper
|
|
1334
1378
|
// was built is installed, and a stale copy would refuse its first write.
|
|
@@ -1370,8 +1414,8 @@ class AddonRegistryService {
|
|
|
1370
1414
|
loadAddonCatalogs(packageName, addonDir) {
|
|
1371
1415
|
if (addonDir === undefined)
|
|
1372
1416
|
return;
|
|
1373
|
-
const ns =
|
|
1374
|
-
const catalogs =
|
|
1417
|
+
const ns = catalogNamespaceOf(packageName);
|
|
1418
|
+
const catalogs = getCatalogRegistry((message) => this.logger.warn(message, { meta: { ns } }));
|
|
1375
1419
|
catalogs.clear(ns);
|
|
1376
1420
|
catalogs.loadAddonDir(ns, addonDir);
|
|
1377
1421
|
}
|
|
@@ -1425,7 +1469,7 @@ class AddonRegistryService {
|
|
|
1425
1469
|
await this.ensureForkedRunner(id, { restart: false });
|
|
1426
1470
|
}
|
|
1427
1471
|
catch (err) {
|
|
1428
|
-
const msg =
|
|
1472
|
+
const msg = errMsg(err);
|
|
1429
1473
|
this.logger.error('Failed to spawn isolated runner for addon', {
|
|
1430
1474
|
tags: { addonId: id },
|
|
1431
1475
|
meta: { error: msg },
|
|
@@ -1449,7 +1493,7 @@ class AddonRegistryService {
|
|
|
1449
1493
|
// resolves native addon ids for getBindings, lists registered wrappers,
|
|
1450
1494
|
// etc. Inject before initialize() runs so the addon's onInitialize can
|
|
1451
1495
|
// rely on it.
|
|
1452
|
-
const initResult =
|
|
1496
|
+
const initResult = normalizeAddonInitResult(await entry.addon.initialize(context));
|
|
1453
1497
|
// NOTE: `postBrokerStart()` is deliberately NOT called here. It used to
|
|
1454
1498
|
// fire right after `initialize()` and would emit readiness events whose
|
|
1455
1499
|
// subscribers (remote workers) immediately called `ctx.api.<addon>.*` —
|
|
@@ -1463,7 +1507,7 @@ class AddonRegistryService {
|
|
|
1463
1507
|
// D4: wrapper behaviour is read from the cap DEFINITION. The runtime
|
|
1464
1508
|
// ProviderRegistration.kind/defaultActive are deprecated hints — if an
|
|
1465
1509
|
// addon still sets them and they disagree, warn (the cap def wins).
|
|
1466
|
-
const kindDrift =
|
|
1510
|
+
const kindDrift = describeProviderKindDrift(capName, reg.capability, {
|
|
1467
1511
|
// eslint-disable-next-line @typescript-eslint/no-deprecated -- this IS the drift detector; it reads the deprecated hint on purpose
|
|
1468
1512
|
kind: reg.kind,
|
|
1469
1513
|
// eslint-disable-next-line @typescript-eslint/no-deprecated -- this IS the drift detector; it reads the deprecated hint on purpose
|
|
@@ -1495,7 +1539,7 @@ class AddonRegistryService {
|
|
|
1495
1539
|
catch (err) {
|
|
1496
1540
|
this.logger.error('Failed to register custom actions for addon', {
|
|
1497
1541
|
tags: { addonId: id },
|
|
1498
|
-
meta: { error:
|
|
1542
|
+
meta: { error: errMsg(err) },
|
|
1499
1543
|
});
|
|
1500
1544
|
throw err;
|
|
1501
1545
|
}
|
|
@@ -1515,7 +1559,7 @@ class AddonRegistryService {
|
|
|
1515
1559
|
if (provider)
|
|
1516
1560
|
capturedProviders.set(capName, provider);
|
|
1517
1561
|
}
|
|
1518
|
-
|
|
1562
|
+
validateProviderRegistrations(id, entry.declaration, capturedProviders, this.logger);
|
|
1519
1563
|
}
|
|
1520
1564
|
await this.restoreAddonDevices(id, entry.addon);
|
|
1521
1565
|
entry.initialized = true;
|
|
@@ -1530,7 +1574,7 @@ class AddonRegistryService {
|
|
|
1530
1574
|
return Object.keys(def.methods);
|
|
1531
1575
|
};
|
|
1532
1576
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- createAddonService return type unresolvable due to upstream eventemitter2/moleculer type chain
|
|
1533
|
-
const schema =
|
|
1577
|
+
const schema = createAddonService(entry.addon, entry.declaration, {
|
|
1534
1578
|
methodResolver,
|
|
1535
1579
|
providers: capturedProviders,
|
|
1536
1580
|
});
|
|
@@ -1539,7 +1583,7 @@ class AddonRegistryService {
|
|
|
1539
1583
|
catch (err) {
|
|
1540
1584
|
this.logger.warn('Failed to register Moleculer service', {
|
|
1541
1585
|
tags: { addonId: id },
|
|
1542
|
-
meta: { error:
|
|
1586
|
+
meta: { error: errMsg(err) },
|
|
1543
1587
|
});
|
|
1544
1588
|
}
|
|
1545
1589
|
}
|
|
@@ -1598,7 +1642,7 @@ class AddonRegistryService {
|
|
|
1598
1642
|
catch (err) {
|
|
1599
1643
|
this.logger.error('restoreDevices failed for addon', {
|
|
1600
1644
|
tags: { addonId },
|
|
1601
|
-
meta: { error:
|
|
1645
|
+
meta: { error: errMsg(err) },
|
|
1602
1646
|
});
|
|
1603
1647
|
}
|
|
1604
1648
|
}
|
|
@@ -1698,7 +1742,7 @@ class AddonRegistryService {
|
|
|
1698
1742
|
errors.push(res.error ?? `restart of "${addonId}" reported failure`);
|
|
1699
1743
|
}
|
|
1700
1744
|
catch (err) {
|
|
1701
|
-
errors.push(`restart of "${addonId}" threw: ${
|
|
1745
|
+
errors.push(`restart of "${addonId}" threw: ${errMsg(err)}`);
|
|
1702
1746
|
}
|
|
1703
1747
|
}
|
|
1704
1748
|
return { success: errors.length === 0, restarted, errors };
|
|
@@ -1723,7 +1767,7 @@ class AddonRegistryService {
|
|
|
1723
1767
|
// suppressing the siblings too, each would flash a spurious "Failed to load".
|
|
1724
1768
|
const suppressed = new Set([addonId]);
|
|
1725
1769
|
if (this.isForkedAddonEntry(entry)) {
|
|
1726
|
-
const runnerId =
|
|
1770
|
+
const runnerId = resolveRunnerId(entry.declaration, addonId);
|
|
1727
1771
|
for (const { addonId: siblingId } of this.buildAddonGroupPlan([
|
|
1728
1772
|
...this.addonEntries.keys(),
|
|
1729
1773
|
]).get(runnerId) ?? []) {
|
|
@@ -1857,7 +1901,7 @@ class AddonRegistryService {
|
|
|
1857
1901
|
return { success: true };
|
|
1858
1902
|
}
|
|
1859
1903
|
catch (err) {
|
|
1860
|
-
const msg =
|
|
1904
|
+
const msg = errMsg(err);
|
|
1861
1905
|
this.logger.error('Failed to restart addon', { tags: { addonId }, meta: { error: msg } });
|
|
1862
1906
|
this.emitAddonLifecycleEvent('addon.error', addonId, {
|
|
1863
1907
|
error: msg,
|
|
@@ -2049,10 +2093,10 @@ class AddonRegistryService {
|
|
|
2049
2093
|
// The real process-service state of each addon's runner (D877) - the last
|
|
2050
2094
|
// `refreshRunnerStates()` answer; `unknown` before the first one.
|
|
2051
2095
|
const runnerStates = this.runnerStates;
|
|
2052
|
-
const processFor = (id, entry) =>
|
|
2096
|
+
const processFor = (id, entry) => describeAddonProcess({
|
|
2053
2097
|
forked: this.runsInOwnRunner(entry),
|
|
2054
2098
|
initialized: entry.initialized === true,
|
|
2055
|
-
runnerId: entry.declaration ?
|
|
2099
|
+
runnerId: entry.declaration ? resolveRunnerId(entry.declaration, id) : id,
|
|
2056
2100
|
runners: runnerStates,
|
|
2057
2101
|
hubPid: process.pid,
|
|
2058
2102
|
});
|
|
@@ -2074,7 +2118,7 @@ class AddonRegistryService {
|
|
|
2074
2118
|
}
|
|
2075
2119
|
catch (err) {
|
|
2076
2120
|
this.logger.debug('Failed to read install-source marker for addon', {
|
|
2077
|
-
meta: { error:
|
|
2121
|
+
meta: { error: errMsg(err) },
|
|
2078
2122
|
});
|
|
2079
2123
|
}
|
|
2080
2124
|
}
|
|
@@ -2087,7 +2131,7 @@ class AddonRegistryService {
|
|
|
2087
2131
|
// `overlayDeclaration` — this is what keeps Home Assistant
|
|
2088
2132
|
// (post broker rework: broker + device-adoption) visible in the
|
|
2089
2133
|
// "+ New Integration" picker.
|
|
2090
|
-
...
|
|
2134
|
+
...overlayDeclaration(entry.addon.manifest, entry.declaration),
|
|
2091
2135
|
packageName: entry.packageName,
|
|
2092
2136
|
packageVersion: entry.packageVersion,
|
|
2093
2137
|
packageDisplayName: entry.packageDisplayName,
|
|
@@ -2173,11 +2217,12 @@ class AddonRegistryService {
|
|
|
2173
2217
|
...(bundleRaw.icon !== undefined ? { icon: bundleRaw.icon } : {}),
|
|
2174
2218
|
}
|
|
2175
2219
|
: undefined;
|
|
2176
|
-
for (const
|
|
2177
|
-
|
|
2178
|
-
const name = typeof decl['name'] === 'string' ? decl['name'] : id;
|
|
2179
|
-
if (!id)
|
|
2220
|
+
for (const rawDecl of addons) {
|
|
2221
|
+
if (!isAddonDeclaration(rawDecl))
|
|
2180
2222
|
continue;
|
|
2223
|
+
const decl = rawDecl;
|
|
2224
|
+
const id = decl.id;
|
|
2225
|
+
const name = typeof decl.name === 'string' ? decl.name : id;
|
|
2181
2226
|
const manifest = {
|
|
2182
2227
|
...decl,
|
|
2183
2228
|
id,
|
|
@@ -2194,7 +2239,7 @@ class AddonRegistryService {
|
|
|
2194
2239
|
declaration: decl,
|
|
2195
2240
|
source: 'installed',
|
|
2196
2241
|
...(installSource ? { installSource } : {}),
|
|
2197
|
-
process:
|
|
2242
|
+
process: NEVER_LOADED_PROCESS,
|
|
2198
2243
|
});
|
|
2199
2244
|
}
|
|
2200
2245
|
};
|
|
@@ -2246,7 +2291,7 @@ class AddonRegistryService {
|
|
|
2246
2291
|
const dataDir = path.resolve(process.env.CAMSTACK_DATA ?? 'camstack-data');
|
|
2247
2292
|
const addonsDir = process.env.CAMSTACK_ADDONS_DIR ?? path.resolve(dataDir, 'addons');
|
|
2248
2293
|
// Create a fresh loader to discover what's on disk (for diffing against existing)
|
|
2249
|
-
const freshLoader = new
|
|
2294
|
+
const freshLoader = new AddonLoader(this.loggingService.createLogger('AddonLoader'));
|
|
2250
2295
|
await freshLoader.loadFromDirectory(addonsDir);
|
|
2251
2296
|
const loaded = [];
|
|
2252
2297
|
const failed = [];
|
|
@@ -2277,7 +2322,7 @@ class AddonRegistryService {
|
|
|
2277
2322
|
// hub-side every agent-only provider (e.g. `hub-forwarder`),
|
|
2278
2323
|
// and a hub-resident `hub-forwarder` creates a write→ingest→
|
|
2279
2324
|
// write feedback loop because the hub IS the log-receiver.
|
|
2280
|
-
if (
|
|
2325
|
+
if (isAgentOnlyPlacement(registered.declaration)) {
|
|
2281
2326
|
this.logger.debug('loadNewAddons: skipping agent-only addon on hub', {
|
|
2282
2327
|
tags: { addonId: registered.declaration.id },
|
|
2283
2328
|
});
|
|
@@ -2315,7 +2360,7 @@ class AddonRegistryService {
|
|
|
2315
2360
|
this.emitAddonLifecycleEvent('addon.installed', registered.declaration.id);
|
|
2316
2361
|
}
|
|
2317
2362
|
catch (err) {
|
|
2318
|
-
const msg =
|
|
2363
|
+
const msg = errMsg(err);
|
|
2319
2364
|
const stack = err instanceof Error ? err.stack : undefined;
|
|
2320
2365
|
this.logger.error('Failed to hot-load addon', {
|
|
2321
2366
|
tags: { addonId: registered.declaration.id },
|
|
@@ -2355,7 +2400,7 @@ class AddonRegistryService {
|
|
|
2355
2400
|
catch (err) {
|
|
2356
2401
|
this.logger.warn('Non-fatal: failed to stop runner for removed addon', {
|
|
2357
2402
|
tags: { addonId: id },
|
|
2358
|
-
meta: { error:
|
|
2403
|
+
meta: { error: errMsg(err) },
|
|
2359
2404
|
});
|
|
2360
2405
|
}
|
|
2361
2406
|
}
|
|
@@ -2369,7 +2414,7 @@ class AddonRegistryService {
|
|
|
2369
2414
|
catch (err) {
|
|
2370
2415
|
this.logger.debug('Non-fatal: shutdown error for removed addon', {
|
|
2371
2416
|
tags: { addonId: id },
|
|
2372
|
-
meta: { error:
|
|
2417
|
+
meta: { error: errMsg(err) },
|
|
2373
2418
|
});
|
|
2374
2419
|
}
|
|
2375
2420
|
await this.drainDisposerChain(id);
|
|
@@ -2446,7 +2491,7 @@ class AddonRegistryService {
|
|
|
2446
2491
|
}
|
|
2447
2492
|
catch (err) {
|
|
2448
2493
|
this.logger.warn('emitReady failed', {
|
|
2449
|
-
meta: { capability, addonId, error:
|
|
2494
|
+
meta: { capability, addonId, error: errMsg(err) },
|
|
2450
2495
|
});
|
|
2451
2496
|
}
|
|
2452
2497
|
}
|
|
@@ -2508,14 +2553,21 @@ class AddonRegistryService {
|
|
|
2508
2553
|
const store = this.capabilityRegistry.getProviderByAddon('settings-store', addonId);
|
|
2509
2554
|
if (!store)
|
|
2510
2555
|
return;
|
|
2511
|
-
void
|
|
2556
|
+
void wireSettingsDoor({
|
|
2512
2557
|
configService: this.configService,
|
|
2513
2558
|
registry: this.capabilityRegistry,
|
|
2514
2559
|
logger: this.logger,
|
|
2515
2560
|
}, store);
|
|
2516
|
-
this.
|
|
2517
|
-
|
|
2518
|
-
|
|
2561
|
+
void this.wireHubMainOrphanRecords(store).catch((err) => {
|
|
2562
|
+
this.logger.warn('hub-main records wiring failed (best-effort)', {
|
|
2563
|
+
meta: { error: errMsg(err) },
|
|
2564
|
+
});
|
|
2565
|
+
this.integrationRegistry = new IntegrationRegistry(store);
|
|
2566
|
+
void this.integrationRegistry.initialize().then(() => {
|
|
2567
|
+
this.logger.info('IntegrationRegistry initialized (legacy)', {
|
|
2568
|
+
meta: { phase: 'v2' },
|
|
2569
|
+
});
|
|
2570
|
+
});
|
|
2519
2571
|
});
|
|
2520
2572
|
this.loadCollectionPreferences();
|
|
2521
2573
|
// DeviceStore/ConfigStore are owned by the `device-persistence`
|
|
@@ -2552,7 +2604,7 @@ class AddonRegistryService {
|
|
|
2552
2604
|
// subscriber unobserved.
|
|
2553
2605
|
void this.mountAddonRoutes(addonId).catch((err) => {
|
|
2554
2606
|
this.logger.error('Failed to mount addon routes', {
|
|
2555
|
-
meta: { phase: 'v2', addonId, error:
|
|
2607
|
+
meta: { phase: 'v2', addonId, error: errMsg(err) },
|
|
2556
2608
|
});
|
|
2557
2609
|
});
|
|
2558
2610
|
break;
|
|
@@ -2566,7 +2618,7 @@ class AddonRegistryService {
|
|
|
2566
2618
|
// restart (the child re-handshakes → caps re-register → this re-fires).
|
|
2567
2619
|
void this.mountAddonDataPlanes(addonId).catch((err) => {
|
|
2568
2620
|
this.logger.error('Failed to mount addon data-planes', {
|
|
2569
|
-
meta: { phase: 'v2', addonId, error:
|
|
2621
|
+
meta: { phase: 'v2', addonId, error: errMsg(err) },
|
|
2570
2622
|
});
|
|
2571
2623
|
});
|
|
2572
2624
|
});
|
|
@@ -2677,7 +2729,7 @@ class AddonRegistryService {
|
|
|
2677
2729
|
method: route.method,
|
|
2678
2730
|
// The CONCRETE path, not the pattern: the child re-matches it and
|
|
2679
2731
|
// would otherwise bind every param to its own `:placeholder`.
|
|
2680
|
-
path:
|
|
2732
|
+
path: materializeRoutePath(route.path, req.params),
|
|
2681
2733
|
params: req.params,
|
|
2682
2734
|
query: req.query,
|
|
2683
2735
|
body: req.body,
|
|
@@ -2716,7 +2768,7 @@ class AddonRegistryService {
|
|
|
2716
2768
|
emitAddonLifecycleEvent(eventType, addonId, data) {
|
|
2717
2769
|
const entry = this.addonEntries.get(addonId);
|
|
2718
2770
|
this.eventBusService.emit({
|
|
2719
|
-
id:
|
|
2771
|
+
id: randomUUID(),
|
|
2720
2772
|
timestamp: new Date(),
|
|
2721
2773
|
source: {
|
|
2722
2774
|
type: 'addon',
|
|
@@ -2748,10 +2800,10 @@ class AddonRegistryService {
|
|
|
2748
2800
|
const declaredCaps = entry.declaredCapabilities.map((c) => c.name);
|
|
2749
2801
|
if (declaredCaps.includes('addon-pages-source')) {
|
|
2750
2802
|
this.eventBusService.emit({
|
|
2751
|
-
id:
|
|
2803
|
+
id: randomUUID(),
|
|
2752
2804
|
timestamp: new Date(),
|
|
2753
2805
|
source: { type: 'addon', id: addonId },
|
|
2754
|
-
category:
|
|
2806
|
+
category: EventCategory.AddonPageReady,
|
|
2755
2807
|
data: { addonId, packageName: entry.packageName },
|
|
2756
2808
|
});
|
|
2757
2809
|
}
|
|
@@ -2762,10 +2814,10 @@ class AddonRegistryService {
|
|
|
2762
2814
|
// reload.
|
|
2763
2815
|
if (declaredCaps.includes('addon-widgets-source')) {
|
|
2764
2816
|
this.eventBusService.emit({
|
|
2765
|
-
id:
|
|
2817
|
+
id: randomUUID(),
|
|
2766
2818
|
timestamp: new Date(),
|
|
2767
2819
|
source: { type: 'addon', id: addonId },
|
|
2768
|
-
category:
|
|
2820
|
+
category: EventCategory.AddonWidgetReady,
|
|
2769
2821
|
data: { addonId, packageName: entry.packageName },
|
|
2770
2822
|
});
|
|
2771
2823
|
}
|
|
@@ -2842,7 +2894,7 @@ class AddonRegistryService {
|
|
|
2842
2894
|
probe: (url, options) => this.streamProbe.probe(url, options),
|
|
2843
2895
|
probeField: (key, value) => this.streamProbe.probeField(key, value),
|
|
2844
2896
|
};
|
|
2845
|
-
const deviceManagerApi =
|
|
2897
|
+
const deviceManagerApi = createBrokerDeviceManagerApi({
|
|
2846
2898
|
api: this.getBrokerApi(),
|
|
2847
2899
|
addonId,
|
|
2848
2900
|
nodeId: this.broker.nodeID,
|
|
@@ -2860,11 +2912,11 @@ class AddonRegistryService {
|
|
|
2860
2912
|
const rr = this.moleculer.readinessRegistry;
|
|
2861
2913
|
const capHandleCache = new Map();
|
|
2862
2914
|
function getOrCreateHandle(capName, scope, timeoutMs) {
|
|
2863
|
-
const key = `${capName}::${
|
|
2915
|
+
const key = `${capName}::${scopeKey(scope)}`;
|
|
2864
2916
|
const existing = capHandleCache.get(key);
|
|
2865
2917
|
if (existing)
|
|
2866
2918
|
return existing;
|
|
2867
|
-
const handle = new
|
|
2919
|
+
const handle = new CapabilityHandle(capName, scope, rr, timeoutMs);
|
|
2868
2920
|
capHandleCache.set(key, handle);
|
|
2869
2921
|
return handle;
|
|
2870
2922
|
}
|
|
@@ -2881,7 +2933,7 @@ class AddonRegistryService {
|
|
|
2881
2933
|
registry.dataPlaneRegistry?.registerAddon(id, endpoints);
|
|
2882
2934
|
},
|
|
2883
2935
|
};
|
|
2884
|
-
const dataPlaneFacility =
|
|
2936
|
+
const dataPlaneFacility = createAddonDataPlaneFacility({
|
|
2885
2937
|
addonId,
|
|
2886
2938
|
logger,
|
|
2887
2939
|
sink: dataPlaneSink,
|
|
@@ -2890,12 +2942,22 @@ class AddonRegistryService {
|
|
|
2890
2942
|
// Addon→addon byte transport (`ctx.peerBytes`, D613) for a co-located hub
|
|
2891
2943
|
// builtin, on the same terms a forked addon gets it. `broker.nodeID` IS the
|
|
2892
2944
|
// host node here — hub-main has no runner suffix.
|
|
2893
|
-
const peerBytesFacility =
|
|
2945
|
+
const peerBytesFacility = createAddonPeerBytesFacility({
|
|
2894
2946
|
addonId,
|
|
2895
2947
|
logger,
|
|
2896
2948
|
nodeId: this.broker.nodeID,
|
|
2897
2949
|
});
|
|
2898
2950
|
this.getOrCreateDisposerChain(addonId).add(() => peerBytesFacility.dispose());
|
|
2951
|
+
// This builtin's keychain values (D787). In-process, so the caller is
|
|
2952
|
+
// STATED (no wire, no stamp) - the one place the keychain trusts a claim.
|
|
2953
|
+
// `forOwner` is the door a system builtin (device-manager,
|
|
2954
|
+
// storage-orchestrator) uses to keep another addon's values.
|
|
2955
|
+
const secrets = createSecretsAccess({
|
|
2956
|
+
owner: addonId,
|
|
2957
|
+
transport: this.inProcessKeychainTransport(addonId),
|
|
2958
|
+
transportForOwner: (owner) => this.inProcessKeychainTransport(owner),
|
|
2959
|
+
eventBus: this.eventBusService,
|
|
2960
|
+
});
|
|
2899
2961
|
const ctx = {
|
|
2900
2962
|
id: `addon:${addonId}`,
|
|
2901
2963
|
logger,
|
|
@@ -2933,7 +2995,7 @@ class AddonRegistryService {
|
|
|
2933
2995
|
return registry.capabilityRegistry.getSingleton(capName) ?? undefined;
|
|
2934
2996
|
},
|
|
2935
2997
|
},
|
|
2936
|
-
deps: new
|
|
2998
|
+
deps: new AddonDepsManager(dataDir, logger),
|
|
2937
2999
|
kernel: {
|
|
2938
3000
|
localNodeId: this.broker.nodeID,
|
|
2939
3001
|
// The keychain accepts a grouped runner's stamp (its group id) for the
|
|
@@ -2942,9 +3004,9 @@ class AddonRegistryService {
|
|
|
2942
3004
|
storage: storageProvider ?? undefined,
|
|
2943
3005
|
deviceRegistry: this.deviceRegistry ?? undefined,
|
|
2944
3006
|
devices: deviceManagerApi,
|
|
2945
|
-
cluster: { broker:
|
|
3007
|
+
cluster: { broker: adaptBrokerToCluster(this.moleculer.broker) },
|
|
2946
3008
|
streamProbe: kernelStreamProbe,
|
|
2947
|
-
hwaccel:
|
|
3009
|
+
hwaccel: createKernelHwAccel(),
|
|
2948
3010
|
capabilityRegistry: this.capabilityRegistry,
|
|
2949
3011
|
// Per-addon storage-location declarations across every installed
|
|
2950
3012
|
// addon — surfaced from the kernel's AddonLoader so the
|
|
@@ -2980,19 +3042,15 @@ class AddonRegistryService {
|
|
|
2980
3042
|
fetchDevice: async (deviceId) => {
|
|
2981
3043
|
const api = registry.getBrokerApi();
|
|
2982
3044
|
const binding = await api.deviceManager.getBindings.query({ deviceId });
|
|
2983
|
-
return
|
|
3045
|
+
return createDeviceProxy(api, binding);
|
|
2984
3046
|
},
|
|
2985
3047
|
settings: settingsView,
|
|
2986
|
-
|
|
2987
|
-
//
|
|
2988
|
-
//
|
|
2989
|
-
//
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
transport: this.inProcessKeychainTransport(addonId),
|
|
2993
|
-
transportForOwner: (owner) => this.inProcessKeychainTransport(owner),
|
|
2994
|
-
eventBus: this.eventBusService,
|
|
2995
|
-
}),
|
|
3048
|
+
secrets,
|
|
3049
|
+
// This builtin's record kinds (D881). In-process, so the caller is STATED
|
|
3050
|
+
// (no wire, no stamp): the same single place identity is trusted as for
|
|
3051
|
+
// the keychain. `ctx.records` is the owner's own door; a builtin that
|
|
3052
|
+
// acts for another owner (the entries service) builds one with `recordsForOwner`.
|
|
3053
|
+
records: this.recordsForOwner(addonId, secrets),
|
|
2996
3054
|
useCapability(capName, scope = { type: 'global' }) {
|
|
2997
3055
|
return getOrCreateHandle(capName, scope, 15_000);
|
|
2998
3056
|
},
|
|
@@ -3057,10 +3115,10 @@ class AddonRegistryService {
|
|
|
3057
3115
|
let chain = this.disposerChains.get(addonId);
|
|
3058
3116
|
if (chain == null) {
|
|
3059
3117
|
const log = this.loggingService.createLogger().withTags({ addonId });
|
|
3060
|
-
chain = new
|
|
3118
|
+
chain = new DisposerChain({
|
|
3061
3119
|
onError: (err, index) => {
|
|
3062
3120
|
log.error(`Disposer #${index} threw during teardown`, {
|
|
3063
|
-
meta: { error:
|
|
3121
|
+
meta: { error: errMsg(err) },
|
|
3064
3122
|
});
|
|
3065
3123
|
},
|
|
3066
3124
|
});
|
|
@@ -3119,7 +3177,7 @@ class AddonRegistryService {
|
|
|
3119
3177
|
continue;
|
|
3120
3178
|
if (!this.runsInOwnRunner(entry))
|
|
3121
3179
|
continue;
|
|
3122
|
-
const runnerId =
|
|
3180
|
+
const runnerId = resolveRunnerId(entry.declaration, id);
|
|
3123
3181
|
const bucket = plan.get(runnerId) ?? [];
|
|
3124
3182
|
bucket.push({ addonId: id, addonDir: entry.addonDir });
|
|
3125
3183
|
plan.set(runnerId, bucket);
|
|
@@ -3140,11 +3198,11 @@ class AddonRegistryService {
|
|
|
3140
3198
|
runsInOwnRunner(entry) {
|
|
3141
3199
|
if (!entry.declaration || !entry.addonDir)
|
|
3142
3200
|
return false;
|
|
3143
|
-
if (
|
|
3201
|
+
if (resolveAddonPlacement(entry.declaration) === 'agent-only')
|
|
3144
3202
|
return false;
|
|
3145
3203
|
if (entry.packageName !== '@camstack/system')
|
|
3146
3204
|
return true;
|
|
3147
|
-
return
|
|
3205
|
+
return isIsolatedBuiltin(entry.declaration);
|
|
3148
3206
|
}
|
|
3149
3207
|
/**
|
|
3150
3208
|
* Spawn ONE runner subprocess that hosts every addon in `addons` and
|
|
@@ -3182,7 +3240,7 @@ class AddonRegistryService {
|
|
|
3182
3240
|
// Unserialized the loser almost never got here — it spawned a SECOND
|
|
3183
3241
|
// runner instead, which is the defect. Serializing makes the loser
|
|
3184
3242
|
// reliable, so the promise has to be kept rather than documented.
|
|
3185
|
-
if (!/already running/i.test(
|
|
3243
|
+
if (!/already running/i.test(errMsg(err)))
|
|
3186
3244
|
throw err;
|
|
3187
3245
|
this.logger.info('initializeAddonGroup: runner already running — nothing to spawn', {
|
|
3188
3246
|
meta: { runnerId, addonIds: addons.map((a) => a.addonId) },
|
|
@@ -3191,7 +3249,7 @@ class AddonRegistryService {
|
|
|
3191
3249
|
});
|
|
3192
3250
|
}
|
|
3193
3251
|
catch (err) {
|
|
3194
|
-
throw new Error(`Failed to spawn runner "${runnerId}" (${addons.length} addons): ${
|
|
3252
|
+
throw new Error(`Failed to spawn runner "${runnerId}" (${addons.length} addons): ${errMsg(err)}`, { cause: err });
|
|
3195
3253
|
}
|
|
3196
3254
|
// Provider registration for cap methods is handled by
|
|
3197
3255
|
// `CapabilityBridge.onProviderConnected` once the runner's INFO heartbeat
|
|
@@ -3232,7 +3290,7 @@ class AddonRegistryService {
|
|
|
3232
3290
|
}
|
|
3233
3291
|
catch (err) {
|
|
3234
3292
|
this.logger.warn('could not list the runner processes - addon process state is unknown', {
|
|
3235
|
-
meta: { error:
|
|
3293
|
+
meta: { error: errMsg(err) },
|
|
3236
3294
|
});
|
|
3237
3295
|
return null;
|
|
3238
3296
|
}
|
|
@@ -3296,13 +3354,13 @@ class AddonRegistryService {
|
|
|
3296
3354
|
* two live episodes this closes and why the lock inside `process-service`
|
|
3297
3355
|
* could not.
|
|
3298
3356
|
*/
|
|
3299
|
-
serializeRunner =
|
|
3357
|
+
serializeRunner = createRunnerSerializer();
|
|
3300
3358
|
async ensureForkedRunner(addonId, opts) {
|
|
3301
3359
|
const entry = this.addonEntries.get(addonId);
|
|
3302
3360
|
if (!entry?.declaration || !entry.addonDir) {
|
|
3303
3361
|
throw new Error(`ensureForkedRunner("${addonId}") requires an on-disk forked addon`);
|
|
3304
3362
|
}
|
|
3305
|
-
const runnerId =
|
|
3363
|
+
const runnerId = resolveRunnerId(entry.declaration, addonId);
|
|
3306
3364
|
const roster = this.buildAddonGroupPlan([...this.addonEntries.keys()]).get(runnerId) ?? [
|
|
3307
3365
|
{ addonId, addonDir: entry.addonDir },
|
|
3308
3366
|
];
|
|
@@ -3325,12 +3383,12 @@ class AddonRegistryService {
|
|
|
3325
3383
|
// action atomic, and the unit that matters is the sequence.
|
|
3326
3384
|
return this.serializeRunner(runnerId, async () => {
|
|
3327
3385
|
const running = await this.listRunnerProcesses();
|
|
3328
|
-
const plan =
|
|
3386
|
+
const plan = planRunnerConvergence(runnerId, roster.map((r) => r.addonId), running, { restart: effectiveRestart });
|
|
3329
3387
|
// 1. Tear down every stale/mismatched runner that hosts a roster member.
|
|
3330
3388
|
for (const name of plan.stop) {
|
|
3331
3389
|
await this.broker.call('$process.stop', { name }).catch((err) => {
|
|
3332
3390
|
this.logger.warn('ensureForkedRunner: failed to stop stale runner (continuing)', {
|
|
3333
|
-
meta: { runnerId, stale: name, error:
|
|
3391
|
+
meta: { runnerId, stale: name, error: errMsg(err) },
|
|
3334
3392
|
});
|
|
3335
3393
|
});
|
|
3336
3394
|
}
|
|
@@ -3341,7 +3399,7 @@ class AddonRegistryService {
|
|
|
3341
3399
|
.catch((err) => {
|
|
3342
3400
|
// A concurrent path may have won the spawn race — treat "already
|
|
3343
3401
|
// running" as success (idempotent), rethrow anything else.
|
|
3344
|
-
if (!/already running/i.test(
|
|
3402
|
+
if (!/already running/i.test(errMsg(err)))
|
|
3345
3403
|
throw err;
|
|
3346
3404
|
});
|
|
3347
3405
|
this.recentGroupRespawns.set(runnerId, Date.now());
|
|
@@ -3401,7 +3459,7 @@ class AddonRegistryService {
|
|
|
3401
3459
|
const catalogs = child.customActions;
|
|
3402
3460
|
if (catalogs === undefined)
|
|
3403
3461
|
return;
|
|
3404
|
-
const fingerprint =
|
|
3462
|
+
const fingerprint = childCatalogsFingerprint(catalogs);
|
|
3405
3463
|
if (this.childCatalogFingerprints.get(child.childId) === fingerprint)
|
|
3406
3464
|
return;
|
|
3407
3465
|
this.childCatalogFingerprints.set(child.childId, fingerprint);
|
|
@@ -3416,9 +3474,9 @@ class AddonRegistryService {
|
|
|
3416
3474
|
this.customActionRegistry.unregisterAddon(addonId);
|
|
3417
3475
|
continue;
|
|
3418
3476
|
}
|
|
3419
|
-
this.customActionRegistry.registerAddon(addonId,
|
|
3477
|
+
this.customActionRegistry.registerAddon(addonId, toForkedCustomActionsSpec(descriptor), (action, input, caller) => this.dispatchForkedCustomAction(addonId, action, input, caller));
|
|
3420
3478
|
}
|
|
3421
|
-
const summary =
|
|
3479
|
+
const summary = summarizeChildCatalogs(catalogs);
|
|
3422
3480
|
this.logger.info('UDS child custom-action catalogs applied', {
|
|
3423
3481
|
meta: {
|
|
3424
3482
|
childId: child.childId,
|
|
@@ -3446,4 +3504,3 @@ class AddonRegistryService {
|
|
|
3446
3504
|
});
|
|
3447
3505
|
}
|
|
3448
3506
|
}
|
|
3449
|
-
exports.AddonRegistryService = AddonRegistryService;
|