Règles de jointure des liens reference
Déclarez une paire de colonnes de jointure explicite (et une normalisation facultative des valeurs) sur un type de lien reference afin que le moteur de graphe effectue une jointure déterministe au lieu de deviner — et évitez les mauvaises surprises silencieuses à 0 arête lorsque la clé étrangère est une colonne hors identité ou que les formats stockés diffèrent.
Cette page couvre la règle join facultative des types de liens reference (kind: "reference", qui est la valeur par défaut). Les types de liens de proximité utilisent un mécanisme différent (maxMeters / maxMinutes) et ne sont pas concernés par le contenu de cette page.
Dans le contrat d'authoring v1, sourceTypes et targetTypes doivent chacun être un tableau à un seul élément ou "*". Plusieurs slugs de types d'objets sur l'une ou l'autre extrémité sont rejetés tant qu'un contrat versionné d'extrémités en union n'est pas disponible.
Contexte : comment les arêtes sont trouvées habituellement
Un type de lien reference infère les arêtes par jointure par valeur : une propriété de l'objet source doit être égale à une colonne de l'objet cible. Par défaut, le moteur découvre automatiquement la colonne de jointure à l'aide d'une heuristique : il recherche un recouvrement de tokens entre le nom de la colonne source et les colonnes d'identité de la cible (les colonnes déclarées dans defineIdentityRule) ainsi que les colonnes columnMap hors identité de la liaison cible — les colonnes d'identité étant classées en premier. Ce sondage a été élargi dans cette version pour couvrir les colonnes columnMap en plus des colonnes d'identité.
Cela fonctionne bien lorsque les noms des colonnes source et cible ont un recouvrement de tokens clair, que la colonne cible soit ou non une colonne d'identité. Cela ne fonctionne pas lorsque :
- Les noms de colonnes ne se recouvrent pas alors que les valeurs correspondent sémantiquement — l'heuristique classe les candidates par recouvrement de tokens, de sorte que les colonnes sans token commun obtiennent un score nul et ne sont pas sondées.
- Les valeurs sont stockées dans des formats différents de chaque côté (par ex. ASN sous la forme
"AS3215"contre3215, cveId avec une casse différente) — l'heuristique utilise toujours l'égalité brute.
Dans ces cas, la découverte automatique produit soit 0 arête, soit de mauvaises arêtes — en silence, sans aucune erreur.
Déclarer une règle de jointure explicite
Ajoutez un objet join à defineLinkType(...). Le moteur l'utilise de manière déterministe et ignore entièrement la découverte automatique :
import { defineOntology, defineLinkType } from '@scrydon/sdk-authoring/ontologies'
defineLinkType({
slug: 'VulnAffectsVendor',
displayName: 'Vulnerability affects vendor',
sourceTypes: ['Vulnerability'],
targetTypes: ['NetworkOperatorVendor'],
// No kind needed — "reference" is the default.
join: {
sourceColumn: 'vendorProject', // column on the Vulnerability side
targetColumn: 'vendorName', // column on the NetworkOperatorVendor side
},
})vendorName n'est pas une colonne d'identité de NetworkOperatorVendor, si bien que la découverte automatique la manquerait. Déclarer join fixe le moteur sur exactement cette paire de colonnes.
Pourquoi cet exemple est important
Les enregistrements CISA KEV stockent le fournisseur concerné dans vendorProject. Le type d'objet NetworkOperatorVendor conserve son identifiant canonique dans vendorName — une propriété hors identité. Sans règle join explicite, l'heuristique sonde à la fois les colonnes d'identité et hors identité (classées par tokens), mais les résultats dépendent du recouvrement des noms et peuvent être non déterministes lorsque plusieurs colonnes sont candidates. Déclarer join fixe le moteur sur exactement cette paire de colonnes et rend l'inférence des arêtes déterministe.
Normalisation des valeurs
Si la même valeur logique est stockée dans des formats différents de chaque côté, utilisez normalize pour les rendre comparables au moment de la jointure :
defineLinkType({
slug: 'AsPathUsesAsn',
displayName: 'AS-path entry uses ASN',
sourceTypes: ['AsPathEntry'],
targetTypes: ['AutonomousSystem'],
join: {
sourceColumn: 'asnRaw', // stored as "AS3215" (string with prefix)
targetColumn: 'asNumber', // stored as 3215 (integer or plain number string)
normalize: 'digits', // strip all non-digit characters before comparing
},
})Valeur de normalize | Ce qu'elle fait |
|---|---|
"trim" | Supprime les espaces en début et en fin. |
"lower" | Convertit en minuscules (utile pour caseId, hostname, email). |
"digits" | Ne conserve que les chiffres — "AS3215" devient "3215", "3215" reste "3215". |
La normalisation est appliquée aux deux côtés avant la comparaison. La jointure est une égalité standard après normalisation.
La normalisation ne remplace pas des données propres. Si les deux côtés n'ont réellement aucune valeur en commun — par ex. cveId couvre des millésimes de CVE différents de chaque côté — aucune normalisation ne trouvera de correspondances. N'utilisez normalize que lorsque la même valeur logique est encodée différemment, pas lorsque les jeux de données ne se recouvrent tout simplement pas.
Exemple de casse des identifiants CVE
Certains flux stockent les identifiants CVE sous la forme CVE-2024-12345 et d'autres sous la forme cve-2024-12345. Normalisez en minuscules pour les traiter comme égaux :
defineLinkType({
slug: 'ExploitTargetsCve',
displayName: 'Exploit targets CVE',
sourceTypes: ['Exploit'],
targetTypes: ['CveRecord'],
join: {
sourceColumn: 'cveRef',
targetColumn: 'cveId',
normalize: 'lower',
},
})Quand déclarer join
Déclarez une règle join explicite lorsque :
- Les noms de colonnes n'ont aucun token en commun. La découverte automatique (élargie dans cette version pour sonder à la fois les colonnes
columnMapd'identité et hors identité) classe les candidates selon les tokens partagés entre les noms des colonnes source et cible — si aucun token ne se recouvre, la paire obtient un score nul et n'est pas sondée, produisant 0 arête en silence. - Les valeurs sont stockées dans des formats différents. L'heuristique utilise toujours l'égalité brute, de sorte que les différences de format (par ex.
"AS3215"contre3215, casse des identifiants CVE) nécessitent unjoinexplicite avecnormalize. - La jointure découverte automatiquement est ambiguë. Lorsque plusieurs colonnes cibles partagent des tokens avec la colonne source, déclarez
joinpour rendre le choix explicite.
Vous n'avez pas besoin de join lorsque :
- Le nom de la colonne source a un recouvrement de tokens clair avec une colonne cible (d'identité ou
columnMaphors identité) et que les deux côtés stockent les valeurs dans le même format. - Les deux côtés utilisent le même nom de colonne et le même format.
Préférez la canonicalisation à l'ingestion lorsque c'est possible
normalize est destiné aux cas où vous ne maîtrisez pas le format source — par exemple, lorsque deux flux externes publient des ASN dans des formats différents.
Si vous maîtrisez bien la manière dont les valeurs sont ingérées (par ex. votre propre pipeline ETL alimente la table), la meilleure approche consiste à stocker les valeurs dans un format canonique des deux côtés au moment de l'ingestion. C'est plus rapide au moment de la requête, plus facile à comprendre, et cela évite une logique de normalisation cachée dans l'extension.
Règle empirique : corrigez les données à l'ingestion lorsque vous en êtes propriétaire ; n'utilisez
normalizeque lorsque ce n'est pas le cas.
Référence complète du schéma
join?: {
sourceColumn: string // Property slug on the source object type
targetColumn: string // Property slug on the target object type
normalize?: 'trim' | 'lower' | 'digits'
}| Champ | Requis | Remarques |
|---|---|---|
sourceColumn | Oui | Le slug de propriété côté source. Doit correspondre à une colonne avec effect: "allow" dans la politique DLP/de colonnes — les colonnes masquées ou cachées ne peuvent pas servir de clés de jointure. |
targetColumn | Oui | Le slug de propriété côté cible. Peut être une colonne hors identité. Même exigence d'effet allow. |
normalize | Non | Appliqué symétriquement aux deux côtés avant la comparaison. Choisissez la valeur qui couvre la différence de format. |
sourceColumn et targetColumn doivent tous deux se résoudre en colonnes avec effect: "allow" dans la politique au niveau des colonnes. Les colonnes masquées ou cachées ne sont jamais utilisées comme clés de jointure (le moteur l'impose au moment de la requête — le nombre d'arêtes renvoie null au lieu de 0 si une restriction de politique est rencontrée). normalize n'affecte que la comparaison de jointure (la clause ON) sur les colonnes que vous déclarez dans join — il ne touche jamais aux valeurs d'identité renvoyées pour les références de nœuds, qui sont toujours comparées et renvoyées telles quelles. Vos colonnes de jointure déclarées peuvent être des colonnes d'identité ou hors identité ; dans les deux cas, seule la comparaison est normalisée, pas l'identité stockée. Les colonnes masquées ou cachées ne sont jamais utilisées comme clés de jointure (une restriction de politique fait renvoyer null au nombre d'arêtes au lieu de 0).
Les liens de proximité ne sont pas concernés
Cette page porte sur les types de liens reference. Les types de liens de proximité (kind: "proximity") utilisent un mécanisme d'inférence d'arêtes entièrement différent — la distance géodésique — et n'utilisent pas du tout join. Consultez le guide Types de liens de proximité.
Pour aller plus loin
Inférez des arêtes par distance spatiale et fenêtre temporelle facultative — aucune clé partagée nécessaire.
Modélisez le même concept à travers plusieurs systèmes sources, avec provenance et transformations.
Guide de base — types d'objets, types de liens, types d'actions, règles d'identité et liaisons.