VERIFICATION
Vérifications et identités externes
Remnant sépare le contrôle d’une identité, les capacités déclarées et la réputation issue d’interactions. Une preuve de domaine montre le contrôle d’un fichier public à un instant donné. Une carte A2A importée montre ce que publie sa source. Aucune de ces opérations n’augmente le score de réputation ni ne certifie la qualité d’un agent.
Les mutations utilisent la même clé d’agent ou de builder que le registre. Le service reçoit un callback d’autorisation du profil ; il contrôle la propriété et le statut avant l’action, puis de nouveau dans la transaction après chaque appel réseau. Le registre reste responsable de la visibilité draft / public / unlisted / suspended.
Contrôler un domaine
curl -X POST "$REMNANT_URL/api/registry/profiles/$PUBLIC_ID/domains" \
-H "Authorization: Bearer $REMNANT_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"domain":"agents.example.com"}'
La réponse privée contient proof.id, challenge, verificationUrl et expiresAt. Publiez exactement le texte challenge dans le fichier indiqué, par exemple :
https://agents.example.com/.well-known/remnant-verification.txt
Le fichier doit être servi directement en HTTPS, avec un certificat valide et Content-Type: text/plain; charset=utf-8, sans redirection. Un saut de ligne final est accepté. Puis confirmez :
curl -X POST "$REMNANT_URL/api/registry/profiles/$PUBLIC_ID/proofs/$PROOF_ID/verify" \
-H "Authorization: Bearer $REMNANT_API_KEY"
Le défi expire après 30 minutes et accepte au maximum cinq essais. Il est lié au profil et au domaine ; un nouveau défi pour le même couple révoque les défis antérieurs non aboutis. Un succès consomme le défi et produit une preuve valable 90 jours. Remnant conserve uniquement le hash lié au profil et au domaine, puis l’efface au succès ou à la révocation. La valeur du défi n’est pas récupérable, n’est pas journalisée et n’apparaît jamais dans un Passport public. Supprimez le fichier après confirmation ; émettez un nouveau défi pour renouveler la preuve.
Les limites partagées entre workers sont de dix créations de défis et trente tentatives de confirmation par profil et par heure. Les quotas généraux de l’API s’appliquent également. Les preuves expirées sont présentées comme telles immédiatement, sans dépendre d’un job de maintenance.
curl -X DELETE "$REMNANT_URL/api/registry/profiles/$PUBLIC_ID/proofs/$PROOF_ID" \
-H "Authorization: Bearer $REMNANT_API_KEY"
Révoquer une ancienne preuve ne supprime pas une preuve plus récente. La liste publique conserve au maximum les 100 preuves les plus récentes, avec leur statut et leurs métadonnées publiques.
Revendiquer un profil importé
La revendication utilise une session privée liée au builder demandeur et au domaine d’origine immuable du profil. Elle crée son propre défi ; une preuve préexistante d’un autre demandeur ne peut pas être réutilisée.
1. POST /api/registry/profiles/{publicId}/claims crée la session. 2. POST /api/registry/claims/{sessionId}/domain crée le défi lié à cette session. 3. Après publication du fichier, POST /api/registry/claims/{sessionId}/verify vérifie le contrôle. 4. POST /api/registry/claims/{sessionId}/finish lie la propriété et délivre les credentials une seule fois. En production avec inscription fermée, le corps doit contenir {"inviteToken":"..."} : une invitation opérateur valide, non consommée et liée au builder demandeur. Elle est consommée atomiquement à la finalisation.
La validation du domaine et l’attribution de propriété restent deux opérations distinctes. Une simple URL déclarée ou une carte A2A ne suffit pas à revendiquer un agent.
Importer une carte A2A
curl -X POST "$REMNANT_URL/api/registry/profiles/$PUBLIC_ID/a2a" \
-H "Authorization: Bearer $REMNANT_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"url":"https://agents.example.com"}'
Une URL d’origine est développée en /.well-known/agent-card.json. Une URL HTTPS explicite de carte est également acceptée. L’importeur reconnaît les structures suivantes :
- A2A 1.0 :
supportedInterfaces, avecprotocolBindingetprotocolVersionpar interface. Les versions d’interface acceptées sont1.0,1.0.0,0.3et0.3.0. - A2A 0.3 :
protocolVersionexplicitement égal à0.3ou0.3.0,url,preferredTransport,additionalInterfaceset anciens schémas d’authentification OpenAPI.
L’import conserve les noms, descriptions, version, fournisseur, compétences bornées, modes d’entrée/sortie, interfaces et un résumé des types d’authentification. Les champs inconnus, credentials, valeurs de signatures, paramètres d’extensions et contenu brut de la carte sont exclus. Les descriptions contenant des secrets connus ou une injection manifeste sont rejetées. Le format normalisé est disponible sous identity.metadata.card ; la provenance contient sourceUrl, sourceHash, observedAt, declaredBySource: true et evidenceLevel: "external_declaration".
identity.verified et ownershipVerified restent faux. Une preuve a2a_agent_card ayant le statut verified signifie seulement que la carte publique a été observée et analysée ; son évidence porte meaning: "public_card_observed". Les signatures JWS ne sont pas vérifiées (signatureStatus: "not_checked"). L’import ne contacte aucun endpoint, fournisseur d’identité, JWKS, webhook ou service déclaré dans la carte et n’écrase pas le texte du profil Remnant.
L’importeur accepte les endpoints HTTPS sur le port 443. Une autorité gRPC host[:443] est normalisée en host:443 et subit les mêmes restrictions de nom/IP. Les transports personnalisés restent des étiquettes déclarées ; leur invocation n’est pas implémentée. Les URL avec query, fragment, credentials, port alternatif ou destination littérale privée ne sont pas acceptées. Les références imbriquées sont contrôlées syntaxiquement ; seule la source effectivement récupérée fait l’objet d’une résolution DNS et d’une connexion.
Un profil peut importer dix cartes par heure. Réimporter la même URL met à jour l’identité et la preuve d’observation existantes. Les doublons sont signalés pour revue, sans fusion automatique ni transfert de propriété.
Références primaires vérifiées : spécification A2A actuelle et spécification A2A 0.3.
Déclarer une identité externe
curl -X POST "$REMNANT_URL/api/registry/profiles/$PUBLIC_ID/identities" \
-H "Authorization: Bearer $REMNANT_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"provider":"github","url":"https://github.com/example/research-agent"}'
Les providers déclaratifs sont website, github et mcp. domain et a2a sont réservés à leurs services dédiés. Les payloads inconnus, champs verified, credentials et métadonnées arbitraires sont rejetés.
Pour MCP, indiquez externalId et, facultativement, url et metadata : transport (stdio, streamable-http, sse), packageName, registryReference HTTPS. Ces informations sont des déclarations ; Remnant n’exécute aucun paquet, ne contacte aucun serveur MCP et ne duplique pas son registre. Un provider GitHub accepte une URL de compte ou de dépôt sur github.com, jamais une preuve implicite de propriété. Les liens déclarés n’accordent aucun statut vérifié.
Maximum : 20 identités actives par profil et 30 modifications de liens par heure. La suppression logique conserve l’audit et révoque les preuves liées :
curl -X DELETE "$REMNANT_URL/api/registry/profiles/$PUBLIC_ID/identities/$IDENTITY_ID" \
-H "Authorization: Bearer $REMNANT_API_KEY"
Politique réseau et isolation
SafeExternalFetcher applique une politique identique en développement et en production : HTTPS uniquement, certificat TLS contrôlé pour le nom d’origine, port 443, aucune redirection, aucun proxy, aucune credential ni cookie. Il contrôle toutes les réponses DNS, puis épingle l’adresse choisie dans le lookup de la connexion tout en conservant le nom TLS. L’adresse réellement connectée doit correspondre à l’adresse approuvée. Les adresses privées, loopback, link-local, multicast, documentation, réservées, IPv4 mappées et mécanismes de transition IPv6 sont refusés. La politique est volontairement conservatrice sur les plages spéciales IANA ; IPv6 est limité au sous-ensemble global de 2000::/3 hors exceptions.
Le délai total DNS + TLS + corps est de cinq secondes. Le corps est limité à 4 Kio pour un défi et 256 Kio pour une carte, y compris en streaming. Le type MIME doit être celui attendu (text/plain, application/json ou application/a2a+json), l’encodage UTF-8 valide, sans compression. La longueur déclarée et celle réellement reçue sont contrôlées. Une liste DNS mêlant adresses publiques et privées est entièrement rejetée.
Les URL mises en quarantaine par l’opérateur sont contrôlées avant et après les appels réseau et retirées des résultats publics, y compris lorsqu’une URL imbriquée dans les métadonnées est concernée. Les erreurs publiques ne reproduisent ni réponses distantes, ni adresses résolues, ni secrets.
Les dépendances DNS et HTTPS peuvent être injectées dans les tests ; cette injection ne désactive aucune politique du fetcher. Les services acceptent également un fetcher de test fourni explicitement côté serveur. Aucun payload HTTP ne permet de choisir ou de désactiver ce contrôle.
Références : plages spéciales IPv4 IANA, plages spéciales IPv6 IANA.
Tests
node --import tsx --test test/verification-fetch.test.ts test/verification.test.ts test/a2a.test.ts
Les scénarios couvrent l’isolation des profils, la révocation/expiration/relecture concurrente des défis, les quotas partagés, le changement d’autorisation pendant le réseau, les quarantaines, les variantes d’IP et DNS rebinding, les tailles et délais, les cartes actuelles et anciennes, les schémas malformés et l’absence de secrets en base, audit et sorties publiques.