@camstack/server 0.2.2 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. package/{src/agent-status-page.ts → dist/agent-status-page.js} +30 -45
  2. package/dist/api/addon-upload.js +441 -0
  3. package/dist/api/addons-custom.router.js +91 -0
  4. package/dist/api/auth-whoami.js +55 -0
  5. package/dist/api/bridge-addons.router.js +109 -0
  6. package/dist/api/capabilities.router.js +229 -0
  7. package/dist/api/core/addon-settings.router.js +117 -0
  8. package/dist/api/core/agents.router.js +73 -0
  9. package/dist/api/core/auth.router.js +286 -0
  10. package/dist/api/core/bulk-update-coordinator.js +229 -0
  11. package/dist/api/core/cap-providers.js +1124 -0
  12. package/dist/api/core/capabilities.router.js +138 -0
  13. package/dist/api/core/collection-preference.js +17 -0
  14. package/dist/api/core/event-bus-proxy.router.js +45 -0
  15. package/dist/api/core/hwaccel.router.js +91 -0
  16. package/dist/api/core/live-events.router.js +61 -0
  17. package/dist/api/core/logs.router.js +172 -0
  18. package/dist/api/core/notifications.router.js +67 -0
  19. package/dist/api/core/repl.router.js +35 -0
  20. package/dist/api/core/settings-backend.router.js +121 -0
  21. package/dist/api/core/stream-probe.router.js +58 -0
  22. package/dist/api/core/system-events.router.js +100 -0
  23. package/dist/api/health/health.routes.js +68 -0
  24. package/{src/api/oauth2/consent-page.ts → dist/api/oauth2/consent-page.js} +11 -20
  25. package/dist/api/oauth2/oauth2-routes.js +219 -0
  26. package/dist/api/trpc/cap-mount-helpers.js +194 -0
  27. package/dist/api/trpc/cap-route-error-formatter.js +133 -0
  28. package/dist/api/trpc/client-ip.js +147 -0
  29. package/dist/api/trpc/core-cap-bridge.js +115 -0
  30. package/dist/api/trpc/generated-cap-mounts.js +388 -0
  31. package/dist/api/trpc/generated-cap-routers.js +7635 -0
  32. package/dist/api/trpc/scope-access.js +93 -0
  33. package/dist/api/trpc/trpc.context.js +184 -0
  34. package/dist/api/trpc/trpc.middleware.js +139 -0
  35. package/dist/api/trpc/trpc.router.js +188 -0
  36. package/dist/auth/session-cookie.js +47 -0
  37. package/dist/boot/boot-config.js +241 -0
  38. package/dist/boot/integration-id-backfill.js +76 -0
  39. package/dist/boot/post-boot.service.js +85 -0
  40. package/dist/core/addon/addon-call-gateway.js +99 -0
  41. package/dist/core/addon/addon-package.service.js +1560 -0
  42. package/dist/core/addon/addon-registry.service.js +2739 -0
  43. package/{src/core/addon/addon-row-manifest.ts → dist/core/addon/addon-row-manifest.js} +5 -5
  44. package/dist/core/addon/addon-search.service.js +62 -0
  45. package/dist/core/addon/addon-settings-provider.js +102 -0
  46. package/dist/core/addon/addon.tokens.js +5 -0
  47. package/dist/core/addon-bridge/addon-bridge.service.js +145 -0
  48. package/dist/core/addon-pages/addon-pages.service.js +107 -0
  49. package/dist/core/addon-widgets/addon-widgets.service.js +120 -0
  50. package/dist/core/agent/agent-registry.service.js +477 -0
  51. package/dist/core/auth/auth.service.js +10 -0
  52. package/dist/core/capability/capability.service.js +58 -0
  53. package/dist/core/config/config.schema.js +7 -0
  54. package/dist/core/config/config.service.js +10 -0
  55. package/dist/core/events/event-bus.service.js +83 -0
  56. package/dist/core/feature/feature.service.js +10 -0
  57. package/dist/core/lifecycle/lifecycle-state-machine.js +6 -0
  58. package/dist/core/logging/log-ring-buffer.js +6 -0
  59. package/dist/core/logging/logging.service.js +130 -0
  60. package/dist/core/logging/scoped-logger.js +6 -0
  61. package/dist/core/moleculer/cap-call-fn.js +50 -0
  62. package/dist/core/moleculer/cap-route-authority.js +122 -0
  63. package/dist/core/moleculer/moleculer.service.js +898 -0
  64. package/dist/core/network/network-quality.service.js +7 -0
  65. package/dist/core/notification/notification-wrapper.service.js +33 -0
  66. package/dist/core/notification/toast-wrapper.service.js +25 -0
  67. package/dist/core/provider/provider.tokens.js +4 -0
  68. package/dist/core/repl/repl-engine.service.js +140 -0
  69. package/dist/core/storage/fs-storage-backend.js +6 -0
  70. package/dist/core/storage/storage-location-manager.js +6 -0
  71. package/dist/core/storage/storage.service.js +7 -0
  72. package/dist/core/streaming/stream-probe.service.js +209 -0
  73. package/dist/core/topology/topology-emitter.service.js +106 -0
  74. package/dist/launcher.js +325 -0
  75. package/dist/main.js +1098 -0
  76. package/dist/manual-boot.js +227 -0
  77. package/package.json +5 -1
  78. package/src/__tests__/addon-install-e2e.test.ts +0 -74
  79. package/src/__tests__/addon-pages-e2e.test.ts +0 -200
  80. package/src/__tests__/addon-route-session.test.ts +0 -17
  81. package/src/__tests__/addon-settings-router.spec.ts +0 -67
  82. package/src/__tests__/addon-upload.spec.ts +0 -475
  83. package/src/__tests__/agent-registry.spec.ts +0 -179
  84. package/src/__tests__/agent-status-page.spec.ts +0 -82
  85. package/src/__tests__/auth-session-cookie.test.ts +0 -48
  86. package/src/__tests__/bulk-update-coordinator.spec.ts +0 -303
  87. package/src/__tests__/cap-ownership-authority.spec.ts +0 -431
  88. package/src/__tests__/cap-providers/cap-providers-location-import.spec.ts +0 -206
  89. package/src/__tests__/cap-providers/cap-usage-graph.spec.ts +0 -37
  90. package/src/__tests__/cap-providers/compute-topology-categories.spec.ts +0 -110
  91. package/src/__tests__/cap-providers/integrations-delete-cascade.spec.ts +0 -292
  92. package/src/__tests__/cap-providers-bulk-update.spec.ts +0 -408
  93. package/src/__tests__/cap-route-adapter.spec.ts +0 -302
  94. package/src/__tests__/cap-routers/_meta.spec.ts +0 -199
  95. package/src/__tests__/cap-routers/addon-settings.router.spec.ts +0 -115
  96. package/src/__tests__/cap-routers/broker-routing.router.spec.ts +0 -177
  97. package/src/__tests__/cap-routers/cap-route-error-formatter.spec.ts +0 -125
  98. package/src/__tests__/cap-routers/capabilities-node.spec.ts +0 -68
  99. package/src/__tests__/cap-routers/device-link-overlay.spec.ts +0 -137
  100. package/src/__tests__/cap-routers/device-manager-aggregate.router.spec.ts +0 -194
  101. package/src/__tests__/cap-routers/harness.ts +0 -163
  102. package/src/__tests__/cap-routers/metrics-provider.router.spec.ts +0 -133
  103. package/src/__tests__/cap-routers/null-provider-guard.spec.ts +0 -64
  104. package/src/__tests__/cap-routers/pipeline-executor.router.spec.ts +0 -159
  105. package/src/__tests__/cap-routers/settings-store.router.spec.ts +0 -291
  106. package/src/__tests__/capability-e2e.test.ts +0 -384
  107. package/src/__tests__/cli-e2e.test.ts +0 -150
  108. package/src/__tests__/core-cap-bridge.spec.ts +0 -91
  109. package/src/__tests__/dev-bootstrap-shm-ring.spec.ts +0 -40
  110. package/src/__tests__/device-settings-contribution-dispatch.spec.ts +0 -280
  111. package/src/__tests__/embedded-deps-e2e.test.ts +0 -125
  112. package/src/__tests__/event-bus-proxy-router.spec.ts +0 -75
  113. package/src/__tests__/fixtures/mock-analysis-addon-a.ts +0 -37
  114. package/src/__tests__/fixtures/mock-analysis-addon-b.ts +0 -37
  115. package/src/__tests__/fixtures/mock-log-addon.ts +0 -37
  116. package/src/__tests__/fixtures/mock-storage-addon.ts +0 -40
  117. package/src/__tests__/framework-allowlist.spec.ts +0 -96
  118. package/src/__tests__/framework-installer-defer-restart.spec.ts +0 -165
  119. package/src/__tests__/https-e2e.test.ts +0 -124
  120. package/src/__tests__/lifecycle-e2e.test.ts +0 -189
  121. package/src/__tests__/live-events-subscription.spec.ts +0 -149
  122. package/src/__tests__/moleculer/uds-readiness.spec.ts +0 -150
  123. package/src/__tests__/moleculer/uds-topology.spec.ts +0 -418
  124. package/src/__tests__/moleculer/uds-unowned-call.spec.ts +0 -383
  125. package/src/__tests__/moleculer-register-node-idempotency.spec.ts +0 -273
  126. package/src/__tests__/native-cap-route.spec.ts +0 -427
  127. package/src/__tests__/oauth2-account-linking.spec.ts +0 -867
  128. package/src/__tests__/post-boot-restart.spec.ts +0 -161
  129. package/src/__tests__/singleton-contention.test.ts +0 -499
  130. package/src/__tests__/streaming-diagnostic.test.ts +0 -615
  131. package/src/__tests__/streaming-scale.test.ts +0 -314
  132. package/src/__tests__/uds-addon-call-wiring.spec.ts +0 -242
  133. package/src/__tests__/uds-log-ingest.spec.ts +0 -183
  134. package/src/api/__tests__/addons-custom.spec.ts +0 -148
  135. package/src/api/__tests__/capabilities.router.test.ts +0 -56
  136. package/src/api/addon-upload.ts +0 -529
  137. package/src/api/addons-custom.router.ts +0 -101
  138. package/src/api/auth-whoami.ts +0 -101
  139. package/src/api/bridge-addons.router.ts +0 -122
  140. package/src/api/capabilities.router.ts +0 -265
  141. package/src/api/core/__tests__/auth-router-totp.spec.ts +0 -297
  142. package/src/api/core/__tests__/integration-markers.spec.ts +0 -10
  143. package/src/api/core/addon-settings.router.ts +0 -127
  144. package/src/api/core/agents.router.ts +0 -86
  145. package/src/api/core/auth.router.ts +0 -322
  146. package/src/api/core/bulk-update-coordinator.ts +0 -305
  147. package/src/api/core/cap-providers.ts +0 -1339
  148. package/src/api/core/capabilities.router.ts +0 -149
  149. package/src/api/core/collection-preference.ts +0 -40
  150. package/src/api/core/event-bus-proxy.router.ts +0 -45
  151. package/src/api/core/hwaccel.router.ts +0 -108
  152. package/src/api/core/live-events.router.ts +0 -67
  153. package/src/api/core/logs.router.ts +0 -195
  154. package/src/api/core/notifications.router.ts +0 -66
  155. package/src/api/core/repl.router.ts +0 -39
  156. package/src/api/core/settings-backend.router.ts +0 -140
  157. package/src/api/core/stream-probe.router.ts +0 -57
  158. package/src/api/core/system-events.router.ts +0 -125
  159. package/src/api/health/health.routes.ts +0 -117
  160. package/src/api/oauth2/__tests__/oauth2-routes.spec.ts +0 -62
  161. package/src/api/oauth2/oauth2-routes.ts +0 -281
  162. package/src/api/trpc/__tests__/client-ip.spec.ts +0 -146
  163. package/src/api/trpc/__tests__/scope-access-device.spec.ts +0 -268
  164. package/src/api/trpc/__tests__/scope-access.spec.ts +0 -102
  165. package/src/api/trpc/__tests__/webrtc-session-ua-enrich.spec.ts +0 -136
  166. package/src/api/trpc/cap-mount-helpers.ts +0 -245
  167. package/src/api/trpc/cap-route-error-formatter.ts +0 -171
  168. package/src/api/trpc/client-ip.ts +0 -147
  169. package/src/api/trpc/core-cap-bridge.ts +0 -154
  170. package/src/api/trpc/generated-cap-mounts.ts +0 -1240
  171. package/src/api/trpc/generated-cap-routers.ts +0 -11523
  172. package/src/api/trpc/scope-access.ts +0 -110
  173. package/src/api/trpc/trpc.context.ts +0 -258
  174. package/src/api/trpc/trpc.middleware.ts +0 -146
  175. package/src/api/trpc/trpc.router.ts +0 -389
  176. package/src/auth/session-cookie.ts +0 -54
  177. package/src/boot/__tests__/integration-id-backfill.spec.ts +0 -131
  178. package/src/boot/boot-config.ts +0 -259
  179. package/src/boot/integration-id-backfill.ts +0 -109
  180. package/src/boot/post-boot.service.ts +0 -105
  181. package/src/core/addon/__tests__/addon-registry-capability.test.ts +0 -62
  182. package/src/core/addon/__tests__/addon-row-manifest.spec.ts +0 -62
  183. package/src/core/addon/addon-call-gateway.ts +0 -171
  184. package/src/core/addon/addon-package.service.ts +0 -1787
  185. package/src/core/addon/addon-registry.service.ts +0 -3130
  186. package/src/core/addon/addon-search.service.ts +0 -91
  187. package/src/core/addon/addon-settings-provider.ts +0 -220
  188. package/src/core/addon/addon.tokens.ts +0 -2
  189. package/src/core/addon-bridge/addon-bridge.service.ts +0 -130
  190. package/src/core/addon-pages/addon-pages.service.spec.ts +0 -117
  191. package/src/core/addon-pages/addon-pages.service.ts +0 -82
  192. package/src/core/addon-widgets/addon-widgets.service.ts +0 -95
  193. package/src/core/agent/agent-registry.service.ts +0 -529
  194. package/src/core/auth/auth.service.spec.ts +0 -86
  195. package/src/core/auth/auth.service.ts +0 -8
  196. package/src/core/capability/capability.service.ts +0 -66
  197. package/src/core/config/config.schema.ts +0 -3
  198. package/src/core/config/config.service.spec.ts +0 -175
  199. package/src/core/config/config.service.ts +0 -7
  200. package/src/core/events/event-bus.service.spec.ts +0 -235
  201. package/src/core/events/event-bus.service.ts +0 -89
  202. package/src/core/feature/feature.service.spec.ts +0 -99
  203. package/src/core/feature/feature.service.ts +0 -8
  204. package/src/core/lifecycle/lifecycle-state-machine.spec.ts +0 -166
  205. package/src/core/lifecycle/lifecycle-state-machine.ts +0 -3
  206. package/src/core/logging/log-ring-buffer.ts +0 -3
  207. package/src/core/logging/logging.service.spec.ts +0 -287
  208. package/src/core/logging/logging.service.ts +0 -143
  209. package/src/core/logging/scoped-logger.ts +0 -3
  210. package/src/core/moleculer/cap-call-fn.spec.ts +0 -173
  211. package/src/core/moleculer/cap-call-fn.ts +0 -107
  212. package/src/core/moleculer/cap-route-authority.ts +0 -194
  213. package/src/core/moleculer/moleculer.service.ts +0 -1072
  214. package/src/core/network/network-quality.service.spec.ts +0 -53
  215. package/src/core/network/network-quality.service.ts +0 -5
  216. package/src/core/notification/notification-wrapper.service.ts +0 -34
  217. package/src/core/notification/toast-wrapper.service.ts +0 -27
  218. package/src/core/provider/provider.tokens.ts +0 -1
  219. package/src/core/repl/repl-engine.service.spec.ts +0 -444
  220. package/src/core/repl/repl-engine.service.ts +0 -155
  221. package/src/core/storage/fs-storage-backend.spec.ts +0 -70
  222. package/src/core/storage/fs-storage-backend.ts +0 -3
  223. package/src/core/storage/storage-location-manager.spec.ts +0 -130
  224. package/src/core/storage/storage-location-manager.ts +0 -3
  225. package/src/core/storage/storage.service.spec.ts +0 -73
  226. package/src/core/storage/storage.service.ts +0 -3
  227. package/src/core/streaming/stream-probe.service.ts +0 -221
  228. package/src/core/topology/topology-emitter.service.ts +0 -105
  229. package/src/launcher.ts +0 -314
  230. package/src/main.ts +0 -1245
  231. package/src/manual-boot.ts +0 -301
  232. package/tsconfig.build.json +0 -8
  233. package/tsconfig.json +0 -33
  234. package/vitest.config.ts +0 -26
@@ -1,431 +0,0 @@
1
- /**
2
- * Task G2 — single cap-ownership authority (consolidation)
3
- *
4
- * This test pins the consolidated behavior: the `setNativeFallback` closure
5
- * consults the CapRouteResolver FIRST for hub-local native caps, and only
6
- * falls through to `resolveNativeCapOwnerSync` for remote native caps.
7
- *
8
- * The three canonical cases:
9
- * (a) Hub-local child device-scoped native cap → resolver classifies hub-local-uds;
10
- * `resolveNativeCapOwnerSync` is NOT consulted.
11
- * (b) Remote device-scoped native cap → resolver does not find hub-local;
12
- * `resolveNativeCapOwnerSync` is consulted and `buildNativeCapProxy` is used.
13
- * (c) Cap absent entirely → neither resolver nor resolveNativeCapOwnerSync
14
- * know the owner; fallback returns null.
15
- *
16
- * Behavior parity: the old flow called resolveNativeCapOwnerSync first and then
17
- * forked on nodeId prefix. The new flow queries the resolver first for hub-local
18
- * (skipping resolveNativeCapOwnerSync in that branch) and falls through to
19
- * resolveNativeCapOwnerSync only for the remote branch. Both flows return
20
- * identical proxy objects / null for the same inputs.
21
- */
22
-
23
- import { describe, it, expect, vi } from 'vitest'
24
- import type {
25
- NodeCapAuthority,
26
- HubLocalChildDispatcher,
27
- CapRouteResolverDeps,
28
- } from '@camstack/kernel'
29
- import { CapRouteResolver, CapRouteError } from '@camstack/kernel'
30
-
31
- // ---------------------------------------------------------------------------
32
- // Helper: build the consolidated native-cap fallback (the G2 implementation)
33
- // ---------------------------------------------------------------------------
34
-
35
- /**
36
- * The G2-consolidated `setNativeFallback` implementation.
37
- *
38
- * Resolution order:
39
- * 1. Try the resolver for hub-local (avoids resolveNativeCapOwnerSync entirely
40
- * when the resolver's snapshot knows the child).
41
- * 2. If no hub-local route: consult resolveNativeCapOwnerSync for remote caps.
42
- * 3. If not remote either: return null.
43
- */
44
- function buildConsolidatedNativeFallback(opts: {
45
- readonly resolver: CapRouteResolver | null
46
- readonly hubNodeId: string
47
- readonly resolveNativeCapOwnerSync: (
48
- capName: string,
49
- deviceId: number,
50
- ) => { addonId: string; nodeId: string } | null
51
- readonly buildNativeCapProxy: (
52
- addonId: string,
53
- capName: string,
54
- deviceId: number,
55
- ) => Record<string, unknown>
56
- }): (capName: string, deviceId: number) => unknown | null {
57
- const { resolver, hubNodeId, resolveNativeCapOwnerSync, buildNativeCapProxy } = opts
58
-
59
- return (capName: string, deviceId: number): unknown | null => {
60
- // 1. Hub-local: resolver is the single authority. No resolveNativeCapOwnerSync needed.
61
- if (resolver !== null) {
62
- try {
63
- const route = resolver.resolveCapRoute(capName, { nodeId: hubNodeId, deviceId })
64
- if (route.kind === 'hub-local-uds') {
65
- // Build a proxy that routes every method through the resolver.
66
- const proxy: Record<string, (input: unknown) => Promise<unknown>> = {}
67
- // Proxy is returned as a property-access object (same shape as before)
68
- return new Proxy(proxy, {
69
- get(_target, property): ((input: unknown) => Promise<unknown>) | undefined {
70
- if (typeof property !== 'string') return undefined
71
- return (input: unknown): Promise<unknown> => {
72
- const mergedInput =
73
- typeof input === 'object' && input !== null
74
- ? { ...input, deviceId }
75
- : { deviceId }
76
- const r = resolver.resolveCapRoute(capName, { nodeId: hubNodeId, deviceId })
77
- return resolver.dispatch(r, property, mergedInput)
78
- }
79
- },
80
- })
81
- }
82
- } catch (err) {
83
- // resolver throws CapRouteError for no-provider/node-offline — fall through to remote
84
- if (!(err instanceof CapRouteError)) throw err
85
- }
86
- }
87
-
88
- // 2. Remote: consult resolveNativeCapOwnerSync (includes push-fed remoteNativeCaps).
89
- const owner = resolveNativeCapOwnerSync(capName, deviceId)
90
- if (!owner) return null
91
-
92
- // Only build a remote proxy for genuinely-remote nodes; hub-local already handled above.
93
- if (!owner.nodeId.startsWith(`${hubNodeId}/`)) {
94
- return buildNativeCapProxy(owner.addonId, capName, deviceId)
95
- }
96
-
97
- // Edge case: resolver was null OR didn't find the hub-local route but resolveNativeCapOwnerSync
98
- // says hub-local. This can happen during startup before the resolver is initialised.
99
- // Fall through to null — the caller will retry when the resolver is ready.
100
- return null
101
- }
102
- }
103
-
104
- // ---------------------------------------------------------------------------
105
- // Helpers to build test doubles
106
- // ---------------------------------------------------------------------------
107
-
108
- interface FakeHubLocalRegistry extends HubLocalChildDispatcher {
109
- readonly callSpy: ReturnType<typeof vi.fn>
110
- }
111
-
112
- function makeHubLocalRegistry(
113
- caps: ReadonlyMap<string, ReadonlyMap<number | 'singleton', string>>,
114
- ): FakeHubLocalRegistry {
115
- const callSpy = vi.fn(async (_childId: string, _input: unknown) => ({ ok: true, from: 'uds' }))
116
- return {
117
- resolveChildId: (capName: string, deviceId?: number): string | null => {
118
- const capMap = caps.get(capName)
119
- if (capMap === undefined) return null
120
- if (deviceId !== undefined) {
121
- const deviceSpecific = capMap.get(deviceId)
122
- if (deviceSpecific !== undefined) return deviceSpecific
123
- }
124
- return capMap.get('singleton') ?? null
125
- },
126
- callCapOnChild: callSpy,
127
- callSpy,
128
- }
129
- }
130
-
131
- interface NativeCapSpec {
132
- readonly nodeId: string
133
- readonly addonId: string
134
- readonly capName: string
135
- readonly deviceId: number
136
- }
137
-
138
- function makeNodeAuthority(
139
- systemCaps: ReadonlyMap<string, { addonId: string; nodeId: string }>,
140
- onlineNodes: ReadonlySet<string>,
141
- nativeCaps: readonly NativeCapSpec[],
142
- ): NodeCapAuthority {
143
- return {
144
- nodeKnowsCap: (nodeId: string, capName: string): boolean => {
145
- const sys = systemCaps.get(capName)
146
- if (sys !== undefined && sys.nodeId === nodeId) return true
147
- return nativeCaps.some((n) => n.nodeId === nodeId && n.capName === capName)
148
- },
149
- nodeIsAgent: (nodeId: string): boolean => nodeId !== 'hub' && !nodeId.includes('/'),
150
- nodeOnline: (nodeId: string): boolean => onlineNodes.has(nodeId),
151
- listNodeIds: (): readonly string[] => {
152
- const ids = new Set<string>()
153
- for (const spec of systemCaps.values()) ids.add(spec.nodeId)
154
- for (const n of nativeCaps) ids.add(n.nodeId)
155
- return [...ids]
156
- },
157
- getAddonId: (nodeId: string, capName: string): string | null => {
158
- const sys = systemCaps.get(capName)
159
- if (sys !== undefined && sys.nodeId === nodeId) return sys.addonId
160
- const nat = nativeCaps.find((n) => n.nodeId === nodeId && n.capName === capName)
161
- return nat?.addonId ?? null
162
- },
163
- getAgentChildId: (): string | null => null,
164
- isNativeCap: (nodeId: string, capName: string, deviceId?: number): boolean => {
165
- if (deviceId !== undefined) {
166
- return nativeCaps.some(
167
- (n) => n.nodeId === nodeId && n.capName === capName && n.deviceId === deviceId,
168
- )
169
- }
170
- return nativeCaps.some((n) => n.nodeId === nodeId && n.capName === capName)
171
- },
172
- }
173
- }
174
-
175
- const HUB_NODE_ID = 'hub'
176
-
177
- // ---------------------------------------------------------------------------
178
- // Test (a): hub-local child device-scoped native cap
179
- // ---------------------------------------------------------------------------
180
-
181
- describe('G2 — (a) hub-local device-scoped native cap: resolver is consulted first, resolveNativeCapOwnerSync is NOT called', () => {
182
- it('builds a proxy without consulting resolveNativeCapOwnerSync when resolver finds hub-local-uds', async () => {
183
- const hubLocalRegistry = makeHubLocalRegistry(
184
- new Map([['ptz', new Map([[7, 'provider-reolink']])]]),
185
- )
186
- const nativeCaps: NativeCapSpec[] = [
187
- {
188
- nodeId: 'hub/provider-reolink',
189
- addonId: 'addon-provider-reolink',
190
- capName: 'ptz',
191
- deviceId: 7,
192
- },
193
- ]
194
- const nodeAuthority = makeNodeAuthority(
195
- new Map(),
196
- new Set(['hub/provider-reolink']),
197
- nativeCaps,
198
- )
199
-
200
- const deps: CapRouteResolverDeps = {
201
- hubNodeId: HUB_NODE_ID,
202
- broker: { call: vi.fn(), waitForServices: vi.fn() },
203
- hubLocalRegistry,
204
- nodeAuthority,
205
- inProcessProviders: () => null,
206
- }
207
- const resolver = new CapRouteResolver(deps)
208
-
209
- const resolveNativeCapOwnerSync = vi.fn()
210
- const buildNativeCapProxy = vi.fn()
211
-
212
- const fallback = buildConsolidatedNativeFallback({
213
- resolver,
214
- hubNodeId: HUB_NODE_ID,
215
- resolveNativeCapOwnerSync,
216
- buildNativeCapProxy,
217
- })
218
-
219
- const proxy = fallback('ptz', 7)
220
-
221
- // Proxy is NOT null — hub-local route was found
222
- expect(proxy).not.toBeNull()
223
- expect(typeof proxy).toBe('object')
224
-
225
- // resolveNativeCapOwnerSync was NOT called (resolver handled it)
226
- expect(resolveNativeCapOwnerSync).not.toHaveBeenCalled()
227
- // buildNativeCapProxy was NOT called (not a remote cap)
228
- expect(buildNativeCapProxy).not.toHaveBeenCalled()
229
- })
230
-
231
- it('proxy built by the consolidated fallback routes to callCapOnChild via the resolver', async () => {
232
- const hubLocalRegistry = makeHubLocalRegistry(
233
- new Map([['ptz', new Map([[7, 'provider-reolink']])]]),
234
- )
235
- const nativeCaps: NativeCapSpec[] = [
236
- {
237
- nodeId: 'hub/provider-reolink',
238
- addonId: 'addon-provider-reolink',
239
- capName: 'ptz',
240
- deviceId: 7,
241
- },
242
- ]
243
- const nodeAuthority = makeNodeAuthority(
244
- new Map(),
245
- new Set(['hub/provider-reolink']),
246
- nativeCaps,
247
- )
248
-
249
- const deps: CapRouteResolverDeps = {
250
- hubNodeId: HUB_NODE_ID,
251
- broker: { call: vi.fn(), waitForServices: vi.fn() },
252
- hubLocalRegistry,
253
- nodeAuthority,
254
- inProcessProviders: () => null,
255
- }
256
- const resolver = new CapRouteResolver(deps)
257
-
258
- const fallback = buildConsolidatedNativeFallback({
259
- resolver,
260
- hubNodeId: HUB_NODE_ID,
261
- resolveNativeCapOwnerSync: vi.fn(),
262
- buildNativeCapProxy: vi.fn(),
263
- })
264
-
265
- const proxy = fallback('ptz', 7) as Record<string, (args: unknown) => Promise<unknown>>
266
- expect(proxy).not.toBeNull()
267
-
268
- // Invoke a method through the proxy — should reach callCapOnChild
269
- const result = await proxy['move']!({ pan: 10 })
270
- expect(result).toEqual({ ok: true, from: 'uds' })
271
- expect(hubLocalRegistry.callSpy).toHaveBeenCalledOnce()
272
- const [calledChildId, calledInput] = hubLocalRegistry.callSpy.mock.calls[0] as [string, unknown]
273
- expect(calledChildId).toBe('provider-reolink')
274
- expect(calledInput).toMatchObject({ capName: 'ptz', method: 'move', deviceId: 7 })
275
- })
276
- })
277
-
278
- // ---------------------------------------------------------------------------
279
- // Test (b): remote device-scoped native cap
280
- // ---------------------------------------------------------------------------
281
-
282
- describe('G2 — (b) remote device-scoped native cap: resolver does not find hub-local, resolveNativeCapOwnerSync IS called', () => {
283
- it('falls through to resolveNativeCapOwnerSync and calls buildNativeCapProxy for remote caps', () => {
284
- // No hub-local registry for this cap+device (resolver will throw no-provider for hub)
285
- const hubLocalRegistry = makeHubLocalRegistry(new Map()) // empty: no hub-local caps
286
- const nodeAuthority = makeNodeAuthority(new Map(), new Set([]), [])
287
-
288
- const deps: CapRouteResolverDeps = {
289
- hubNodeId: HUB_NODE_ID,
290
- broker: { call: vi.fn(), waitForServices: vi.fn() },
291
- hubLocalRegistry,
292
- nodeAuthority,
293
- inProcessProviders: () => null,
294
- }
295
- const resolver = new CapRouteResolver(deps)
296
-
297
- const resolveNativeCapOwnerSync = vi.fn().mockReturnValue({
298
- addonId: 'addon-provider-reolink',
299
- nodeId: 'dev-agent-0/provider-reolink', // remote node
300
- })
301
- const remoteProxy = { remote: true }
302
- const buildNativeCapProxy = vi.fn().mockReturnValue(remoteProxy)
303
-
304
- const fallback = buildConsolidatedNativeFallback({
305
- resolver,
306
- hubNodeId: HUB_NODE_ID,
307
- resolveNativeCapOwnerSync,
308
- buildNativeCapProxy,
309
- })
310
-
311
- const result = fallback('ptz', 7)
312
-
313
- // resolveNativeCapOwnerSync was called (no hub-local route in resolver)
314
- expect(resolveNativeCapOwnerSync).toHaveBeenCalledWith('ptz', 7)
315
- // buildNativeCapProxy was called with the remote owner's addonId
316
- expect(buildNativeCapProxy).toHaveBeenCalledWith('addon-provider-reolink', 'ptz', 7)
317
- // The remote proxy is returned
318
- expect(result).toBe(remoteProxy)
319
- })
320
- })
321
-
322
- // ---------------------------------------------------------------------------
323
- // Test (c): cap absent entirely
324
- // ---------------------------------------------------------------------------
325
-
326
- describe('G2 — (c) cap absent: returns null (no hub-local, no remote owner)', () => {
327
- it('returns null when resolver finds no hub-local route AND resolveNativeCapOwnerSync returns null', () => {
328
- const hubLocalRegistry = makeHubLocalRegistry(new Map())
329
- const nodeAuthority = makeNodeAuthority(new Map(), new Set([]), [])
330
-
331
- const deps: CapRouteResolverDeps = {
332
- hubNodeId: HUB_NODE_ID,
333
- broker: { call: vi.fn(), waitForServices: vi.fn() },
334
- hubLocalRegistry,
335
- nodeAuthority,
336
- inProcessProviders: () => null,
337
- }
338
- const resolver = new CapRouteResolver(deps)
339
-
340
- const resolveNativeCapOwnerSync = vi.fn().mockReturnValue(null)
341
- const buildNativeCapProxy = vi.fn()
342
-
343
- const fallback = buildConsolidatedNativeFallback({
344
- resolver,
345
- hubNodeId: HUB_NODE_ID,
346
- resolveNativeCapOwnerSync,
347
- buildNativeCapProxy,
348
- })
349
-
350
- const result = fallback('ptz', 7)
351
-
352
- expect(result).toBeNull()
353
- expect(buildNativeCapProxy).not.toHaveBeenCalled()
354
- })
355
- })
356
-
357
- // ---------------------------------------------------------------------------
358
- // Test (startup-window): resolver === null, resolveNativeCapOwnerSync returns a hub-local
359
- // owner — must return null (no Moleculer proxy to a UDS-only child)
360
- // ---------------------------------------------------------------------------
361
-
362
- describe('G2 — (startup-window) resolver null, resolveNativeCapOwnerSync returns hub-local owner: returns null', () => {
363
- it('returns null and does NOT call buildNativeCapProxy when resolver is null and owner is hub-local', () => {
364
- // resolver === null simulates the window before the CapRouteResolver is initialised
365
- const resolveNativeCapOwnerSync = vi.fn().mockReturnValue({
366
- addonId: 'addon-provider-x',
367
- nodeId: `${HUB_NODE_ID}/provider-x`, // hub-local UDS child — must NOT get a Moleculer proxy
368
- })
369
- const buildNativeCapProxy = vi.fn()
370
-
371
- const fallback = buildConsolidatedNativeFallback({
372
- resolver: null,
373
- hubNodeId: HUB_NODE_ID,
374
- resolveNativeCapOwnerSync,
375
- buildNativeCapProxy,
376
- })
377
-
378
- const result = fallback('ptz', 7)
379
-
380
- // Guard: hub-local UDS children must not receive a Moleculer proxy
381
- expect(result).toBeNull()
382
- expect(buildNativeCapProxy).not.toHaveBeenCalled()
383
- // resolveNativeCapOwnerSync IS consulted (resolver skipped due to null)
384
- expect(resolveNativeCapOwnerSync).toHaveBeenCalledWith('ptz', 7)
385
- })
386
- })
387
-
388
- // ---------------------------------------------------------------------------
389
- // Test: singleton (hub-in-process) cap resolves in-process, no resolveNativeCapOwnerSync
390
- // ---------------------------------------------------------------------------
391
-
392
- describe('G2 — (d) hub-in-process singleton: resolver classifies hub-in-process, resolveNativeCapOwnerSync not called', () => {
393
- it('resolves hub-in-process singleton without consulting resolveNativeCapOwnerSync', () => {
394
- const invokeSpy = vi.fn().mockResolvedValue({ ok: true })
395
- const inProcessProviders = (_cap: string) =>
396
- _cap === 'device-manager' ? { invoke: invokeSpy } : null
397
-
398
- const deps: CapRouteResolverDeps = {
399
- hubNodeId: HUB_NODE_ID,
400
- broker: { call: vi.fn(), waitForServices: vi.fn() },
401
- hubLocalRegistry: null,
402
- nodeAuthority: makeNodeAuthority(new Map(), new Set([]), []),
403
- inProcessProviders,
404
- }
405
- const resolver = new CapRouteResolver(deps)
406
-
407
- const resolveNativeCapOwnerSync = vi.fn()
408
- const buildNativeCapProxy = vi.fn()
409
-
410
- const fallback = buildConsolidatedNativeFallback({
411
- resolver,
412
- hubNodeId: HUB_NODE_ID,
413
- resolveNativeCapOwnerSync,
414
- buildNativeCapProxy,
415
- })
416
-
417
- // For in-process singletons, the fallback is not called at all (CapabilityRegistry
418
- // finds the provider directly). But if called with a deviceId for such a cap, the
419
- // resolver will classify hub-in-process — not hub-local-uds — so we fall through
420
- // to the remote branch and resolveNativeCapOwnerSync is consulted (returns null).
421
- // This is the correct behavior: in-process singletons go through getNativeProvider
422
- // on the local nativeProviders map, not through setNativeFallback.
423
- //
424
- // The consolidated fallback is specifically for CROSS-PROCESS native caps.
425
- // The assertion here is that for a cap with NO hub-local-uds route AND no
426
- // resolveNativeCapOwnerSync result, the fallback correctly returns null.
427
- const result = fallback('device-manager', 7)
428
- expect(result).toBeNull()
429
- expect(buildNativeCapProxy).not.toHaveBeenCalled()
430
- })
431
- })
@@ -1,206 +0,0 @@
1
- /**
2
- * cap-providers-location-import.spec.ts
3
- *
4
- * Verifies that `buildIntegrationsProvider().getAvailableTypes()` correctly
5
- * surfaces the `supportsLocationImport` flag:
6
- * - A `device-adoption` addon whose manifest declares `supportsLocationImport: true`
7
- * → returned entry has `supportsLocationImport === true`.
8
- * - A `device-adoption` addon WITHOUT the flag
9
- * → returned entry has `supportsLocationImport === false`.
10
- * - A `device-provider` addon (non-adoption kind), regardless of the flag
11
- * → returned entry has `supportsLocationImport === false`.
12
- *
13
- * Harness mirrors integrations-delete-cascade.spec.ts in the same directory.
14
- */
15
- import { describe, it, expect, vi } from 'vitest'
16
- import { buildIntegrationsProvider } from '../../api/core/cap-providers.js'
17
-
18
- // ── Minimal stubs ────────────────────────────────────────────────────────────
19
-
20
- function makeIntegrationRegistry() {
21
- return {
22
- listIntegrations: vi.fn(async () => []),
23
- getIntegration: vi.fn(async (_id: string) => null),
24
- deleteIntegration: vi.fn(async (_id: string) => undefined),
25
- createIntegration: vi.fn(),
26
- updateIntegration: vi.fn(),
27
- getIntegrationByAddonId: vi.fn(),
28
- getIntegrationSettings: vi.fn(),
29
- setIntegrationSettings: vi.fn(),
30
- }
31
- }
32
-
33
- type FakeAddonRow = {
34
- manifest: {
35
- id: string
36
- name: string
37
- description: string
38
- capabilities: Array<{ name: string }>
39
- supportsLocationImport?: boolean
40
- brokerKind?: string
41
- instanceMode?: string
42
- icon?: string
43
- color?: string
44
- }
45
- declaration?: {
46
- supportsLocationImport?: boolean
47
- brokerKind?: string
48
- instanceMode?: string
49
- icon?: string
50
- color?: string
51
- }
52
- process?: { state: string }
53
- }
54
-
55
- function makeAddonRegistry(addons: FakeAddonRow[] = []) {
56
- const integrationReg = makeIntegrationRegistry()
57
- return {
58
- getIntegrationRegistry: vi.fn(() => integrationReg),
59
- listAddons: vi.fn(() => addons),
60
- restartAddon: vi.fn(),
61
- getCapabilityRegistry: vi.fn(() => ({
62
- getProviderByAddon: vi.fn(() => null),
63
- })),
64
- }
65
- }
66
-
67
- function makeEventBus() {
68
- return { emit: vi.fn() }
69
- }
70
-
71
- function makeLogger() {
72
- return {
73
- info: vi.fn(),
74
- warn: vi.fn(),
75
- error: vi.fn(),
76
- }
77
- }
78
-
79
- function makeLoggingService() {
80
- const log = makeLogger()
81
- return { createLogger: vi.fn(() => log) }
82
- }
83
-
84
- // ── Tests ────────────────────────────────────────────────────────────────────
85
-
86
- describe('getAvailableTypes — supportsLocationImport flag', () => {
87
- it('returns supportsLocationImport=true for a device-adoption addon that declares it in the manifest', async () => {
88
- const addon: FakeAddonRow = {
89
- manifest: {
90
- id: 'provider-homeassistant',
91
- name: 'Home Assistant Provider',
92
- description: 'Adopt HA devices',
93
- capabilities: [{ name: 'device-adoption' }],
94
- supportsLocationImport: true,
95
- brokerKind: 'home-assistant',
96
- instanceMode: 'multiple',
97
- },
98
- }
99
- const ar = makeAddonRegistry([addon])
100
- const provider = buildIntegrationsProvider(
101
- ar as never,
102
- makeEventBus() as never,
103
- makeLoggingService() as never,
104
- null,
105
- )
106
- const types = await provider.getAvailableTypes()
107
-
108
- expect(types).toHaveLength(1)
109
- expect(types[0]).toMatchObject({
110
- addonId: 'provider-homeassistant',
111
- kind: 'device-adoption',
112
- supportsLocationImport: true,
113
- })
114
- })
115
-
116
- it('returns supportsLocationImport=false for a device-adoption addon that omits the flag', async () => {
117
- const addon: FakeAddonRow = {
118
- manifest: {
119
- id: 'provider-test-adoption',
120
- name: 'Test Adoption Provider',
121
- description: 'Adoption without location import',
122
- capabilities: [{ name: 'device-adoption' }],
123
- brokerKind: 'test-broker',
124
- instanceMode: 'multiple',
125
- },
126
- }
127
- const ar = makeAddonRegistry([addon])
128
- const provider = buildIntegrationsProvider(
129
- ar as never,
130
- makeEventBus() as never,
131
- makeLoggingService() as never,
132
- null,
133
- )
134
- const types = await provider.getAvailableTypes()
135
-
136
- expect(types).toHaveLength(1)
137
- expect(types[0]).toMatchObject({
138
- addonId: 'provider-test-adoption',
139
- kind: 'device-adoption',
140
- supportsLocationImport: false,
141
- })
142
- })
143
-
144
- it('returns supportsLocationImport=false for a device-provider addon even when the flag is set on manifest', async () => {
145
- const addon: FakeAddonRow = {
146
- manifest: {
147
- id: 'provider-reolink',
148
- name: 'Reolink Provider',
149
- description: 'Classic device provider',
150
- capabilities: [{ name: 'device-provider' }],
151
- // Hypothetical: even if someone mistakenly sets this on a device-provider
152
- supportsLocationImport: true,
153
- instanceMode: 'single',
154
- },
155
- }
156
- const ar = makeAddonRegistry([addon])
157
- const provider = buildIntegrationsProvider(
158
- ar as never,
159
- makeEventBus() as never,
160
- makeLoggingService() as never,
161
- null,
162
- )
163
- const types = await provider.getAvailableTypes()
164
-
165
- expect(types).toHaveLength(1)
166
- expect(types[0]).toMatchObject({
167
- addonId: 'provider-reolink',
168
- kind: 'device-provider',
169
- supportsLocationImport: false,
170
- })
171
- })
172
-
173
- it('prefers declaration.supportsLocationImport over manifest when declaration is present', async () => {
174
- const addon: FakeAddonRow = {
175
- manifest: {
176
- id: 'provider-homeassistant',
177
- name: 'Home Assistant Provider',
178
- description: 'Adopt HA devices',
179
- capabilities: [{ name: 'device-adoption' }],
180
- supportsLocationImport: false,
181
- brokerKind: 'home-assistant',
182
- instanceMode: 'multiple',
183
- },
184
- declaration: {
185
- supportsLocationImport: true,
186
- brokerKind: 'home-assistant',
187
- instanceMode: 'multiple',
188
- },
189
- }
190
- const ar = makeAddonRegistry([addon])
191
- const provider = buildIntegrationsProvider(
192
- ar as never,
193
- makeEventBus() as never,
194
- makeLoggingService() as never,
195
- null,
196
- )
197
- const types = await provider.getAvailableTypes()
198
-
199
- expect(types).toHaveLength(1)
200
- expect(types[0]).toMatchObject({
201
- addonId: 'provider-homeassistant',
202
- kind: 'device-adoption',
203
- supportsLocationImport: true,
204
- })
205
- })
206
- })
@@ -1,37 +0,0 @@
1
- import { describe, it, expect, beforeEach, vi } from 'vitest'
2
- import { __resetCapUsageRegistryForTests, getCapUsageRegistry } from '@camstack/kernel'
3
- import { buildNodesProvider } from '../../api/core/cap-providers'
4
-
5
- describe('nodes.getCapUsageGraph provider', () => {
6
- beforeEach(() => {
7
- __resetCapUsageRegistryForTests()
8
- })
9
-
10
- it('returns recorded edges projected through the cap-usage registry', async () => {
11
- const reg = getCapUsageRegistry()
12
- const t0 = Date.now() - 5_000
13
- reg.recordCall({
14
- callerAddonId: 'A',
15
- providerAddonId: 'B',
16
- capName: 'foo',
17
- methodName: 'm',
18
- atMs: t0,
19
- })
20
- reg.recordCall({
21
- callerAddonId: 'A',
22
- providerAddonId: 'B',
23
- capName: 'foo',
24
- methodName: 'm',
25
- atMs: t0 + 1000,
26
- })
27
-
28
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
29
- const provider = buildNodesProvider({} as any, { broker: { call: vi.fn() } } as any)
30
- const out = await provider.getCapUsageGraph({ windowSeconds: 60 })
31
- expect(out).toHaveLength(1)
32
- expect(out[0]!.callerAddonId).toBe('A')
33
- expect(out[0]!.providerAddonId).toBe('B')
34
- expect(out[0]!.capName).toBe('foo')
35
- expect(out[0]!.callsPerMin).toBeGreaterThan(0)
36
- })
37
- })