Ontologies
Créer des extensions d'ontologie — types d'objets, types de liens, types d'actions, règles d'identité et liaisons
Cet artefact est distribué dans une extension. Pour le cycle de vie partagé — installation, extension build, upload — voir Extensions & SDK d'authoring.
Une Ontologie est le système de types sur lequel votre tenant fonctionne : Customer, Asset, Risk, Incident, les relations entre eux (owns, affects, mitigates), et les actions typées que vous pouvez effectuer sur eux (AssignAsset, AssessRisk). Le SDK d'authoring vous permet de distribuer cela sous forme d'une extension d'ontologie versionnée et validée.
Une ontologie est le système de types. Une base de connaissances est le contenu qui l'utilise. Ce sont des choses différentes — une ontologie, de nombreuses bases de connaissances. Plusieurs ontologies peuvent coexister dans un même tenant (par défaut + par domaine + importé via extension).
Installation
bun add -d @scrydon/sdk-authoring zodnpm install --save-dev @scrydon/sdk-authoring zodimport {
defineOntology,
defineObjectType,
defineLinkType,
defineActionType,
defineIdentityRule,
defineBinding,
defineProperty,
} from '@scrydon/sdk-authoring/ontologies'Ce que contient une extension
| Élément | Rôle |
|---|---|
| Object Type | Une entité typée — Customer, Asset, RiskFinding. A des properties, un kind (analytical / transactional / binding_only). |
| Link Type | Une relation typée — affects(RiskFinding → Customer). Cardinalité + nom de traversée inverse optionnel. |
| Action Type | Une mutation côté serveur contrôlée par OPA. Porte un schéma d'entrée Zod et un descripteur d'effet secondaire (object_edit / link_edit / outbox / webhook). |
| Identity Rule | Comment les instances sont dédupliquées. Clés de correspondance déterministes, ER probabiliste optionnel (signaux + seuils). |
| Binding | Relie une source en amont (silver_table, memex_page, process_flow_task, process_flow_action, extraction, stream, manual) à des instances d'objet ou de lien typées. |
Un exemple complet
import {
defineOntology,
defineObjectType,
defineLinkType,
defineActionType,
defineIdentityRule,
defineProperty,
} from '@scrydon/sdk-authoring/ontologies'
import { z } from 'zod'
export default defineOntology({
id: 'acme-crm',
version: '1.0.0',
displayName: 'ACME CRM',
description: 'Salesforce-style CRM ontology',
maturity: 'preview',
author: { name: 'Acme Inc.' },
objectTypes: [
defineObjectType({
slug: 'Account',
displayName: 'Account',
kind: 'transactional',
properties: [
defineProperty({
slug: 'name',
displayName: 'Legal name',
scalarType: 'string',
cardinality: 'required',
}),
defineProperty({
slug: 'jurisdiction',
displayName: 'Jurisdiction',
scalarType: 'string',
classification: 'internal',
}),
defineProperty({
slug: 'riskTier',
displayName: 'Risk tier',
scalarType: 'string',
piiFlags: ['regulatory'],
}),
],
}),
defineObjectType({
slug: 'Asset',
displayName: 'Asset',
kind: 'transactional',
properties: [
defineProperty({ slug: 'name', displayName: 'Name', scalarType: 'string' }),
defineProperty({ slug: 'value', displayName: 'Value', scalarType: 'float' }),
],
}),
],
linkTypes: [
defineLinkType({
slug: 'owns',
displayName: 'Owns',
sourceTypes: ['Account'],
targetTypes: ['Asset'],
cardinality: '1-N',
reverseName: 'ownedBy',
}),
],
actionTypes: [
defineActionType({
slug: 'AssignAsset',
displayName: 'Assign asset to account',
subjectTypeSlug: 'Asset',
inputZod: z.object({ accountId: z.string().uuid() }),
sideEffectDescriptor: { kind: 'link_edit' },
}),
],
identityRules: [
defineIdentityRule({
objectTypeSlug: 'Account',
deterministicKeys: ['name', 'jurisdiction'],
}),
],
bindings: [],
})Modèle de propriété
| Champ | Notes |
|---|---|
scalarType | L'un de string, int, float, bool, uuid, timestamp, date, json, bytes |
cardinality | required, optional ou many |
classification | public, internal, confidential, restricted — l'habilitation dont un lecteur a besoin pour voir la valeur de la propriété (voir ci-dessous) |
piiFlags | Tags libres indiquant quel type de données personnelles la propriété contient. Donnez aussi une classification à une propriété PII : les tags seuls ne masquent rien |
Qui voit une propriété classifiée
Un lecteur autorisé à voir un objet ne voit pas forcément toutes ses propriétés.
À chaque lecture d'un objet stocké (transactional) — un objet, une liste, une
requête, une lecture à une date donnée, une vue de graphe ou de voisins, une
requête d'app — chaque propriété dont la classification dépasse l'habilitation
du lecteur est omise de la réponse, comme si elle n'était pas renseignée. Les
niveaux suivent l'ordre du treillis d'habilitation : public n'exige aucune
habilitation, internal le niveau de base d'un membre, confidential le niveau
suivant, restricted celui d'au-dessus. Une propriété sans classification est
visible par toute personne qui peut voir l'objet.
Un filtre sur une propriété que vous ne pouvez pas voir ne renvoie aucun objet : un filtre ne permet donc pas de deviner une valeur masquée. Une requête d'app qui nomme une telle propriété est refusée avec « App property unavailable ».
Une lecture à un revisionId épinglé ou via une branch ne montre jamais plus
que la révision publiée. Un objet est toujours lu dans son état actuel : une
propriété classifiée dans la révision publiée reste donc masquée même si la
révision ou la branche lue la déclare non classifiée, ou ne la déclare pas du
tout ; une propriété classifiée uniquement dans la révision lue est masquée
elle aussi.
Types de kinds d'Object Type
analytical— faits en masse, lecture intensive. Supporté par une liaisonsilver_table.transactional— entités modifiables (saisie manuelle, sortie de process-flow). Natif Postgres.binding_only— projection en lecture seule uniquement (ex.memex_page,extraction). Jamais directement modifié.
Liaisons — les sept kinds
| Kind | Relie |
|---|---|
silver_table | Une table analytique bronze/silver → un type d'objet |
memex_page | Un nœud de contenu KB Memex (correspondance slug/alias) → une instance d'objet |
process_flow_task | Une ligne process_instance_task → une instance d'objet typée |
process_flow_action | Un process_instance_task_action de actionType=entity_link → une instance de lien typée |
extraction | Le pipeline d'extraction analytics 5-WF → une instance d'objet |
stream | Une source de streaming (Kafka, webhook fanout) → une instance d'objet |
manual | Saisie directe dans le workbench → une instance d'objet |
defineBinding({
objectTypeSlug: 'Account',
kind: 'silver_table',
source: { table: 'silver_account' },
identitySpec: { keys: ['external_id'] },
columnMap: { name: 'legal_name', jurisdiction: 'country_code' },
})Flux de pistes corrélées
Pour une liaison stream avec source.backend: 'correlated_tracks', déclarez
uniquement backend, streamId et policyRevisionId dans la source publique.
Placez la politique de corrélation correspondante dans le tableau trackPolicies
à la racine de defineOntology. Chaque entrée associe un streamId à une
politique contenant sa revisionId, son marquage, les identifiants des sources
admises et leurs limites de priorité, qualité et ancienneté, ainsi que les
associations explicites d'identités ou de contacts. L'installateur doit avoir
l'habilitation requise pour tous les marquages. L'installation fixe la politique
en privé ; la liaison lisible et l'enregistrement de l'installation ne la contiennent pas.
Toute modification exige une migration explicite. L'installation seule ne
démarre pas de flux. Consultez l'exemple MIK en mer du Nord pour la politique à deux sources et la liaison.
Réduction en entités — identitySpec.entityColumns
Les sources riches en observations (trajectoires de position, relevés de
capteurs, événements de journal) comptent souvent des millions de lignes pour
quelques milliers d'objets réels. Déclarer entityColumns — un sous-ensemble
strict de identityColumns désignant l'entité persistante observée — indique
au graphe d'afficher un nœud par entité avec un décompte exact
d'observations, au lieu d'un nœud par ligne :
defineBinding({
objectTypeSlug: 'AircraftPosition',
kind: 'silver_table',
source: { table: 'adsb_positions' },
identitySpec: {
identityColumns: ['icao24', 'seenAt'], // one observation (position fix)
entityColumns: ['icao24'], // the persistent thing (airframe)
},
columnMap: { icao24: 'icao24', seenAt: 'seenAt', latitude: 'lat', longitude: 'lon' },
})Effets sur la page graphe :
- Un nœud par aéronef, étiqueté avec un décompte exact
N observationscalculé au moment de la lecture sous vos permissions — jamais un échantillon présenté comme la donnée. - Les jointures de relations sur des colonnes de niveau entité (p. ex.
icao24) se connectent au niveau entité. - Chaque réponse du graphe rapporte
affichés / totalpar type ; tout ce que le budget de lisibilité masque est représenté par un nœud agrégé portant le reste exact. - La vue d'ensemble affiche le tissu connecté : les instances participant à au moins une relation. Les instances sans relation calculée ne sont pas éparpillées sur le canevas en points isolés — elles sont repliées dans le nœud agrégé de leur type (avec le décompte exact) et restent consultables via l'expansion de type et la recherche. Une ontologie sans aucune relation calculable retombe sur un échantillon par type afin que la page reste navigable.
- Les nœuds agrégés s'étendent au clic : chaque clic amène une page bornée des membres du type sur le canevas (rattachés autour de l'agrégat) et le décompte restant décroît exactement ; quand il ne reste rien, l'agrégat disparaît.
- Étendre les voisins montre d'abord ce qui est connecté — les voisins groupés par relation et type cible avec leurs décomptes — pour ajouter les groupes pertinents au lieu d'inonder la disposition.
- La vue Carte (bascule en bas de la page graphe) affiche les types portant des coordonnées sous forme de cellules avec des décomptes exacts en vue, selon vos permissions. Le zoom subdivise les cellules ; quand le total en vue d'un type est assez petit, les instances réelles s'affichent à la place. Les listes de cellules tronquées sont étiquetées comme bornes inférieures — jamais présentées comme des nombres définitifs.
- Les relations s'affichent aussi sur la vue Carte : les types de liens de référence avec une règle de jointure explicite tracent des arcs entre cellules, chacun portant le nombre exact de paires liées entre ces deux zones (survolez pour le voir) ; les liens de proximité tracent de fins connecteurs entre les instances actuellement affichées, et la vue précise qu'ils ne couvrent que les instances affichées — zoomez pour matérialiser le reste. Chaque type de lien rapporte son total de paires en vue aux côtés des totaux par type.
Validation : entityColumns doit être un sous-ensemble strict non vide de
identityColumns (les ensembles égaux sont rejetés — omettez entityColumns
dans ce cas). L'installation d'une extension rejette les manifestes qui violent cette
règle.
Visualisation du graphe
Les extensions denses peuvent inclure des indications optionnelles de visualisation de graphe. Ces indications ne modifient pas le schéma d'ontologie ; elles indiquent aux surfaces de graphe comment restituer efficacement une vue d'instance.
Utilisez mode: 'compact' quand une extension a un type d'objet racine de haute valeur et de nombreuses liaisons enfant de haute cardinalité. Le point de terminaison du graphe ne projette que la liaison racine, affiche des nœuds agrégés par instance racine, et ignore la projection de lignes pour les liaisons d'objet et de lien réduites.
defineOntology({
id: 'acme-docs',
version: '1.0.0',
displayName: 'ACME Documents',
description: 'Document intelligence ontology',
maturity: 'preview',
author: { name: 'Acme Inc.' },
visualization: {
graph: {
mode: 'compact',
rootObjectType: 'DocumentCorpus',
rootLimit: 100,
rootGroup: {
slug: 'DocumentCorpusIndex',
label: 'All document corpora',
edgeLabel: 'contains corpus',
},
collapsedObjectTypes: ['Document', 'DocumentSection', 'ExtractedEntity'],
collapsedLinkTypes: ['ContainsDocument', 'MentionsEntity'],
aggregates: [
{
slug: 'Document',
label: 'Documents',
linkSlug: 'ContainsDocument',
edgeLabel: 'contains documents',
countObjectTypes: ['Document'],
},
{
slug: 'ExtractedEntity',
label: 'Extracted entities',
linkSlug: 'MentionsEntity',
edgeLabel: 'mentions entities',
countObjectTypes: ['ExtractedEntity'],
countLinkTypes: ['MentionsEntity'],
},
],
},
},
objectTypes: [],
linkTypes: [],
actionTypes: [],
identityRules: [],
bindings: [],
})Règles d'identité — déterministes + probabilistes
Les règles d'identité indiquent à la plateforme comment fusionner les instances. Le chemin déterministe est obligatoire ; le chemin probabiliste est optionnel pour les sources plus floues.
defineIdentityRule({
objectTypeSlug: 'Account',
deterministicKeys: ['external_id', 'name + jurisdiction'],
probabilistic: {
blockingKeys: ['name'],
signals: [
{ name: 'name_levenshtein', weight: 0.6, on: 'name' },
{ name: 'jurisdiction_match', weight: 0.4, on: 'jurisdiction' },
],
thresholds: { auto: 0.85, review: 0.70 },
},
})Types d'actions — mutations côté serveur contrôlées par la façade
Les types d'actions sont le seul chemin d'écriture typé. Chaque action s'exécute de la même façon :
- Validation Zod de l'entrée contre
inputZod. - Le service propriétaire de l'action résout la cible réelle et consomme une décision unique de la façade d'autorisation. OPA peut évaluer l'exigence Rego comme adaptateur privé ; le code de l'action ne l'appelle pas directement.
- Le descripteur d'effet secondaire décide de ce qui s'exécute — édition directe d'objet/lien, dispatch d'outbox, ou webhook.
- Une preuve d'autorisation durable est écrite avec la provenance des adaptateurs applicables, notamment
archiveHashetpolicy_execution_grant.grantIdlorsqu'ils sont présents.
sideEffectDescriptor.kind | Ce qui se passe |
|---|---|
object_edit | Upsert dans le store transactionnel du type d'objet |
link_edit | Upsert dans le store d'arêtes du type de lien |
outbox | Mise en file d'attente d'un événement d'outbox transactionnel |
webhook | Déclenchement d'un webhook configuré (URL dans config) |
Maturité
Choisissez-en une — affichée comme un badge dans le workbench :
verified— prêt pour la production, possédé et maintenupreview— fonctionnellement complet, peut évoluerexperimental— accès anticipé, s'attendre à des changements cassants
Validation
L'extension est validée contre OntologyManifestSchema (Zod) au moment du build et à nouveau côté serveur au moment du téléversement. Échecs courants :
| Erreur | Cause |
|---|---|
id must be lowercase kebab-case | L'id de l'extension doit correspondre à /^[a-z][a-z0-9-]*$/ |
Slug must start with a letter, alnum + _ - | Le slug d'objet/lien/action/propriété a échoué le regex |
Must be a valid semver string | version n'est pas semver |
linkType source/target slug not found | sourceTypes / targetTypes référencent un slug de type d'objet inconnu |
expected array to have exactly 1 item | Chaque extrémité concrète sourceTypes / targetTypes doit contenir exactement un slug de type d'objet ; utilisez "*" uniquement pour un lien de compatibilité générique |
Empaquetées via le pipeline d'archives d'extension (aujourd'hui)
Les extensions d'ontologie sont distribuées dans un conteneur d'extension : extension.json déclare l'ontologie sous provides (kind: "ontology"), et ontology/manifest.json contient le manifeste produit par defineOntology(...). Construisez l'archive avec bunx @scrydon/sdk-authoring extension build ; voir Extensions pour la structure du conteneur. Un CLI autonome scrydon-ontologies est prévu.
import { defineOntology } from '@scrydon/sdk-authoring/ontologies'
import myCrmOntology from './crm.ontology'
export default defineOntology({
id: 'acme-crm',
version: '1.0.0',
displayName: 'ACME CRM',
description: 'Salesforce-style CRM ontology',
maturity: 'preview',
author: { name: 'Acme Inc.' },
objectTypes: myCrmOntology.objectTypes,
linkTypes: myCrmOntology.linkTypes,
actionTypes: myCrmOntology.actionTypes,
bindings: myCrmOntology.bindings,
identityRules: myCrmOntology.identityRules,
})