From 40965a55bffa5cc4ff5bf03321bc39d6e0c3277c Mon Sep 17 00:00:00 2001 From: Kasper Markus Date: Tue, 23 Jan 2018 13:38:29 +0100 Subject: [PATCH] GPII-2824: Updated and extended documentation of the matchmaker --- documentation/CanopyMatchMaker.md | 16 +- documentation/MatchMakerFramework.md | 351 ++++++++---------- .../src/MatchMakerFramework.js | 4 +- 3 files changed, 170 insertions(+), 201 deletions(-) diff --git a/documentation/CanopyMatchMaker.md b/documentation/CanopyMatchMaker.md index 51df9a45f..d9dab8afd 100644 --- a/documentation/CanopyMatchMaker.md +++ b/documentation/CanopyMatchMaker.md @@ -21,17 +21,13 @@ Broadly, the match making algorithm used by the canopy match maker is as follows More details are given below on solution types, as well as the two disposition methods used (priority and canopy). ## Solution Types: -To ensure that several conflicting solutions (e.g. two screenreaders) are not launched, "Types" are calculated for each solution, based on the capabilities they have (and in the future, on their explicitly stated types). The inference of solution type based on capabilities is based on the "apptology" tranformation, from the "flat" format into a solution type ontology. In the matchmaking process, each time a solution is 'accepted', any other conflicting solutions (i.e. of the same type) will be rejected. +To avoid that two conflicting solutions of the same time are run, the canopy matchmaker uses Solution Types, as described in [Match Maker Framework](MatchMakerFramework.md) under the section on utilities and concepts. -The apptology is described in more details here: [Apptology.md](Apptology.md). - -## Disposing from prioriy: -Explicit priorities are declared in the NP sets metadata section and will always be a floating point value of 1024 or more. Implicit priorities are deduced from application specific settings. When a user has application specific settings for a solution a priority of 512 is set for that solution. - -Goes through all solutions from high priority to low. If a solution already has a disposition, it is ignored. Else it will be selected (accepted). Any solution with the same type, but a lower priority (or no priority) will be rejected. If there are two or more solutions of the same priority _and_ the same type (even partly), these will be considered a "tie". All lower-priority solutions of this type will still be rejected. The tied solutions will have their current disposition and priority removed and left to be disposed by some other disposal algorithm. +## Disposing from priority: +If the user explicitly or implicitly has specified priorities in terms of applications, this will be used to select the relevant solutions. Priorities can be inferred from application specific settings, or they can be explicitly stated by the user. More information on disposition from priority can be found in [Match Maker Framework](MatchMakerFramework.md) under the utilities and concepts section. ## Disposing from Canopy -The Canopy matching strategy is used for deciding how to dispose solutions in case no priorities are available or there is a priority-tie between two applications of the same type. +The Canopy matching strategy is used for deciding how to dispose solutions in case no priorities are available or there is a priority-tie between two applications of the same type. * The canopy approach is based on a vectorial "fitness measure" of a solution plus a lexicographical ordering * It is similar to the strategy used in resolving CSS rules. @@ -45,7 +41,7 @@ The Canopy matching strategy is used for deciding how to dispose solutions in ca * Compute capabilities of solution * Compute vector of prefix depths for each leaf el path from NP set * Sort vector in descending order of fitness ("fitness vector") -* Rank solutions by fitness using lexicographic ordering +* Rank solutions by fitness using lexicographic ordering *The canopy matching* * Compute fitness vectors for each solution and sort in rank order @@ -53,5 +49,3 @@ The Canopy matching strategy is used for deciding how to dispose solutions in ca * For each solution, “raise the canopy” by setting the canopy value to the maximum of its old value and solution value * For each solution which “raised the canopy” at any leaf, accept it. Reject any solution of the same type. * For each solution which did not raise the canopy, reject it - - diff --git a/documentation/MatchMakerFramework.md b/documentation/MatchMakerFramework.md index d966c5751..76000c8d6 100644 --- a/documentation/MatchMakerFramework.md +++ b/documentation/MatchMakerFramework.md @@ -1,49 +1,94 @@ -## Matchmaker Framework +# Matchmaker Framework + +The matchmaker framework is a component and a set of utitilities that can run either locally or remotely depending on the configuration. Together with the actual matchmaker, it is respnosible for deciding how the users device should be configured based on their preferences, current context and the solution registry data. More specifically, the matchmaker framework has the following responsibility: + +* Doing the preprocessing - that is, preparing and augmenting the input payload for the specific matchmaker +* Making the decision of which matchmaker to call, and then call that matchmaker (for example the [Canopy MatchMaker](CanopyMatchMaker.md)) +* Doing the post-processing - that is, taking the return payload from the matchmakers, parse it and pass it on in the login (or other) flow. +* Generally providing utilities that can be used by matchmaker implementations. -A locally running component, with the following responsiblities: -* Doing the preprocessing - that is, preparing the input payload for the matchmakers -* Making the decision of which MM to call (i.e. hybrid matchmaking), and then call that MM -* Doing the post-processing - that is, taking the return payload from the matchmakers and transformat ## Configuration -Like all other Kettle apps, the Matchmaker Framework is defined by a config file. Generally, the Matchmaker Framework configs are located in the `configs` folder of the matchMakerFramework component. The declaration of what matchmakers are available to the Matchmaker Framework, and where to find them should be set under the `matchMakers` option of the `gpii.matchMakerFramework`. Each entry should have the name of the matchmaker as key, and then the value of an object which at least contains a `url`. For example, in the following config file, two matchmakers are listed to be available. The "default" matchmaker, located at the url http://localhost:8081 and the "RuleBased" one at the url http://localhost:8078. +The matchmaker framework itself is not a Kettle app, but it expect at least matchmaker implementation to be available on a URL. The available matchmaker implementation should be provided in the options block under a `matchMakers` directive. Here, matchmaker implementation can be listed by some identifier (key) and an object with a `{ url: "SOME URL" }` object each. For example the options of having two matchmakers available, where the default one will be on port 8081 will be done like: ``` { - "type": "matchMakerFramework.development.all.local", + "type": "my.matchmakerframework.config", "options": { - "gradeNames": ["fluid.component"], - "components": { - "server": { - "type": "kettle.server", - "options": { - "logging": true, - "components": { - "matchMakerFramework": { - "type": "gpii.matchMakerFramework", - "options": { - "matchMakers": { - "default": { - "url": "http://localhost:8081" - }, - "RuleBased": { - "url": "http://localhost:8078" - } - } - } - } + "distributeOptions": { + "untrusted.development.matchMakers": { + "record": { + "default": { + "url": "http://localhost:8081" + }, + "otherMM": { + "url": "http://localhost:8084" } - } + }, + "target": "{that flowManager}.options.matchMakers" } } - }, - "mergeConfigs": [ - "./base.json" - ] + } } + ``` +## Detailed Description: + + +### Main steps of the match maker frameworks matching process + +1. The matchmaking process is triggered by the `processMatch` event of the FlowManager. The actual matchmaking flow is dictated by a combination of the priorities in `gpii.flowmanager.processmatch.priorities`, and the `processMatch` listeners (which are different depending on whether the system runs in trusted or untrusted mode) +* Preprocess: + * Triggered when the initial data has been collected (preferences, context, solution registry entries, etc) + * Augments the match maker input with inferred common terms (see under important utilities/functions in this document) + * TODO: _Full solutions registry is currently being used when inferring common terms. This is because the users NP set might contain e.g. Linux app preferences, even if the user is logging into a windows box. We then need the linux solutions registry entries to get information (if available) about how to transform the linux app preferences into common terms (that in turn can be translated into app settings for windows)._ +* matchMakerDispatcher: + * Sends the matchmaker input data to the actual matchmaker. _While there is functionality in place for allowing multiple matchmakers, the system is currently hardcoded to use the “default” one, which is the Canopy matchmaker in all the default configs_. + * A promise is returned, which will contain the original matchmaker input along with the response from the matchmaker in a block keyed by `matchMakerOutput`. + * The return payload is built in: `gpii.matchMakerFramework.utils.buildReturnPayload` + + +### Important utilities and concepts in the Match Maker Framework + +**The Canopy Matchmaker**: While this is not part of the matchmaker framework, it is important to mention the Canopy Match Maker, which is an (the only) implementation of a matchmaker, which uses most of the utilities described in this document as well as a canopy matching algorithm. It is described in details here: https://github.com/GPII/universal/blob/master/documentation/CanopyMatchMaker.md + + +**Solution Types**: To ensure that several conflicting solutions (e.g. two screenreaders) are not launched, "Types" can be calculated for each solution, based on the capabilities they have (and in the future, on their explicitly stated types). The inference of solution type based on capabilities is based on the "apptology" tranformation, from the "flat" format into a solution type ontology. In the matchmaking process, each time a solution is 'accepted', any other conflicting solutions (i.e. of the same type) will be rejected. Adding solution type information can be done via the `gpii.matchMakerFramework.utils.addSolutionTypeInformation` function. + +The apptology is described in more details here: [Apptology.md](Apptology.md). + +**Common Term Inference**: If the solution registry entry for a solution has an `inverseCapabilitiesTransformations` block specified, or does not have a `capabilitiesTransformations` (in which case, the system attempts to automatically calculate the inverses), the matchmaker framework can infer common terms from application specific settings. This means, that if the user has application specific settings for e.g. windows magnifier, the equivalent common term preferences can be inferred and used in the matchmaking process (e.g. finding the relevant settings for the linux magnifier). The inference happens via the `gpii.matchMakerFramework.utils.inferCommonTerms` function, and if these should be merged with an NP set, this can be done via the `gpii.matchMakerFramework.utils.addInferredCommonTerms` function. It is worth stressing two things: The use of inferred common terms is not mandatory in a matchmaking flow, but available via the above functions, and something which _is_ being used in the canopy matchmaker. Secondly, there is no metadata generated saying that the common terms are inferred, meaning that once added to an NP set, the matchmaker has no way of knowing whether the term was originally in the NP set or was inferred. + +Also, hte full solutions registry is currently being used when inferring common terms. This is because the users NP set might contain e.g. Linux app preferences, even if the user is logging into a windows box. We then need the linux solutions registry entries to get information (if available) about how to transform the linux app preferences into common terms (that in turn can be translated into app settings for windows). This should be changed into a more dynamically loaded solution registry fetching based on the users application settings, once the solutions registry has been turned into a queriable service. + + +**gpii.matchMakerFramework.utils.disposeFromPriority**: Explicit priorities are declared in the NP sets metadata section and will always be a floating point value of 1024 or more. Implicit priorities are deduced from application specific settings. When a user has application specific settings for a solution a priority of 512 is set for that solution. + +This function goes through all solutions from high priority to low. If a solution already has a disposition, it is ignored. Else it will be selected (accepted). Any solution with the same type, but a lower priority (or no priority) will be rejected. If there are two or more solutions of the same priority _and_ the same type (even partly), these will be considered a "tie". All lower-priority solutions of this type will still be rejected. The tied solutions will have their current disposition and priority removed and left to be disposed by some other disposal algorithm. + + +**gpii.matchmakerframework.utils.disposeSolution:** In many ways the “Heart” of the matchmaking process. It is responsible for adding priorities, augmenting the solution and preference data and finally passing it to some disposition (matchmaker) strategy that is passed as a parameter. In more details the following happens: +1. The solutions are run through `gpii.matchMakerFramework.utils.expandSolutions` augmenting them to, besides the original solution registry data, a skeleton of the solutions capabilities is built is built - i.e. a hierarchical object matching the solutions capabilities. +1. Next solution priorities are added to each solution according to application settings via `gpii.matchMakerFramework.utils.expandSolutions`. That is, if a user has any application settings the solution is considered to have a priority of 512. +1. Following this, explicit priorities of the users NP set is added to each solution. That is, if a user has application priorities in their metadata, this is added. These values will always be above 1024, and hence take priority (overwrite) the implicit priorities from the step above. +1. A leaves structure of the preferences is calculated in the `gpii.matchMakerFramework.utils.computeLeaves` function. This is a structure with a key for each preference in an EL-path format and a value of true. This, in conjunction with the solutions skeleton is useful for looking up matches via fluids' getPath functionality. +1. Once all this data has been prepared, the information is sent to the strategy (e.g. canopy match makers algorithm) +1. Finally a disposition is assigned to each solution based on the output of the strategy. + + +**Preference filtering:** When using a the untrusted flowmanager, security dictates that no "irrelevant" or unnused preferences of settings should be sent to the local device. Since the users NP set does need to be sent to the local device for use in the PSP, a filtering needs to happen, to ensure that the only settings reaching the local device are those that have been used to configure the device. This filtering can be done using the `gpii.matchMakerFramework.utils.filterPreferencesForSolution` function. + + +**matchMakerDispatcher:** Sends the matchmaker input data to the actual matchmaker. While there is functionality in place for allowing multiple matchmakers, the system is currently hardcoded to use the “default” one, which is the Canopy matchmaker. + +A promise is returned, which will contain the original matchmaker input along with the matchmaker response in a block keyed by matchMakerOutput + +The dispatching happens in the function `gpii.matchMakerFramework.matchMakerDispatcher`. + + + ## API: The matchmakers that can be called from the Matchmaker Framework need to adhere to the following API. @@ -58,91 +103,66 @@ The input for these POST requests will be in the following format. Note that it ``` { - preferences: { - "contexts": { - "gpii-default": { - "name": "Default preferences", - "preferences": { - "http://registry.gpii.net/common/fontSize": 15, - "http://registry.gpii.net/common/speechRate": 15 - } - }, - "nighttime-at-home": { - "name": "Nighttime at home", - "preferences": { - "http://registry.gpii.net/common/fontSize": 18 - }, - "metadata": [ - { - "type": "required", - "scope": ["http://registry.gpii.net/common/fontSize"], - "value": 100 - } - ], - "conditions": [ - { - "type": "http://gpii.net/common/operators/inRange", - "max": 1800, - "min": 600, - "inputPath": "http://registry.gpii.net/conditions/timeOfDay" - } - ] - } +{ + "activeContexts": [], + "environmentReporter": {}, + "inferredCommonTerms": { + "gpii-default": { + "com.microsoft.windows.highContrast": { + "http://registry.gpii.net/common/highContrastEnabled": true, + "http://registry.gpii.net/common/highContrastTheme": "white-black" + }, + "net.gpii.uioPlus": { + "http://registry.gpii.net/common/highContrastEnabled": true, + "http://registry.gpii.net/common/highContrastTheme": "white-black" + } + } + }, + "userToken": "snapset_2a", + "preferences": { + "name": "Dark & Larger 125%", + "contexts": { + "gpii-default": { + "name": "Default preferences", + "preferences": { + "http://registry.gpii.net/common/cursorSize": 1, + "http://registry.gpii.net/common/DPIScale": 1.25, + "http://registry.gpii.net/applications/com.microsoft.windows.highContrast": { + "http://registry.gpii.net/common/highContrastEnabled": true, + "http://registry.gpii.net/common/highContrastTheme": "white-black" + }, + "http://registry.gpii.net/applications/net.gpii.uioPlus": { + "http://registry.gpii.net/common/highContrastEnabled": true, + "http://registry.gpii.net/common/highContrastTheme": "white-black" + } } - }, - deviceReporter: { - "OS": { - "id": "win32", - "version": "5.0.0" - }, - "solutions": [ - { - "id": "com.microsoft.windows.onscreenKeyboard" - }, { - "id": "com.microsoft.windows.desktop" - }, - (...) - { - "id": "org.nvda-project" - }, { - "id": "com.freedomScientific.jaws" - } - ] - }, - environmentReporter: { - "http://registry.gpii.net/terms/environment/brightness": 60, - "http://registry.gpii.net/terms/environment/sound": -6 - "http://registry.gpii.net/terms/environment/timeOfDay": "18:29:00" - }, - solutionsRegistry: { - //stuff from: https://github.com/GPII/universal/blob/master/testData/solutions/win32.json5 - }, - activeContexts: [ - "gpii-default", - "nighttime-at-home" + } + } + }, + "deviceContext": { + "solutions": [ + { + "id": "com.microsoft.windows.magnifier" + }, + { + "id": "com.microsoft.windows.onscreenKeyboard" + }, + ... + { + "id": "com.microsoft.windows.narrator" + } ], - inverseCapabilities: { - "com.microsoft.windows.cursors": { - "http://registry.gpii.net/common/mouseTrailing": 0.8, - (...), - "http://registry.gpii.net/common/cursorSize": 0.60 - }, - (...), - "org.nvda-project": { - (...) - } - }, - rematch: { //out of scope for review - it would be useful to allow a MM specific block in the MM output, - //which can then be sent back in the rematch section to make the MM more informed - // [ ? - userFeedback: { - dislike: true - }, - previousOutput: { - // vast document previously output by matchmaker in this session - } - // ]? + "OS": { + "id": "win32", + "version": "6.1.7601" } + }, + "solutionsRegistryEntries": { + "com.microsoft.windows.magnifier": { .. }, + "com.microsoft.windows.narrator": { ... }, + .... + "com.microsoft.windows.filterKeys": { ... } + } } ``` @@ -152,82 +172,39 @@ The return payload from at call to `/match` should be in the following format: ``` { - "inferredConfiguration": { - "gpii-default": { - "applications": { - "com.microsoft.windows.desktop": { - "active": true, - "settings": { - "http://registry.gpii.net/common/fontSize": 200, - "http://registry.gpii.net/applications/com.microsoft.windows.desktop/otherSetting": "reallyBig" - } - }, - "org.cats": {} - } + "inferredConfiguration": { + "gpii-default": { + "applications": { + "com.microsoft.windows.highContrast": { + "active": true, + "settings": { + "http://registry.gpii.net/common/highContrastEnabled": true, + "http://registry.gpii.net/common/highContrastTheme": "white-black", + "http://registry.gpii.net/applications/com.microsoft.windows.highContrast": {} + } }, - "nighttime-at-home":{ - "applications": { - "com.microsoft.windows.desktop": { - "active": true, - "settings": { - "http://registry.gpii.net/common/fontSize": 800 /// <-- really? fontsize 800!? - } - } - }, - "conditions": [ - { - "type": "http://gpii.net/common/operators/(time?)inRange", - "max": 1800, - "min": 600, - "inputPath": "http://registry.gpii.net/conditions/timeOfDay" - } - ] + "com.microsoft.windows.screenDPI": { + "active": true, + "settings": { + "http://registry.gpii.net/common/DPIScale": 1.25 + } }, - "gpii-xyz-autogenerated12365478": { - "applications": { - "com.freedomScientic.jaws": { - "active": true, - "settings": { - "active": false, - "http://registry.gpii.net/common/speechRate": 800 - } - } - }, - "conditions": [ - { - "type": "http://gpii.net/common/operators/inRange", - "max": 10, - "min": 6, - "inputPath": "http://registry.gpii.net/conditions/sound" - } - ], - "metadata": [ - { - "scope": [ - "org.nvda-project" - ], - "type": "infoMessage", - "help": "....TBDwithalex...." - }, - { - "type": "PCPPopulation", - "scope": [], - "settings": { - "http: //registry.gpii.net/applications/com.microsoft.windows.desktop": [ - "fontSize" - ], - "com.microsoft.windows.desktop": [ - "http: //registry.gpii.net/common/fontSize" - ] - } - } - ] + "com.microsoft.windows.cursors": { + "active": true, + "settings": { + "http://registry.gpii.net/common/cursorSize": 1 + } + }, + "net.gpii.uioPlus": { + "active": true, + "settings": { + "http://registry.gpii.net/common/highContrastEnabled": true, + "http://registry.gpii.net/common/highContrastTheme": "white-black", + "http://registry.gpii.net/applications/net.gpii.uioPlus": {} + } } - }, - "status": { - "type": "success", - "code": 200, - "message": "Catsare(inconsistently)decent" + } } + } } ``` diff --git a/gpii/node_modules/matchMakerFramework/src/MatchMakerFramework.js b/gpii/node_modules/matchMakerFramework/src/MatchMakerFramework.js index 89b21a5df..37de7a48c 100644 --- a/gpii/node_modules/matchMakerFramework/src/MatchMakerFramework.js +++ b/gpii/node_modules/matchMakerFramework/src/MatchMakerFramework.js @@ -59,9 +59,7 @@ fluid.defaults("gpii.matchMakerFramework", { */ gpii.matchMakerFramework.preProcess = function (that, initialPayload) { var matchMakerInput = fluid.extend({ - activeContexts: [ // TODO calculate properly - "gpii-default" - ], + activeContexts: [], // set later in the process environmentReporter: {}, // TODO, inferredCommonTerms: gpii.matchMakerFramework.utils.inferCommonTerms(initialPayload.preferences, initialPayload.fullSolutionsRegistry) }, initialPayload);