Keyboard shortcuts

Press ← or → to navigate between chapters

Press ? to show this help

Press Esc to hide this help

Migration vers une configuration typée des plugins

La configuration d’instance typée constitue un changement rétro-incompatible pour tout plugin pré-1.0 qui lit la configuration de l’opérateur. Cette page est le document de migration destiné aux auteurs de plugins et aux opérateurs : ce qui cesse de fonctionner, pourquoi, et les étapes exactes pour corriger un paquet.

Le comportement décrit ici est vérifié par rapport à crates/zeroclaw-plugins/src/config.rs, crates/zeroclaw-plugins/src/instance.rs et au chemin d’admission dans crates/zeroclaw-plugins/src/host.rs.

Décision de publication

L’application de cette règle est fournie avec la fonctionnalité. Il n’y a aucun mécanisme de compatibilité, aucune période de grâce ni aucun indicateur de désactivation. Les plugins constituent une surface expérimentale pré-1.0 ; le projet accepte donc la rupture plutôt que de conserver définitivement un chemin de configuration plus faible : un repli non typé devrait fournir à un invité des valeurs que l’hôte ne peut ni typer, ni nommer, ni borner, ce qui constitue précisément la faille que cette fonctionnalité comble.

Les paquets qui ne sont pas migrés cessent d’être détectés. Rien n’est rétrogradé silencieusement et aucune configuration partielle n’atteint le code invité.

Ce qui casse

Trois choses, indépendamment :

  1. Un manifeste qui demande config_read sans config_schema n’est plus détecté ni installé. Les deux sont liés par une condition nécessaire et suffisante : un schéma sans l’autorisation est tout aussi invalide.
  2. Les entrées de configuration indexées par le nom du package ou de la liaison ne sont plus consultées. Les valeurs des opérateurs résident désormais sous une clé d’instance complète dérivée du package, de la capacité et de la liaison.
  3. Les invités reçoivent du JSON typé, et non une map de chaînes. Un invité qui analysait lui-même les chaînes reçoit désormais de vrais booléens, nombres, tableaux et objets.

Pourquoi l’hôte a besoin d’un schéma

Les valeurs de l’opérateur sont stockées sous forme d’une map de chaînes marquées comme secrètes et chiffrées au repos, tandis que le composant invité est du code tiers non fiable. Sans contrat déclaré, l’hôte ne peut pas répondre aux deux questions auxquelles il doit répondre avant le démarrage du composant invité : quelles clés ce package est-il autorisé à recevoir, et de quel type est chaque valeur. Les mondes WIT sont fixes et partagés par tous les plugins, de sorte que les types de configuration propres à chaque package ne peuvent pas résider dans l’ABI. Le manifeste est le seul endroit où le contrat peut être déclaré, et l’association de additionalProperties = false avec une map properties explicite est ce qui donne à l’autorisation config_read une portée énumérable.

Étapes pour les auteurs

1. Déclarer le schéma

Ajoutez un objet Draft 2020-12 fermé couvrant exactement les clés lues par votre plugin. Chaque propriété de premier niveau doit se résoudre en un seul type explicite : string, boolean, integer, number, array ou object.

name = "my-plugin"
version = "0.2.0"
wasm_path = "my_plugin.wasm"
capabilities = ["channel"]
permissions = ["config_read"]

[config_schema]
"$schema" = "https://json-schema.org/draft/2020-12/schema"
type = "object"
required = ["bot_token"]
additionalProperties = false

[config_schema.properties.bot_token]
type = "string"
minLength = 1

[config_schema.properties.poll_interval_secs]
type = "integer"
minimum = 1

[config_schema.properties.allowed_chats]
type = "array"

L’hôte applique ces limites au schéma lui-même : 64 KiB sérialisé, au plus 32 niveaux d’imbrication, pas plus de 256 propriétés racines, aucun $id, et les cibles $ref doivent être des JSON Pointers locaux. Les références distantes sont rejetées, de sorte qu’un schéma ne déclenche jamais de requête réseau. Un schéma qui dépasse l’une de ces limites est refusé avec un comportement fail-closed (échec sécurisé) lors de l’installation ou de l’admission du paquet, avant la compilation du validateur.

Les mots-clés à clés dynamiques ne font pas partie de ce dialecte : patternProperties, propertyNames et unevaluatedProperties sont rejetés à la racine, car l’hôte ne matérialise une valeur que pour une clé nommée dans la map properties de la racine ; les clés admises par un motif n’atteindraient donc jamais votre plugin. Les schémas de propriétés imbriqués ne sont pas concernés.

pattern utilise le dialecte d’expressions régulières en temps linéaire

L’hôte résout la configuration en recompilant et en revalidant votre schéma à chaque appel, et ce travail s’exécute sur l’hôte, et non dans le budget de fuel de votre composant. Ainsi, pattern est limité aux expressions régulières dont l’hôte peut prédire le coût :

  • Les références arrière et les assertions de contexte sont refusées. (\w+)\s\1, (?=...), (?<=...) et autres nécessitent un moteur avec retour arrière. Les motifs sont plutôt compilés dans le dialecte regex à temps linéaire, qui effectue la correspondance en un temps proportionnel à la longueur de la valeur, quelle que soit la manière dont le motif est écrit.
  • Un seul motif ne peut pas être compilé en un programme de plus de 256 Kio. Cela pose problème avec des nombres élevés de répétitions : ^[\s\S]{0,200}$ convient, ^[\s\S]{0,1000}$ ne convient pas. Utilisez maxLength pour les limites de longueur ; sa vérification est gratuite et exprime clairement votre intention.

Les deux refus ont lieu au moment de l’installation avec une erreur InvalidManifest indiquant le schéma, de sorte qu’un plugin dont l’hôte ne peut pas borner le pattern ne s’exécute jamais. Les patterns structurels se comportent comme prévu : les slugs, les UUID, les adresses e-mail, les URL et les textes libres courts et bornés se compilent tous.

2. Faites correspondre les encodages de valeurs

Le stockage de l’opérateur reste un mappage de chaînes. Le schéma indique à l’hôte comment lire chaque chaîne stockée :

Type déclaréCe que l’opérateur stockeCe que reçoit l’invité
stringsecret-value"secret-value"
booleantruetrue
integer44
number0.50.5
array["a","b"]["a","b"]
object{"k":"v"}{"k":"v"}

Tout ce qui ne peut pas être analysé comme le type déclaré est rejeté avant l’exécution de votre code.

3. Déterminez ce qui est obligatoire ou facultatif pour chaque clé

Les autorisations effectives sont vérifiées séparément des demandes du manifeste. Lorsque config_read est demandé mais n’est pas accordé, l’hôte valide un objet vide par rapport à votre schéma :

  • Un schéma dont tous les champs sont facultatifs reçoit {} : attribuez donc à chaque champ une valeur par défaut côté invité.
  • Un champ required échoue en mode fermé, ce qui est souhaitable pour les identifiants. Un canal qui ne peut pas s’authentifier doit refuser de démarrer plutôt que de fonctionner avec une configuration partielle.

4. Désérialiser du JSON typé dans l’invité

Remplacez l’analyse de chaînes par une seule désérialisation de l’objet injecté. Les plugins d’outils lisent la clé réservée __config, que l’hôte fusionne avec les arguments de l’appel après avoir supprimé toute valeur de ce nom fournie par le modèle.

5. Reconstruire et re-signer

config_schema est couvert par la signature du manifeste ; un paquet signé doit donc être signé à nouveau après son ajout. Consultez Distribution des plugins pour connaître le processus de signature.

Étapes de l’opérateur

Les blocs [[plugins.entries]] existants portant le nom d’un paquet ou d’une liaison ne sont pas lus. Le chemin de migration disponible dépend de la capacité du plugin.

Instances d’outils

Les commandes Install et info peuvent déduire une instance d’outil à partir de la liaison d’outil par défaut du paquet. Pour déplacer les valeurs d’outil vers la nouvelle clé :

  1. Exécutez zeroclaw plugin info <package> pour afficher la clé d’instance complète, qui ressemble à zpi1_....
  2. Renommez le name de l’entrée existante avec cette clé, ou réinstallez le plugin pour initialiser l’entrée, puis définissez les valeurs avec zeroclaw config set plugins.entries.<instance-key>.config.<key>.
  3. Enregistrez la configuration. Les valeurs restent chiffrées au repos.

La clé est un encodage versionné et réversible du paquet, de la capacité et de la liaison, c’est pourquoi deux paquets peuvent tous deux utiliser une liaison nommée main sans partager leurs identifiants. Les nouvelles installations initialisent et affichent automatiquement cette clé d’outil.

Instances de canal

Une clé de canal inclut l’alias de canal configuré. Le package seul ne nomme aucun alias ; une clé de canal n’est donc dérivée qu’une fois un alias lié, et aucune commande n’invente de substitut au niveau du package. La construction de canaux tenant compte des alias et la résolution de la configuration à l’exécution ont été intégrées dans zeroclaw#10146 : un démon construit une instance de canal explicitement déclarée et résout sa configuration typée à partir de zpi1(package, channel, alias), en se basant sur l’alias réellement configuré.

Lier l’alias dérive, affiche et initialise cette clé. Pour y placer les valeurs d’une instance de canal :

  1. Exécutez zeroclaw plugin bind <package> --channel-alias <alias>. Si le manifeste déclare des destinations, que le paquet embarque un transport capable de les atteindre (http_client, websocket_client ou socket_client) et que l’instance n’a pas encore d’entrée, ajoutez --egress declared pour leur accorder l’accès ou --egress none pour lier sans accès réseau ; sans l’un ou l’autre, la commande refuse et les énumère. Elle crée la liaison [channels.plugin.<alias>], ou conserve celle que vous avez déjà écrite, et initialise l’entrée [[plugins.entries]] de l’instance. zeroclaw plugin install <source> --channel-alias <alias> fait de même pendant l’installation.
  2. Définissez les valeurs avec zeroclaw config set plugins.entries.<instance-key>.config.<key>. Le rapport de disponibilité du bind répertorie chaque clé required comme définie ou manquante, avec la commande exacte pour une clé manquante, et zeroclaw plugin info <package> affiche la clé d’instance ainsi que le même rapport par la suite. Les valeurs restent chiffrées au repos.

Une ligne déjà présente sous la clé d’instance est laissée exactement telle quelle, de sorte que les valeurs que vous y avez saisies manuellement survivent à la liaison. Aucun nom hérité n’est consulté pour une instance de canal : la construction des canaux est postérieure à la clé d’instance complète, si bien qu’aucun runtime n’a jamais lu la configuration d’un canal à partir d’une ligne nommée d’après son paquet ou son alias.

Diagnostiquer un rejet

MessageCause
demande config_read, mais ne déclare aucun config_schemaétape 1 non terminée
déclare config_schema sans demander config_readsupprimez le schéma ou ajoutez l’autorisation
config_schema doit définir additionalProperties = falsel’objet racine est ouvert
config_schema ne doit pas déclarer <keyword> à la racinela racine utilise un mot-clé de clé dynamique ; nommez plutôt chaque clé dans properties
la propriété utilise un type non pris en chargeune propriété n’a aucun type explicitement pris en charge, ou un $ref local non résoluble
config contient une propriété absente de config_schemaune clé d’opérateur n’est pas déclarée, souvent à cause d’une faute de frappe
la propriété de configuration doit être un entier JSONla chaîne stockée ne peut pas être analysée comme le type déclaré
config ne respecte pas config_schema à <path>une contrainte telle que minimum ou required a échoué

Packages de première partie

Chaque paquet publié dans zeroclaw-labs/zeroclaw-plugins demande config_read, et aucun ne déclarait config_schema au moment de cette intégration ; ils ont donc tous besoin des étapes 1 et 5. La migration est suivie dans ce dépôt plutôt qu’ici, puisque les paquets ont des versions indépendantes de l’hôte. Les deux types peuvent dès maintenant effectuer l’étape de la clé opérateur : les paquets d’outils via l’installation et plugin info, et les paquets de canal seul via zeroclaw plugin bind, comme décrit ci-dessus.

Plugins de mémoire

Les plugins de mémoire ne disposent pas encore d’export de configuration et ne doivent pas demander config_read tant que cette ABI n’existe pas.