gcheck-core 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,59 @@
1
+ """Moteur de checklists de l'écosystème Duniter/Ğ1, avec les checklists officielles embarquées.
2
+
3
+ Même moteur et mêmes checklists que le crate Rust `gcheck-core` et que le paquet npm du même nom :
4
+ des cas de test métier communs garantissent que les trois implémentations ne divergent pas.
5
+ """
6
+
7
+ from ._version import __version__
8
+ from .checklists import (
9
+ MEMBER_CERTIFICATION,
10
+ SMITH_ASPIRANT,
11
+ SMITH_CERTIFIER,
12
+ Catalog,
13
+ Checklist,
14
+ all_checklists,
15
+ checklist_from_file,
16
+ external_checklist,
17
+ get_checklist,
18
+ )
19
+ from .engine import GcheckEngine
20
+ from .evaluator import evaluate
21
+ from .hash import checklist_hash
22
+ from .parser import parse
23
+ from .resolver import resolve
24
+ from .selector import select
25
+ from .types import (
26
+ AppDefaults,
27
+ GcheckError,
28
+ InputSchema,
29
+ Issue,
30
+ QuizQuestion,
31
+ QuizSchema,
32
+ Verdict,
33
+ )
34
+
35
+ __all__ = [
36
+ "AppDefaults",
37
+ "__version__",
38
+ "Catalog",
39
+ "Checklist",
40
+ "GcheckEngine",
41
+ "GcheckError",
42
+ "InputSchema",
43
+ "Issue",
44
+ "MEMBER_CERTIFICATION",
45
+ "QuizQuestion",
46
+ "QuizSchema",
47
+ "SMITH_ASPIRANT",
48
+ "SMITH_CERTIFIER",
49
+ "Verdict",
50
+ "all_checklists",
51
+ "checklist_from_file",
52
+ "checklist_hash",
53
+ "evaluate",
54
+ "external_checklist",
55
+ "get_checklist",
56
+ "parse",
57
+ "resolve",
58
+ "select",
59
+ ]
@@ -0,0 +1,61 @@
1
+ # Généré par scripts/embed-checklists.ts — ne pas modifier à la main.
2
+ # Checklists officielles synchronisées depuis la source de vérité (voir upstream.json).
3
+
4
+ from typing import Any
5
+
6
+ EMBEDDED: list[dict[str, Any]] = [
7
+ {
8
+ "id": "smith-aspirant",
9
+ "lang": "en",
10
+ "status": "validated",
11
+ "content": "{\n\t\"title\": \"Ğ1 Aspiring Smith Commitment\",\n\t\"preamble\": \"Ensuring as a community that the smiths who manage the technical ecosystem do so with: competence, rigor, security and responsiveness.\\n\",\n\t\"lang\": \"en\",\n\t\"fileFormatVersion\": \"gcheck-1\",\n\t\"checklistVersion\": \"smith-v2.0.0-check-v1.0.0\",\n\t\"sorting\": \"shuffle\",\n\t\"defaults\": {\n\t\t\"expected\": true,\n\t\t\"severity\": \"blocking\"\n\t},\n\t\"chapeauError\": \"Warning, you did not answer the question correctly. We invite you to re-read all the smith commitments and discuss them with other smiths before attempting to certify again.\",\n\t\"categories\": {\n\t\t\"securityCompliance\": {\n\t\t\t\"label\": \"Security & compliance\",\n\t\t\t\"questions\": {\n\t\t\t\t\"motivation\": {\n\t\t\t\t\t\"text\": \"I have clarified what motivates me to become a smith, I take responsibility for my reasons and will share them with anyone who asks.\"\n\t\t\t\t},\n\t\t\t\t\"securityVigilance\": {\n\t\t\t\t\t\"text\": \"I actively monitor and maintain my system and network security practices to meet the current Duniter challenges.\"\n\t\t\t\t},\n\t\t\t\t\"forumNotifications\": {\n\t\t\t\t\t\"text\": \"I have enabled notifications on forum.duniter.org to be alerted of personal and smith-related requests.\"\n\t\t\t\t},\n\t\t\t\t\"randomRecoveryPhrase\": {\n\t\t\t\t\t\"text\": \"I confirm that my recovery phrase was randomly generated and that I use it only for my member account.\"\n\t\t\t\t},\n\t\t\t\t\"separateTransactionAccount\": {\n\t\t\t\t\t\"text\": \"I use a separate account for my daily transactions. (I only authenticate on my member account for operations requiring non-delegable rights, and from my own equipment).\"\n\t\t\t\t},\n\t\t\t\t\"multiPhysicalBackup\": {\n\t\t\t\t\t\"text\": \"I have stored my recovery phrase on multiple physical media (digital or otherwise), recoverable independently. In case of theft, fire, failure, forgetting... I will have access to at least one copy to regain control.\"\n\t\t\t\t},\n\t\t\t\t\"managedNode\": {\n\t\t\t\t\t\"text\": \"I already manage an up-to-date node (software version), correctly synchronized (blockchain state) and reachable (network peer).\"\n\t\t\t\t},\n\t\t\t\t\"unsafeApiProtected\": {\n\t\t\t\t\t\"text\": \"I have ensured that the unsafe API of my smith node is not publicly exposed.\"\n\t\t\t\t},\n\t\t\t\t\"technicalChoicesShared\": {\n\t\t\t\t\t\"text\": \"I provide my technical choices upon request from another smith, and if I change my approach, I notify the smiths who certified me (to be alerted of any associated issues).\"\n\t\t\t\t},\n\t\t\t\t\"declareOfflineIfUnsure\": {\n\t\t\t\t\t\"text\": \"I declare myself offline if in doubt about the security or service continuity of my node.\"\n\t\t\t\t},\n\t\t\t\t\"respond24h\": {\n\t\t\t\t\t\"text\": \"I commit to responding to smiths within 24 hours when I am declared online. Otherwise, I declare myself offline.\"\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t\"contact\": {\n\t\t\t\"label\": \"Contact\",\n\t\t\t\"questions\": {\n\t\t\t\t\"threeSmithsReachable\": {\n\t\t\t\t\t\"text\": \"I can efficiently and quickly reach 3 of the smiths who intend to certify me (via at least 2 channels: phone/SMS + email, XMPP, Matrix...).\"\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t\"knowledge\": {\n\t\t\t\"label\": \"Knowledge\",\n\t\t\t\"questions\": {\n\t\t\t\t\"acceptedCommitments\": {\n\t\t\t\t\t\"text\": \"I have read and agree to respect all the smith commitments and I understand their implications and reasons for being.\"\n\t\t\t\t},\n\t\t\t\t\"smithTdcRules\": {\n\t\t\t\t\t\"text\": \"I am aware of the rules and timeframes associated with the operation of the smith Web of Trust.\"\n\t\t\t\t},\n\t\t\t\t\"blockchainConsensus\": {\n\t\t\t\t\t\"text\": \"I have a good understanding of how a blockchain network works, particularly the security challenges related to the consensus mechanisms of the Duniter blockchain.\"\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t\"trap\": {\n\t\t\t\"label\": \"Trap\",\n\t\t\t\"expected\": false,\n\t\t\t\"questions\": {\n\t\t\t\t\"pressureForCertification\": {\n\t\t\t\t\t\"text\": \"I insist, harass or pressure in any other way to be certified.\"\n\t\t\t\t},\n\t\t\t\t\"seekingGlory\": {\n\t\t\t\t\t\"text\": \"I want to be a smith for glory and power.\"\n\t\t\t\t},\n\t\t\t\t\"seekingToHarm\": {\n\t\t\t\t\t\"text\": \"I seek to harm the Ğ1 ecosystem by infiltrating the smiths.\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n",
12
+ },
13
+ {
14
+ "id": "smith-aspirant",
15
+ "lang": "es",
16
+ "status": "validated",
17
+ "content": "{\n\t\"title\": \"Compromiso del aspirante a forjador Ğ1\",\n\t\"preamble\": \"Asegurarnos, como comunidad, de que los forjadores que gestionan el ecosistema técnico lo hacen con: competencia, rigor, seguridad y capacidad de respuesta.\\n\",\n\t\"lang\": \"es\",\n\t\"fileFormatVersion\": \"gcheck-1\",\n\t\"checklistVersion\": \"smith-v2.0.0-check-v1.0.0\",\n\t\"sorting\": \"shuffle\",\n\t\"defaults\": {\n\t\t\"expected\": true,\n\t\t\"severity\": \"blocking\"\n\t},\n\t\"chapeauError\": \"Atención, no ha respondido correctamente a la pregunta. Le invitamos a releer todos los compromisos de los forjadores y a hablar de ello con otros forjadores antes de intentar certificar de nuevo.\",\n\t\"categories\": {\n\t\t\"securityCompliance\": {\n\t\t\t\"label\": \"Seguridad y conformidad\",\n\t\t\t\"questions\": {\n\t\t\t\t\"motivation\": {\n\t\t\t\t\t\"text\": \"He aclarado qué me motiva a ser forjador, asumo mis razones y las indicaré a quien me las solicite.\"\n\t\t\t\t},\n\t\t\t\t\"securityVigilance\": {\n\t\t\t\t\t\"text\": \"Mantengo una vigilancia activa para que mis prácticas de seguridad de sistemas y redes estén a la altura de los retos actuales de Duniter.\"\n\t\t\t\t},\n\t\t\t\t\"forumNotifications\": {\n\t\t\t\t\t\"text\": \"He activado las notificaciones en forum.duniter.org para ser alertado de solicitudes personales y de smiths.\"\n\t\t\t\t},\n\t\t\t\t\"randomRecoveryPhrase\": {\n\t\t\t\t\t\"text\": \"Confirmo que mi frase de recuperación fue generada aleatoriamente y que la utilizo únicamente para mi cuenta de miembro.\"\n\t\t\t\t},\n\t\t\t\t\"separateTransactionAccount\": {\n\t\t\t\t\t\"text\": \"Uso otra cuenta para mis transacciones habituales. (Solo me autentico en mi cuenta de miembro para operaciones que requieren derechos no delegables, y desde mi propio equipo).\"\n\t\t\t\t},\n\t\t\t\t\"multiPhysicalBackup\": {\n\t\t\t\t\t\"text\": \"He almacenado mi frase de recuperación en varios soportes físicos (digitales o no), recuperables de forma independiente. En caso de robo, incendio, fallo, olvido… tendré acceso al menos a una de las copias para retomar el control.\"\n\t\t\t\t},\n\t\t\t\t\"managedNode\": {\n\t\t\t\t\t\"text\": \"Ya gestiono un nodo actualizado (versión de software), correctamente sincronizado (estado de la blockchain) y accesible (peer de red).\"\n\t\t\t\t},\n\t\t\t\t\"unsafeApiProtected\": {\n\t\t\t\t\t\"text\": \"Me he asegurado de no exponer públicamente la API unsafe de mi nodo smith.\"\n\t\t\t\t},\n\t\t\t\t\"technicalChoicesShared\": {\n\t\t\t\t\t\"text\": \"Proporciono, a solicitud de otro forjador, mis elecciones técnicas y, si cambio de enfoque, informo a los forjadores que me certificaron (para que estén alertados de posibles problemas asociados).\"\n\t\t\t\t},\n\t\t\t\t\"declareOfflineIfUnsure\": {\n\t\t\t\t\t\"text\": \"Me declaro offline en caso de duda sobre la seguridad o la continuidad del servicio de mi nodo.\"\n\t\t\t\t},\n\t\t\t\t\"respond24h\": {\n\t\t\t\t\t\"text\": \"Me comprometo a responder en menos de 24 h a los forjadores cuando estoy declarado online. En caso contrario, me declaro offline.\"\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t\"contact\": {\n\t\t\t\"label\": \"Contacto\",\n\t\t\t\"questions\": {\n\t\t\t\t\"threeSmithsReachable\": {\n\t\t\t\t\t\"text\": \"Sé contactar eficaz y rápidamente con 3 de los forjadores que pretenden certificarme (por al menos 2 canales: teléfono/SMS + email, XMPP, Matrix…).\"\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t\"knowledge\": {\n\t\t\t\"label\": \"Conocimiento\",\n\t\t\t\"questions\": {\n\t\t\t\t\"acceptedCommitments\": {\n\t\t\t\t\t\"text\": \"He leído y acepto respetar todos los compromisos de los forjadores y comprendo sus implicaciones y razón de ser.\"\n\t\t\t\t},\n\t\t\t\t\"smithTdcRules\": {\n\t\t\t\t\t\"text\": \"He tomado conocimiento de las reglas y plazos asociados al funcionamiento de la TdC de forjadores.\"\n\t\t\t\t},\n\t\t\t\t\"blockchainConsensus\": {\n\t\t\t\t\t\"text\": \"He comprendido bien el funcionamiento de una red blockchain, en particular los retos de seguridad ligados a los mecanismos de consenso de la blockchain Duniter.\"\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t\"trap\": {\n\t\t\t\"label\": \"Trampa\",\n\t\t\t\"expected\": false,\n\t\t\t\"questions\": {\n\t\t\t\t\"pressureForCertification\": {\n\t\t\t\t\t\"text\": \"Insisto, acoso o ejerzo presión de cualquier otra forma para ser certificado.\"\n\t\t\t\t},\n\t\t\t\t\"seekingGlory\": {\n\t\t\t\t\t\"text\": \"Quiero ser forjador por gloria o poder.\"\n\t\t\t\t},\n\t\t\t\t\"seekingToHarm\": {\n\t\t\t\t\t\"text\": \"Busco dañar el ecosistema Ǧ1 infiltrándome entre los forjadores.\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n",
18
+ },
19
+ {
20
+ "id": "smith-aspirant",
21
+ "lang": "fr",
22
+ "status": "validated",
23
+ "content": "{\n\t\"title\": \"Engagement de l'aspirant forgeron Ğ1\",\n\t\"preamble\": \"S'assurer en tant que communauté que les forgerons qui gèrent l'écosystème technique le font avec : compétence, rigueur, sécurité et réactivité.\\n\",\n\t\"lang\": \"fr\",\n\t\"fileFormatVersion\": \"gcheck-1\",\n\t\"checklistVersion\": \"smith-v2.0.0-check-v1.0.0\",\n\t\"sorting\": \"shuffle\",\n\t\"defaults\": {\n\t\t\"expected\": true,\n\t\t\"severity\": \"blocking\"\n\t},\n\t\"chapeauError\": \"Attention vous n'avez pas répondu correctement à la question. Nous vous invitons à relire l'ensemble des engagements forgerons et à en discuter avec d'autres forgerons avant de retenter de certifier.\",\n\t\"categories\": {\n\t\t\"securityCompliance\": {\n\t\t\t\"label\": \"Sécurité & conformité\",\n\t\t\t\"questions\": {\n\t\t\t\t\"motivation\": {\n\t\t\t\t\t\"text\": \"J'ai clarifié ce qui me motive à devenir forgeron, j'en assume les raisons et les indiquerai à qui me les demande.\"\n\t\t\t\t},\n\t\t\t\t\"securityVigilance\": {\n\t\t\t\t\t\"text\": \"Je fais de la veille pour maintenir mes pratiques de sécurité système et réseau à la hauteur des enjeux Duniter actuels.\"\n\t\t\t\t},\n\t\t\t\t\"forumNotifications\": {\n\t\t\t\t\t\"text\": \"J'ai activé les notifications sur forum.duniter.org pour être alerté des sollicitations perso et smiths.\"\n\t\t\t\t},\n\t\t\t\t\"randomRecoveryPhrase\": {\n\t\t\t\t\t\"text\": \"Je confirme que ma phrase de récupération a été générée aléatoirement et que je l'utilise uniquement pour mon compte membre.\"\n\t\t\t\t},\n\t\t\t\t\"separateTransactionAccount\": {\n\t\t\t\t\t\"text\": \"J'utilise un autre compte pour mes transactions courantes. (Je m'authentifie sur mon compte membre seulement pour les opérations nécessitant les droits non délégables, et depuis mon propre matériel).\"\n\t\t\t\t},\n\t\t\t\t\"multiPhysicalBackup\": {\n\t\t\t\t\t\"text\": \"J'ai stocké ma phrase de récupération sur plusieurs supports physiques (numérique ou non), récupérables indépendamment. En cas de vol, incendie, panne, oubli... J'aurai accès à au moins une des copies pour reprendre la main.\"\n\t\t\t\t},\n\t\t\t\t\"managedNode\": {\n\t\t\t\t\t\"text\": \"Je gère déjà un nœud à jour (version logiciel), correctement synchronisé (état blockchain) et joignable (peer réseau).\"\n\t\t\t\t},\n\t\t\t\t\"unsafeApiProtected\": {\n\t\t\t\t\t\"text\": \"J'ai veillé à ne pas exposer publiquement l'api unsafe de mon noeud smith.\"\n\t\t\t\t},\n\t\t\t\t\"technicalChoicesShared\": {\n\t\t\t\t\t\"text\": \"Je fournis à la demande d'un autre forgeron, mes choix techniques et si je change d'approche, je préviens les forgerons qui m'ont certifié (pour être alerté des éventuels soucis associés).\"\n\t\t\t\t},\n\t\t\t\t\"declareOfflineIfUnsure\": {\n\t\t\t\t\t\"text\": \"Je me déclare offline en cas de doute sur la sécurité ou la continuité de service de mon noeud.\"\n\t\t\t\t},\n\t\t\t\t\"respond24h\": {\n\t\t\t\t\t\"text\": \"Je m'engage à répondre en moins de 24h aux forgerons quand je suis déclaré online. Sinon, je me déclare offline.\"\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t\"contact\": {\n\t\t\t\"label\": \"Contact\",\n\t\t\t\"questions\": {\n\t\t\t\t\"threeSmithsReachable\": {\n\t\t\t\t\t\"text\": \"Je sais joindre efficacement et rapidement 3 des forgerons qui comptent me certifier (Cela par au moins 2 canaux : tel/sms + email, xmpp, matrix...).\"\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t\"knowledge\": {\n\t\t\t\"label\": \"Connaissance\",\n\t\t\t\"questions\": {\n\t\t\t\t\"acceptedCommitments\": {\n\t\t\t\t\t\"text\": \"J'ai lu et j'accepte de respecter l'ensemble des engagements forgerons et j'en comprend les implications et raisons d'être.\"\n\t\t\t\t},\n\t\t\t\t\"smithTdcRules\": {\n\t\t\t\t\t\"text\": \"J'ai pris connaissance des règles et délais associé au fonctionnement de la TdC forgeron.\"\n\t\t\t\t},\n\t\t\t\t\"blockchainConsensus\": {\n\t\t\t\t\t\"text\": \"J'ai bien compris le fonctionnement d'un réseau blockchain, en particulier les enjeux de sécurité liés aux mécanismes de consensus de la blockchain Duniter.\"\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t\"trap\": {\n\t\t\t\"label\": \"Piège\",\n\t\t\t\"expected\": false,\n\t\t\t\"questions\": {\n\t\t\t\t\"pressureForCertification\": {\n\t\t\t\t\t\"text\": \"J'insiste, harcèle ou fait pression d'une autre manière pour être certifié.\"\n\t\t\t\t},\n\t\t\t\t\"seekingGlory\": {\n\t\t\t\t\t\"text\": \"Je veux être forgeron pour la gloire et le pouvoir.\"\n\t\t\t\t},\n\t\t\t\t\"seekingToHarm\": {\n\t\t\t\t\t\"text\": \"Je cherche à nuire à l'écosystème Ǧ1 en m'infiltrant parmis les forgerons.\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n",
24
+ },
25
+ {
26
+ "id": "smith-certifier",
27
+ "lang": "en",
28
+ "status": "validated",
29
+ "content": "{\n\t\"title\": \"Ğ1 Certifying Smith Commitment\",\n\t\"preamble\": \"Certifying a smith means vouching that they have sufficient skills and that they respect the smith commitments.\\n\",\n\t\"lang\": \"en\",\n\t\"fileFormatVersion\": \"gcheck-1\",\n\t\"checklistVersion\": \"smith-v2.0.0-check-v1.0.0\",\n\t\"sorting\": \"shuffle\",\n\t\"defaults\": {\n\t\t\"expected\": true,\n\t\t\"severity\": \"blocking\"\n\t},\n\t\"chapeauError\": \"Warning, you did not answer the question correctly. We invite you to re-read all the smith commitments and discuss them with other smiths before attempting to certify again.\",\n\t\"categories\": {\n\t\t\"securityCompliance\": {\n\t\t\t\"label\": \"Security & compliance\",\n\t\t\t\"questions\": {\n\t\t\t\t\"questionedIntention\": {\n\t\t\t\t\t\"text\": \"I have questioned the candidate's intention to join the smiths and I remain ready to certify them.\"\n\t\t\t\t},\n\t\t\t\t\"verifiedSecurityPractices\": {\n\t\t\t\t\t\"text\": \"I have asked the candidate about their security practices and I consider them sufficient for the current network.\"\n\t\t\t\t},\n\t\t\t\t\"randomRecoveryPhrase\": {\n\t\t\t\t\t\"text\": \"The candidate assures me that their account comes from a randomly generated recovery phrase.\"\n\t\t\t\t},\n\t\t\t\t\"multiPhysicalBackup\": {\n\t\t\t\t\t\"text\": \"The candidate assures me they have stored their recovery phrase on multiple physical media (digital or otherwise), recoverable independently. In case of theft, fire, forgotten password... at least one version is accessible to them.\"\n\t\t\t\t},\n\t\t\t\t\"verifiedNodeStatus\": {\n\t\t\t\t\t\"text\": \"I have verified that the candidate already manages an up-to-date node that is correctly synchronized.\"\n\t\t\t\t},\n\t\t\t\t\"notedConfiguration\": {\n\t\t\t\t\t\"text\": \"I have noted the configuration style (Debian package, docker-compose...) that the candidate uses for their node (this configuration has no known flaws, notably does not publicly expose the unsafe API).\"\n\t\t\t\t},\n\t\t\t\t\"changeNotificationCommitment\": {\n\t\t\t\t\t\"text\": \"The candidate has committed to me to inform me of any significant change in their configuration (to be able to transmit information in a targeted way in case of problems).\"\n\t\t\t\t},\n\t\t\t\t\"offlineRisksUnderstood\": {\n\t\t\t\t\t\"text\": \"I have verified with the candidate that they know the risks for themselves and the network of being offline without announcing it, and that they consider themselves able to ensure sufficient uptime.\"\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t\"contact\": {\n\t\t\t\"label\": \"Contact\",\n\t\t\t\"questions\": {\n\t\t\t\t\"efficientSmithContact\": {\n\t\t\t\t\t\"text\": \"I can efficiently and quickly reach the smiths I have certified (generally by phone).\"\n\t\t\t\t},\n\t\t\t\t\"twoChannels\": {\n\t\t\t\t\t\"text\": \"I can reach them via at least 2 channels (SMS, email, XMPP, Matrix...).\"\n\t\t\t\t},\n\t\t\t\t\"defectNotification24h\": {\n\t\t\t\t\t\"text\": \"I commit to contacting this smith within 24 hours maximum if I learn that a defect affects their validator node (desynchronized, not up to date, unreachable...).\"\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t\"knowledge\": {\n\t\t\t\"label\": \"Knowledge\",\n\t\t\t\"questions\": {\n\t\t\t\t\"acceptedFullCommitments\": {\n\t\t\t\t\t\"text\": \"I have verified that the candidate has accepted the smith commitments in their entirety.\"\n\t\t\t\t},\n\t\t\t\t\"smithTdcRulesKnown\": {\n\t\t\t\t\t\"text\": \"I have verified that the candidate knows where to consult the detailed rules of the smith Web of Trust.\"\n\t\t\t\t},\n\t\t\t\t\"onlineOfflineDelays\": {\n\t\t\t\t\t\"text\": \"I have verified that the candidate knows the online/offline transition delays of their validator node.\"\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t\"trap\": {\n\t\t\t\"label\": \"Trap\",\n\t\t\t\"expected\": false,\n\t\t\t\"questions\": {\n\t\t\t\t\"certifyingUnderPressure\": {\n\t\t\t\t\t\"text\": \"I am certifying under threat or other form of pressure.\"\n\t\t\t\t},\n\t\t\t\t\"personalAdvantage\": {\n\t\t\t\t\t\"text\": \"I am gaining a personal advantage in exchange for my certification.\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n",
30
+ },
31
+ {
32
+ "id": "smith-certifier",
33
+ "lang": "es",
34
+ "status": "validated",
35
+ "content": "{\n\t\"title\": \"Compromiso del forjador certificador Ğ1\",\n\t\"preamble\": \"Certificar a un forjador es garantizar que dispone de las competencias suficientes y que respeta los compromisos de los forjadores.\\n\",\n\t\"lang\": \"es\",\n\t\"fileFormatVersion\": \"gcheck-1\",\n\t\"checklistVersion\": \"smith-v2.0.0-check-v1.0.0\",\n\t\"sorting\": \"shuffle\",\n\t\"defaults\": {\n\t\t\"expected\": true,\n\t\t\"severity\": \"blocking\"\n\t},\n\t\"chapeauError\": \"Atención, no ha respondido correctamente a la pregunta. Le invitamos a releer todos los compromisos de los forjadores y a hablar de ello con otros forjadores antes de intentar certificar de nuevo.\",\n\t\"categories\": {\n\t\t\"securityCompliance\": {\n\t\t\t\"label\": \"Seguridad y conformidad\",\n\t\t\t\"questions\": {\n\t\t\t\t\"questionedIntention\": {\n\t\t\t\t\t\"text\": \"He cuestionado la intención de la persona certificada para unirse a los forjadores y sigo dispuesto a certificarla.\"\n\t\t\t\t},\n\t\t\t\t\"verifiedSecurityPractices\": {\n\t\t\t\t\t\"text\": \"He preguntado al certificado cuáles son sus prácticas de seguridad y las considero suficientes para la red actual.\"\n\t\t\t\t},\n\t\t\t\t\"randomRecoveryPhrase\": {\n\t\t\t\t\t\"text\": \"El certificado me asegura que su cuenta proviene de una frase de recuperación generada aleatoriamente.\"\n\t\t\t\t},\n\t\t\t\t\"multiPhysicalBackup\": {\n\t\t\t\t\t\"text\": \"El certificado me asegura haber almacenado su frase de recuperación en varios soportes físicos (digitales o no), recuperables de forma independiente. En caso de robo, incendio, olvido de contraseña… al menos una de las versiones le es accesible.\"\n\t\t\t\t},\n\t\t\t\t\"verifiedNodeStatus\": {\n\t\t\t\t\t\"text\": \"He verificado que el certificado ya gestiona un nodo actualizado y correctamente sincronizado.\"\n\t\t\t\t},\n\t\t\t\t\"notedConfiguration\": {\n\t\t\t\t\t\"text\": \"He anotado el estilo de configuración (paquete Debian, docker-compose…) que utiliza el certificado para su nodo (esta configuración no tiene fallos conocidos, en particular no expone la API unsafe públicamente).\"\n\t\t\t\t},\n\t\t\t\t\"changeNotificationCommitment\": {\n\t\t\t\t\t\"text\": \"El certificado se ha comprometido conmigo a informarme de cualquier cambio significativo en su configuración (para poder transmitir la información de forma dirigida en caso de problema).\"\n\t\t\t\t},\n\t\t\t\t\"offlineRisksUnderstood\": {\n\t\t\t\t\t\"text\": \"He verificado con el certificado que conoce los riesgos para él y para la red de estar offline sin haberlo anunciado, y que se considera capaz de garantizar un uptime suficiente.\"\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t\"contact\": {\n\t\t\t\"label\": \"Contacto\",\n\t\t\t\"questions\": {\n\t\t\t\t\"efficientSmithContact\": {\n\t\t\t\t\t\"text\": \"Sé contactar eficaz y rápidamente con los forjadores que he certificado (generalmente por teléfono).\"\n\t\t\t\t},\n\t\t\t\t\"twoChannels\": {\n\t\t\t\t\t\"text\": \"Puedo contactarlos por al menos 2 canales (SMS, email, XMPP, Matrix…).\"\n\t\t\t\t},\n\t\t\t\t\"defectNotification24h\": {\n\t\t\t\t\t\"text\": \"Me comprometo a contactar en un máximo de 24 h a este forjador si me entero de que un fallo afecta a su nodo validador (desincronizado, no actualizado, inaccesible…).\"\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t\"knowledge\": {\n\t\t\t\"label\": \"Conocimiento\",\n\t\t\t\"questions\": {\n\t\t\t\t\"acceptedFullCommitments\": {\n\t\t\t\t\t\"text\": \"He verificado que el certificado ha aceptado todos los compromisos de los forjadores en su totalidad.\"\n\t\t\t\t},\n\t\t\t\t\"smithTdcRulesKnown\": {\n\t\t\t\t\t\"text\": \"He verificado que el certificado sabe dónde consultar las reglas detalladas de la TdC de forjadores.\"\n\t\t\t\t},\n\t\t\t\t\"onlineOfflineDelays\": {\n\t\t\t\t\t\"text\": \"He verificado que el certificado conoce los plazos de paso online/offline de su nodo validador.\"\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t\"trap\": {\n\t\t\t\"label\": \"Trampa\",\n\t\t\t\"expected\": false,\n\t\t\t\"questions\": {\n\t\t\t\t\"certifyingUnderPressure\": {\n\t\t\t\t\t\"text\": \"Certifico bajo amenaza u otra forma de presión.\"\n\t\t\t\t},\n\t\t\t\t\"personalAdvantage\": {\n\t\t\t\t\t\"text\": \"Obtengo una ventaja personal a cambio de mi certificación.\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n",
36
+ },
37
+ {
38
+ "id": "smith-certifier",
39
+ "lang": "fr",
40
+ "status": "validated",
41
+ "content": "{\n\t\"title\": \"Engagement du forgeron certificateur Ğ1\",\n\t\"preamble\": \"Certifier un forgeron c'est se porter garant qu'il dispose des compétences suffisantes et qu'il respecte les engagements forgerons.\\n\",\n\t\"lang\": \"fr\",\n\t\"fileFormatVersion\": \"gcheck-1\",\n\t\"checklistVersion\": \"smith-v2.0.0-check-v1.0.0\",\n\t\"sorting\": \"shuffle\",\n\t\"defaults\": {\n\t\t\"expected\": true,\n\t\t\"severity\": \"blocking\"\n\t},\n\t\"chapeauError\": \"Attention vous n'avez pas répondu correctement à la question. Nous vous invitons à relire l'ensemble des engagements forgerons et à en discuter avec d'autres forgerons avant de retenter de certifier.\",\n\t\"categories\": {\n\t\t\"securityCompliance\": {\n\t\t\t\"label\": \"Sécurité & conformité\",\n\t\t\t\"questions\": {\n\t\t\t\t\"questionedIntention\": {\n\t\t\t\t\t\"text\": \"J'ai questionné l'intention du certifié à rejoindre les forgerons et je reste prêt à le certifier.\"\n\t\t\t\t},\n\t\t\t\t\"verifiedSecurityPractices\": {\n\t\t\t\t\t\"text\": \"J'ai demandé au certifié quelles étaient ses pratiques de sécurité et je les estime suffisantes pour le réseau actuel.\"\n\t\t\t\t},\n\t\t\t\t\"randomRecoveryPhrase\": {\n\t\t\t\t\t\"text\": \"Le certifié m'assure que son compte est issu d'une phrase de récupération générée aléatoirement.\"\n\t\t\t\t},\n\t\t\t\t\"multiPhysicalBackup\": {\n\t\t\t\t\t\"text\": \"Le certifié m'assure avoir stocké sa phrase de récupération sur plusieurs supports physiques (numérique ou non), récupérables indépendamment. En cas de vol, incendie, oubli de mot de passe... au moins une des versions lui est accessible.\"\n\t\t\t\t},\n\t\t\t\t\"verifiedNodeStatus\": {\n\t\t\t\t\t\"text\": \"J'ai vérifié que le certifié gère déjà un nœud à jour qui est correctement synchronisé.\"\n\t\t\t\t},\n\t\t\t\t\"notedConfiguration\": {\n\t\t\t\t\t\"text\": \"J'ai noté le style de configuration (paquet debian, docker-compose...) que le certifié utilise pour son nœud (cette configuration n'a pas de faille connue, notamment n'expose pas l'api unsafe publiquement).\"\n\t\t\t\t},\n\t\t\t\t\"changeNotificationCommitment\": {\n\t\t\t\t\t\"text\": \"Le certifié s'est engagé auprès de moi à m'informer de tout changement significatif de sa config (pour pouvoir transmettre les infos de manière ciblée en cas de problème).\"\n\t\t\t\t},\n\t\t\t\t\"offlineRisksUnderstood\": {\n\t\t\t\t\t\"text\": \"J'ai vérifié avec le certifié qu'il connait les risques pour lui et le réseau d'être offline sans l'avoir annoncé, et qu'il se considère en mesure d'assurer un uptime suffisant.\"\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t\"contact\": {\n\t\t\t\"label\": \"Contact\",\n\t\t\t\"questions\": {\n\t\t\t\t\"efficientSmithContact\": {\n\t\t\t\t\t\"text\": \"Je sais joindre efficacement et rapidement les forgerons que j'ai certifiés (téléphone généralement).\"\n\t\t\t\t},\n\t\t\t\t\"twoChannels\": {\n\t\t\t\t\t\"text\": \"Je peux les joindre par au moins 2 canaux (sms, email, xmpp, matrix...).\"\n\t\t\t\t},\n\t\t\t\t\"defectNotification24h\": {\n\t\t\t\t\t\"text\": \"Je m'engage à contacter sous 24h max ce forgeron si j'apprends qu'un défaut concerne son nœud validateur (désynchronisé, pas à jour, inaccessible...).\"\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t\"knowledge\": {\n\t\t\t\"label\": \"Connaissance\",\n\t\t\t\"questions\": {\n\t\t\t\t\"acceptedFullCommitments\": {\n\t\t\t\t\t\"text\": \"J'ai vérifié que le certifié a accepté les engagements forgerons dans son intégralité.\"\n\t\t\t\t},\n\t\t\t\t\"smithTdcRulesKnown\": {\n\t\t\t\t\t\"text\": \"J'ai vérifié que le certifié savait où consulter les règles détaillées de la TdC forgeron.\"\n\t\t\t\t},\n\t\t\t\t\"onlineOfflineDelays\": {\n\t\t\t\t\t\"text\": \"J'ai vérifié que le certifié connait les délais de passage en ligne/hors ligne de son nœud validateur.\"\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t\"trap\": {\n\t\t\t\"label\": \"Piège\",\n\t\t\t\"expected\": false,\n\t\t\t\"questions\": {\n\t\t\t\t\"certifyingUnderPressure\": {\n\t\t\t\t\t\"text\": \"Je certifie sous la menace ou autre forme de pression.\"\n\t\t\t\t},\n\t\t\t\t\"personalAdvantage\": {\n\t\t\t\t\t\"text\": \"Je tire un avantage personnel en échange de ma certification.\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n",
42
+ },
43
+ {
44
+ "id": "member-certification",
45
+ "lang": "en",
46
+ "status": "pending",
47
+ "content": "{\n\t\"title\": \"Ğ1 Member Certification Checklist\",\n\t\"preamble\": \"- Whenever \\\"the certifiee\\\" is mentioned below, read \\\"the natural person to be certified or their legal guardian if applicable\\\"\\n- If in doubt about a question, discuss it with other members by referring to the charter before certifying.\\n\",\n\t\"lang\": \"en\",\n\t\"fileFormatVersion\": \"gcheck-1\",\n\t\"checklistVersion\": \"charter-v1.0-check-v1.0.0\",\n\t\"sorting\": \"shuffle\",\n\t\"categoryPick\": 1,\n\t\"globalPick\": 1,\n\t\"defaults\": {\n\t\t\"expected\": true,\n\t\t\"severity\": \"blocking\"\n\t},\n\t\"chapeauError\": \"The following questions did not receive the expected answer. We invite you to consult the Ǧ1 charter and discuss it with other members before attempting to certify again.\",\n\t\"chapeauWarning\": \"The following questions did not receive the optimal answer. Are you sure it is appropriate to certify under these conditions? If in doubt, we invite you to consult the Ǧ1 charter and discuss it with other members.\",\n\t\"categories\": {\n\t\t\"trap\": {\n\t\t\t\"label\": \"Trap\",\n\t\t\t\"expected\": false,\n\t\t\t\"questions\": {\n\t\t\t\t\"certifyUnderThreat\": {\n\t\t\t\t\t\"text\": \"I am certifying under threat or other form of pressure.\"\n\t\t\t\t},\n\t\t\t\t\"requestedCounterpart\": {\n\t\t\t\t\t\"text\": \"I have demanded a counterpart from the certifiee.\"\n\t\t\t\t},\n\t\t\t\t\"certifyingLegalEntity\": {\n\t\t\t\t\t\"text\": \"The account I am about to certify belongs to an association or other legal entity.\"\n\t\t\t\t},\n\t\t\t\t\"multipleIdentities\": {\n\t\t\t\t\t\"text\": \"Does the certifiee possess multiple member identities with certification(s)?\"\n\t\t\t\t},\n\t\t\t\t\"demandedPassword\": {\n\t\t\t\t\t\"text\": \"I have demanded that the certifiee give me their password and/or their secret identifier.\"\n\t\t\t\t},\n\t\t\t\t\"demandedRevocation\": {\n\t\t\t\t\t\"text\": \"I have demanded that the certifiee transmit their revocation document to me.\"\n\t\t\t\t},\n\t\t\t\t\"suspectedTransgression\": {\n\t\t\t\t\t\"text\": \"Do you think the certifiee is likely to transgress one of the following fundamental commitments?\\n- I commit to the community to have only one active member account\\n- I commit to the community to certify only natural persons ready to respect the previous rule and this one\\n\"\n\t\t\t\t},\n\t\t\t\t\"intendedTransgression\": {\n\t\t\t\t\t\"text\": \"In your opinion, does the certifiee intend to transgress one of the following fundamental commitments?\\n- I commit to the community to have only one active member account\\n- I commit to the community to certify only natural persons ready to respect the previous rule and this one\\n\"\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t\"identity\": {\n\t\t\t\"label\": \"Identity\",\n\t\t\t\"questions\": {\n\t\t\t\t\"commitmentsTaken\": {\n\t\t\t\t\t\"text\": \"Have the two fundamental commitments of the charter been taken by the certifiee?\\n- I commit to the community to have only one active member account\\n- I commit to the community to certify only natural persons ready to respect the previous rule and this one\\n\"\n\t\t\t\t},\n\t\t\t\t\"reasonableTrust\": {\n\t\t\t\t\t\"text\": \"Have you formed a reasonable trust that the certifiee will respect the fundamental commitments?\\n- I commit to the community to have only one active member account\\n- I commit to the community to certify only natural persons ready to respect the previous rule and this one\\n\"\n\t\t\t\t},\n\t\t\t\t\"charterAwareness\": {\n\t\t\t\t\t\"text\": \"Have you verified that the certifiee knows where to find the charter in its full version and has committed to respecting the fundamental commitments?\\n- I commit to the community to have only one active member account\\n- I commit to the community to certify only natural persons ready to respect the previous rule and this one\\n\"\n\t\t\t\t},\n\t\t\t\t\"intendedCompliance\": {\n\t\t\t\t\t\"text\": \"In your opinion, does the certifiee intend to respect the following fundamental commitments?\\n- I commit to the community to have only one active member account\\n- I commit to the community to certify only natural persons ready to respect the previous rule and this one\\n\"\n\t\t\t\t},\n\t\t\t\t\"readyToComply\": {\n\t\t\t\t\t\"text\": \"Do you think the certifiee is ready to respect the following fundamental commitments?\\n- I commit to the community to have only one active member account\\n- I commit to the community to certify only natural persons ready to respect the previous rule and this one\\n\"\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t\"contact\": {\n\t\t\t\"label\": \"Contact\",\n\t\t\t\"questions\": {\n\t\t\t\t\"recentContact\": {\n\t\t\t\t\t\"text\": \"Have you recently contacted this person through multiple means and did they respond?\"\n\t\t\t\t},\n\t\t\t\t\"contactCommitment\": {\n\t\t\t\t\t\"text\": \"Do you commit to doing your best to be a point of contact with the certifiee if needed for the entire duration of validity of your issued certification?\"\n\t\t\t\t},\n\t\t\t\t\"personalKnowledge\": {\n\t\t\t\t\t\"text\": \"I personally know the future certifiee (and their legal guardian if applicable).\"\n\t\t\t\t},\n\t\t\t\t\"physicalMeeting\": {\n\t\t\t\t\t\"text\": \"I have physically met the certifiee or, failing that, at least 2 people who have physically met them.\"\n\t\t\t\t},\n\t\t\t\t\"twoMembersKnown\": {\n\t\t\t\t\t\"text\": \"I know at least 2 members ready to certify or who have certified this person and I know how to contact them.\"\n\t\t\t\t},\n\t\t\t\t\"knowFullName\": {\n\t\t\t\t\t\"text\": \"I know the last name, first name and postal address of the future certifiee?\",\n\t\t\t\t\t\"severity\": \"warning\"\n\t\t\t\t},\n\t\t\t\t\"efficientContact\": {\n\t\t\t\t\t\"text\": \"I know how to reach the certified person efficiently and quickly (generally by phone).\",\n\t\t\t\t\t\"severity\": \"warning\"\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t\"accountSecurity\": {\n\t\t\t\"label\": \"Account Security\",\n\t\t\t\"questions\": {\n\t\t\t\t\"securityPractices\": {\n\t\t\t\t\t\"text\": \"Has the certifiee confirmed to you that their access credentials comply with the security guidelines of the software they use?\"\n\t\t\t\t},\n\t\t\t\t\"twoTransactions\": {\n\t\t\t\t\t\"text\": \"Has the person to be certified already made at least two transfers on two different dates from the account to be certified?\",\n\t\t\t\t\t\"severity\": \"warning\"\n\t\t\t\t},\n\t\t\t\t\"accessCodesRetrieval\": {\n\t\t\t\t\t\"text\": \"Do you confirm that the certifiee is absolutely certain they can retrieve their access codes, particularly over time?\"\n\t\t\t\t},\n\t\t\t\t\"secureStorage\": {\n\t\t\t\t\t\"text\": \"Do you confirm that the certifiee has kept their access codes secret in at least two secure locations? (they do not rely solely on their memory)\",\n\t\t\t\t\t\"severity\": \"warning\"\n\t\t\t\t},\n\t\t\t\t\"revocationDocument\": {\n\t\t\t\t\t\"text\": \"Has the certifiee downloaded their revocation document and do they know how to retrieve it, even if one copy is lost?\",\n\t\t\t\t\t\"severity\": \"warning\"\n\t\t\t\t},\n\t\t\t\t\"publicKeyVerified\": {\n\t\t\t\t\t\"text\": \"I have verified with the person to be certified that it is indeed the correct public key that I am certifying. (Checksum)\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n",
48
+ },
49
+ {
50
+ "id": "member-certification",
51
+ "lang": "es",
52
+ "status": "pending",
53
+ "content": "{\n\t\"title\": \"Checklist de certificación de miembro Ğ1\",\n\t\"preamble\": \"- Cada vez que se menciona « la certificada » a continuación, léase « la persona física a certificar o su tutor legal en su caso »\\n- En caso de duda con una pregunta, discútalo con otros miembros consultando la carta antes de certificar.\\n\",\n\t\"lang\": \"es\",\n\t\"fileFormatVersion\": \"gcheck-1\",\n\t\"checklistVersion\": \"charter-v1.0-check-v1.0.0\",\n\t\"sorting\": \"shuffle\",\n\t\"categoryPick\": 1,\n\t\"globalPick\": 1,\n\t\"defaults\": {\n\t\t\"expected\": true,\n\t\t\"severity\": \"blocking\"\n\t},\n\t\"chapeauError\": \"Las siguientes preguntas no recibieron la respuesta esperada. Le invitamos a consultar la carta Ǧ1 y a discutirlo con otros miembros antes de volver a intentar certificar.\",\n\t\"chapeauWarning\": \"Las siguientes preguntas no recibieron la respuesta óptima. ¿Está seguro de que es pertinente certificar en estas condiciones? Si tiene dudas, le invitamos a consultar la carta Ǧ1 y a discutirlo con otros miembros.\",\n\t\"categories\": {\n\t\t\"trap\": {\n\t\t\t\"label\": \"Trampa\",\n\t\t\t\"expected\": false,\n\t\t\t\"questions\": {\n\t\t\t\t\"certifyUnderThreat\": {\n\t\t\t\t\t\"text\": \"Certifico bajo amenaza u otra forma de presión.\"\n\t\t\t\t},\n\t\t\t\t\"requestedCounterpart\": {\n\t\t\t\t\t\"text\": \"He exigido una contrapartida a la certificada.\"\n\t\t\t\t},\n\t\t\t\t\"certifyingLegalEntity\": {\n\t\t\t\t\t\"text\": \"La cuenta que estoy a punto de certificar pertenece a una asociación u otra persona jurídica.\"\n\t\t\t\t},\n\t\t\t\t\"multipleIdentities\": {\n\t\t\t\t\t\"text\": \"¿Posee la certificada múltiples identidades de miembro con certificación(es)?\"\n\t\t\t\t},\n\t\t\t\t\"demandedPassword\": {\n\t\t\t\t\t\"text\": \"He exigido que la certificada me dé su contraseña y/o su identificador secreto.\"\n\t\t\t\t},\n\t\t\t\t\"demandedRevocation\": {\n\t\t\t\t\t\"text\": \"He exigido que la certificada me transmita su documento de revocación.\"\n\t\t\t\t},\n\t\t\t\t\"suspectedTransgression\": {\n\t\t\t\t\t\"text\": \"¿Cree usted que la certificada es susceptible de transgredir uno de los siguientes compromisos fundamentales?\\n- Me comprometo ante la comunidad a tener solo una cuenta de miembro activa\\n- Me comprometo ante la comunidad a certificar únicamente a personas físicas dispuestas a respetar la regla anterior y esta\\n\"\n\t\t\t\t},\n\t\t\t\t\"intendedTransgression\": {\n\t\t\t\t\t\"text\": \"¿Tiene la certificada, en su opinión, la intención de transgredir uno de los siguientes compromisos fundamentales?\\n- Me comprometo ante la comunidad a tener solo una cuenta de miembro activa\\n- Me comprometo ante la comunidad a certificar únicamente a personas físicas dispuestas a respetar la regla anterior y esta\\n\"\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t\"identity\": {\n\t\t\t\"label\": \"Identidad\",\n\t\t\t\"questions\": {\n\t\t\t\t\"commitmentsTaken\": {\n\t\t\t\t\t\"text\": \"¿Han sido asumidos los dos compromisos fundamentales de la carta por la certificada?\\n- Me comprometo ante la comunidad a tener solo una cuenta de miembro activa\\n- Me comprometo ante la comunidad a certificar únicamente a personas físicas dispuestas a respetar la regla anterior y esta\\n\"\n\t\t\t\t},\n\t\t\t\t\"reasonableTrust\": {\n\t\t\t\t\t\"text\": \"¿Se ha forjado usted una confianza razonable en que la certificada respetará los compromisos fundamentales?\\n- Me comprometo ante la comunidad a tener solo una cuenta de miembro activa\\n- Me comprometo ante la comunidad a certificar únicamente a personas físicas dispuestas a respetar la regla anterior y esta\\n\"\n\t\t\t\t},\n\t\t\t\t\"charterAwareness\": {\n\t\t\t\t\t\"text\": \"¿Ha verificado que la certificada sabe dónde encontrar la carta en su versión íntegra y se ha comprometido a respetar los compromisos fundamentales?\\n- Me comprometo ante la comunidad a tener solo una cuenta de miembro activa\\n- Me comprometo ante la comunidad a certificar únicamente a personas físicas dispuestas a respetar la regla anterior y esta\\n\"\n\t\t\t\t},\n\t\t\t\t\"intendedCompliance\": {\n\t\t\t\t\t\"text\": \"¿Tiene la certificada, en su opinión, la intención de respetar los siguientes compromisos fundamentales?\\n- Me comprometo ante la comunidad a tener solo una cuenta de miembro activa\\n- Me comprometo ante la comunidad a certificar únicamente a personas físicas dispuestas a respetar la regla anterior y esta\\n\"\n\t\t\t\t},\n\t\t\t\t\"readyToComply\": {\n\t\t\t\t\t\"text\": \"¿Cree usted que la certificada está preparada para respetar los siguientes compromisos fundamentales?\\n- Me comprometo ante la comunidad a tener solo una cuenta de miembro activa\\n- Me comprometo ante la comunidad a certificar únicamente a personas físicas dispuestas a respetar la regla anterior y esta\\n\"\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t\"contact\": {\n\t\t\t\"label\": \"Contacto\",\n\t\t\t\"questions\": {\n\t\t\t\t\"recentContact\": {\n\t\t\t\t\t\"text\": \"¿Ha contactado recientemente a esta persona por varios medios y le ha respondido?\"\n\t\t\t\t},\n\t\t\t\t\"contactCommitment\": {\n\t\t\t\t\t\"text\": \"¿Se compromete a hacer lo posible para ser un punto de contacto con la certificada en caso de necesidad durante toda la duración de validez de su certificación emitida?\"\n\t\t\t\t},\n\t\t\t\t\"personalKnowledge\": {\n\t\t\t\t\t\"text\": \"Conozco personalmente a la futura certificada (y a su tutor legal en su caso).\"\n\t\t\t\t},\n\t\t\t\t\"physicalMeeting\": {\n\t\t\t\t\t\"text\": \"He conocido físicamente a la certificada o, en su defecto, al menos a 2 personas que la han conocido físicamente.\"\n\t\t\t\t},\n\t\t\t\t\"twoMembersKnown\": {\n\t\t\t\t\t\"text\": \"Conozco al menos a 2 miembros dispuestos a certificar o que han certificado a esta persona y sé cómo contactarlos.\"\n\t\t\t\t},\n\t\t\t\t\"knowFullName\": {\n\t\t\t\t\t\"text\": \"¿Conozco el apellido, el nombre y la dirección postal de la futura certificada?\",\n\t\t\t\t\t\"severity\": \"warning\"\n\t\t\t\t},\n\t\t\t\t\"efficientContact\": {\n\t\t\t\t\t\"text\": \"Sé cómo contactar eficaz y rápidamente a la persona certificada (generalmente por teléfono).\",\n\t\t\t\t\t\"severity\": \"warning\"\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t\"accountSecurity\": {\n\t\t\t\"label\": \"Seguridad de la cuenta\",\n\t\t\t\"questions\": {\n\t\t\t\t\"securityPractices\": {\n\t\t\t\t\t\"text\": \"¿Le ha confirmado la certificada que sus secretos de acceso respetan las consignas de seguridad del software que utiliza?\"\n\t\t\t\t},\n\t\t\t\t\"twoTransactions\": {\n\t\t\t\t\t\"text\": \"¿Ha realizado la persona a certificar al menos dos transferencias en dos fechas diferentes desde la cuenta a certificar?\",\n\t\t\t\t\t\"severity\": \"warning\"\n\t\t\t\t},\n\t\t\t\t\"accessCodesRetrieval\": {\n\t\t\t\t\t\"text\": \"¿Confirma que la certificada está absolutamente segura de poder recuperar sus códigos de acceso, en particular a largo plazo?\"\n\t\t\t\t},\n\t\t\t\t\"secureStorage\": {\n\t\t\t\t\t\"text\": \"¿Confirma que la certificada ha conservado, en secreto, sus códigos de acceso en al menos dos lugares seguros? (no cuenta únicamente con su memoria)\",\n\t\t\t\t\t\"severity\": \"warning\"\n\t\t\t\t},\n\t\t\t\t\"revocationDocument\": {\n\t\t\t\t\t\"text\": \"¿Ha descargado la certificada su documento de revocación y sabe cómo recuperarlo, incluso si se pierde una copia?\",\n\t\t\t\t\t\"severity\": \"warning\"\n\t\t\t\t},\n\t\t\t\t\"publicKeyVerified\": {\n\t\t\t\t\t\"text\": \"He verificado con la persona a certificar que es la clave pública correcta la que estoy certificando. (Checksum)\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n",
54
+ },
55
+ {
56
+ "id": "member-certification",
57
+ "lang": "fr",
58
+ "status": "pending",
59
+ "content": "{\n\t\"title\": \"Checklist de certification membre Ğ1\",\n\t\"preamble\": \"- Chaque fois que « la certifiée » est nommée ci-dessous, lisez « la personne physique à certifier ou sa tutelle le cas échéant »\\n- En cas de doute avec une question, discutez-en avec d'autres membres en vous référant à la charte avant de certifier.\\n\",\n\t\"lang\": \"fr\",\n\t\"fileFormatVersion\": \"gcheck-1\",\n\t\"checklistVersion\": \"charter-v1.0-check-v1.0.0\",\n\t\"sorting\": \"shuffle\",\n\t\"categoryPick\": 1,\n\t\"globalPick\": 1,\n\t\"defaults\": {\n\t\t\"expected\": true,\n\t\t\"severity\": \"blocking\"\n\t},\n\t\"chapeauError\": \"Les questions suivantes n'ont pas reçu la réponse attendue. Nous vous invitons à consulter la charte Ǧ1 et à en discuter avec d'autres membres avant de re-tenter de certifier.\",\n\t\"chapeauWarning\": \"Les questions suivantes n'ont pas reçu la réponse optimale. Êtes-vous sûr qu'il soit pertinent de certifier dans ces conditions ? Si vous avez un doute, nous vous invitons à consulter la charte Ǧ1 et à en discuter avec d'autres membres.\",\n\t\"categories\": {\n\t\t\"trap\": {\n\t\t\t\"label\": \"Piège\",\n\t\t\t\"expected\": false,\n\t\t\t\"questions\": {\n\t\t\t\t\"certifyUnderThreat\": {\n\t\t\t\t\t\"text\": \"Je certifie sous la menace ou autre forme de pression.\"\n\t\t\t\t},\n\t\t\t\t\"requestedCounterpart\": {\n\t\t\t\t\t\"text\": \"J'ai réclamé une contrepartie à la certifiée.\"\n\t\t\t\t},\n\t\t\t\t\"certifyingLegalEntity\": {\n\t\t\t\t\t\"text\": \"Le compte que je m'apprête à certifier est celui d'une association ou autre personne morale.\"\n\t\t\t\t},\n\t\t\t\t\"multipleIdentities\": {\n\t\t\t\t\t\"text\": \"La certifiée possède-t-elle plusieurs identités membres avec certification(s) ?\"\n\t\t\t\t},\n\t\t\t\t\"demandedPassword\": {\n\t\t\t\t\t\"text\": \"J'ai exigé que la certifiée me donne son mot de passe et/ou son identifiant secret.\"\n\t\t\t\t},\n\t\t\t\t\"demandedRevocation\": {\n\t\t\t\t\t\"text\": \"J'ai exigé que la certifiée me transmette son document de révocation.\"\n\t\t\t\t},\n\t\t\t\t\"suspectedTransgression\": {\n\t\t\t\t\t\"text\": \"Pensez-vous que la certifiée est susceptible de transgresser l'un des engagements fondamentaux suivants ?\\n- Je m'engage envers la communauté à n'avoir qu'un compte membre actif\\n- Je m'engage envers la communauté à certifier uniquement des personnes physiques prêtes à respecter la règle précédente et celle-ci\\n\"\n\t\t\t\t},\n\t\t\t\t\"intendedTransgression\": {\n\t\t\t\t\t\"text\": \"La certifiée a-t-elle selon-vous l'intention de transgresser l'un des engagements fondamentaux suivants ?\\n- Je m'engage envers la communauté à n'avoir qu'un compte membre actif\\n- Je m'engage envers la communauté à certifier uniquement des personnes physiques prêtes à respecter la règle précédente et celle-ci\\n\"\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t\"identity\": {\n\t\t\t\"label\": \"Identité\",\n\t\t\t\"questions\": {\n\t\t\t\t\"commitmentsTaken\": {\n\t\t\t\t\t\"text\": \"Les deux engagements fondamentaux de la charte ont ils été pris par la certifiée ?\\n- Je m'engage envers la communauté à n'avoir qu'un compte membre actif\\n- Je m'engage envers la communauté à certifier uniquement des personnes physiques prêtes à respecter la règle précédente et celle-ci\\n\"\n\t\t\t\t},\n\t\t\t\t\"reasonableTrust\": {\n\t\t\t\t\t\"text\": \"Vous êtes-vous forgé une confiance raisonnable dans le fait que la certifiée respectera les engagements fondamentaux ?\\n- Je m'engage envers la communauté à n'avoir qu'un compte membre actif\\n- Je m'engage envers la communauté à certifier uniquement des personnes physiques prêtes à respecter la règle précédente et celle-ci\\n\"\n\t\t\t\t},\n\t\t\t\t\"charterAwareness\": {\n\t\t\t\t\t\"text\": \"Avez-vous vérifié que la certifiée sait où retrouver la charte dans sa version intégrale et s'est engagée à en respecter les engagements fondamentaux ?\\n- Je m'engage envers la communauté à n'avoir qu'un compte membre actif\\n- Je m'engage envers la communauté à certifier uniquement des personnes physiques prêtes à respecter la règle précédente et celle-ci\\n\"\n\t\t\t\t},\n\t\t\t\t\"intendedCompliance\": {\n\t\t\t\t\t\"text\": \"La certifiée a-t-elle selon-vous l'intention de respecter les engagements fondamentaux suivants ?\\n- Je m'engage envers la communauté à n'avoir qu'un compte membre actif\\n- Je m'engage envers la communauté à certifier uniquement des personnes physiques prêtes à respecter la règle précédente et celle-ci\\n\"\n\t\t\t\t},\n\t\t\t\t\"readyToComply\": {\n\t\t\t\t\t\"text\": \"Pensez-vous que la certifiée est prête à respecter les engagements fondamentaux suivants ?\\n- Je m'engage envers la communauté à n'avoir qu'un compte membre actif\\n- Je m'engage envers la communauté à certifier uniquement des personnes physiques prêtes à respecter la règle précédente et celle-ci\\n\"\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t\"contact\": {\n\t\t\t\"label\": \"Contact\",\n\t\t\t\"questions\": {\n\t\t\t\t\"recentContact\": {\n\t\t\t\t\t\"text\": \"Avez-vous contacté récemment cette personne par plusieurs moyens et vous a-t-elle répondu ?\"\n\t\t\t\t},\n\t\t\t\t\"contactCommitment\": {\n\t\t\t\t\t\"text\": \"Vous engagez-vous à faire votre possible pour être un point de contact avec la certifiée en cas de besoin pendant toute la durée de validité de votre certification émise ?\"\n\t\t\t\t},\n\t\t\t\t\"personalKnowledge\": {\n\t\t\t\t\t\"text\": \"Je connais personnellement la future certifiée (et sa tutelle le cas échéant).\"\n\t\t\t\t},\n\t\t\t\t\"physicalMeeting\": {\n\t\t\t\t\t\"text\": \"J'ai rencontré physiquement la certifiée ou à défaut au moins 2 personnes qui l'ont rencontré physiquement.\"\n\t\t\t\t},\n\t\t\t\t\"twoMembersKnown\": {\n\t\t\t\t\t\"text\": \"Je connais au moins 2 membres prêtes à certifier ou ayant certifié cette personne et je sais comment les contacter.\"\n\t\t\t\t},\n\t\t\t\t\"knowFullName\": {\n\t\t\t\t\t\"text\": \"Je connais le nom de famille, le prénom et l'adresse postale de la future certifiée ?\",\n\t\t\t\t\t\"severity\": \"warning\"\n\t\t\t\t},\n\t\t\t\t\"efficientContact\": {\n\t\t\t\t\t\"text\": \"Je sais joindre efficacement et rapidement la personne certifiée (téléphone généralement).\",\n\t\t\t\t\t\"severity\": \"warning\"\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t\"accountSecurity\": {\n\t\t\t\"label\": \"Sécurité du compte\",\n\t\t\t\"questions\": {\n\t\t\t\t\"securityPractices\": {\n\t\t\t\t\t\"text\": \"La certifiée vous a-t-elle confirmé que ses secrets d'accès respectent les consignes de sécurité du logiciel qu'elle utilise ?\"\n\t\t\t\t},\n\t\t\t\t\"twoTransactions\": {\n\t\t\t\t\t\"text\": \"La personne à certifier a-t-elle déjà effectué au moins deux virements à deux dates différentes depuis le compte à certifier ?\",\n\t\t\t\t\t\"severity\": \"warning\"\n\t\t\t\t},\n\t\t\t\t\"accessCodesRetrieval\": {\n\t\t\t\t\t\"text\": \"Confirmez-vous que la certifiée est absolument certaine de retrouver ses codes d'accès, en particulier dans la durée ?\"\n\t\t\t\t},\n\t\t\t\t\"secureStorage\": {\n\t\t\t\t\t\"text\": \"Confirmez-vous que la certifiée a conservé, secrets, ses codes d'accès dans au moins deux lieux sûrs. (elle ne compte pas uniquement sur sa mémoire)\",\n\t\t\t\t\t\"severity\": \"warning\"\n\t\t\t\t},\n\t\t\t\t\"revocationDocument\": {\n\t\t\t\t\t\"text\": \"La certifiée a-t-elle téléchargé son document de révocation et sait-elle le récupérer, même si un exemplaire est perdu ?\",\n\t\t\t\t\t\"severity\": \"warning\"\n\t\t\t\t},\n\t\t\t\t\"publicKeyVerified\": {\n\t\t\t\t\t\"text\": \"J'ai vérifié avec la personne à certifier que c'est bien la bonne clé publique que je certifie. (Checksum)\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n",
60
+ },
61
+ ]
@@ -0,0 +1,2 @@
1
+ # Généré au build.
2
+ __version__ = "0.3.0"
@@ -0,0 +1,188 @@
1
+ """Checklists : les officielles embarquées dans le paquet, et celles fournies par l'application.
2
+
3
+ Les officielles viennent de leur source de vérité,
4
+ [g1_monetary_license](https://git.duniter.org/documents/g1_monetary_license) : mettre à jour la
5
+ dépendance suffit à avoir les dernières questions. L'application peut aussi utiliser ses propres
6
+ checklists, YAML ou JSON, à la place ou en plus, avec un `Catalog`.
7
+
8
+ Même API que les modules `checklists` des moteurs Rust et TypeScript.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from dataclasses import dataclass
14
+ from pathlib import Path
15
+ from typing import Iterable, Iterator, Literal, Optional, Sequence
16
+
17
+ from . import parser
18
+ from ._embedded import EMBEDDED as _EMBEDDED
19
+ from .hash import checklist_hash
20
+ from .types import GcheckError, InputSchema
21
+
22
+ #: Identifiant de la checklist de certification membre.
23
+ MEMBER_CERTIFICATION = "member-certification"
24
+ #: Identifiant de la checklist du membre qui veut devenir forgeron.
25
+ SMITH_ASPIRANT = "smith-aspirant"
26
+ #: Identifiant de la checklist du forgeron qui certifie un autre forgeron.
27
+ SMITH_CERTIFIER = "smith-certifier"
28
+
29
+ #: Origine d'une checklist, par ordre de priorité dans un `Catalog`.
30
+ ChecklistStatus = Literal["external", "validated", "pending"]
31
+
32
+ _PRIORITY: dict[str, int] = {"external": 0, "validated": 1, "pending": 2}
33
+ _EXTENSIONS = (".yaml", ".yml", ".json")
34
+
35
+
36
+ @dataclass(frozen=True)
37
+ class Checklist:
38
+ """Une checklist, dans une langue."""
39
+
40
+ #: Identifiant, commun à toutes ses langues (par exemple `SMITH_ASPIRANT`)
41
+ id: str
42
+ #: Code de langue (par exemple `fr`)
43
+ lang: str
44
+ status: ChecklistStatus
45
+ #: Contenu, YAML ou JSON (JSON pour les checklists embarquées)
46
+ content: str
47
+
48
+ def parse(self) -> InputSchema:
49
+ """Lit le contenu de la checklist."""
50
+ return parser.parse(self.content)
51
+
52
+ def hash(self) -> str:
53
+ """Hash de contenu : à mémoriser après une passation pour détecter un changement."""
54
+ return checklist_hash(self.parse())
55
+
56
+
57
+ def _embedded() -> tuple[Checklist, ...]:
58
+ return tuple(
59
+ Checklist(
60
+ id=checklist["id"],
61
+ lang=checklist["lang"],
62
+ status=checklist["status"],
63
+ content=checklist["content"],
64
+ )
65
+ for checklist in _EMBEDDED
66
+ )
67
+
68
+
69
+ _ALL = _embedded()
70
+
71
+
72
+ def all_checklists() -> tuple[Checklist, ...]:
73
+ """Les checklists embarquées, triées par statut, identifiant et langue."""
74
+ return _ALL
75
+
76
+
77
+ def get_checklist(checklist_id: str, lang: str) -> Optional[Checklist]:
78
+ """Checklist embarquée `checklist_id` en `lang`, avec le repli de langue de `Catalog.get`."""
79
+ return _best(_ALL, checklist_id, lang)
80
+
81
+
82
+ def external_checklist(checklist_id: str, content: str) -> Checklist:
83
+ """Checklist fournie par l'application : le contenu est validé, et la langue y est lue."""
84
+ schema = parser.parse(content)
85
+ return Checklist(
86
+ id=checklist_id, lang=normalize_lang(schema.lang), status="external", content=content
87
+ )
88
+
89
+
90
+ def checklist_from_file(path: Path | str) -> Checklist:
91
+ """Lit un fichier de checklist. Son identifiant est le nom du fichier, sans extension ni
92
+ suffixe de langue : `smith-aspirant.fr.yaml` donne `smith-aspirant`."""
93
+ path = Path(path)
94
+ try:
95
+ content = path.read_text(encoding="utf-8")
96
+ except OSError as error:
97
+ raise GcheckError("INVALID_INPUT", f"{path}: {error}") from error
98
+ stem = path.stem
99
+ try:
100
+ checklist = external_checklist(stem, content)
101
+ except GcheckError as error:
102
+ raise GcheckError(error.code, f"{path}: {error.message}", error.path) from error
103
+ suffix = f".{checklist.lang}"
104
+ if stem.endswith(suffix):
105
+ checklist = Checklist(
106
+ id=stem[: -len(suffix)],
107
+ lang=checklist.lang,
108
+ status=checklist.status,
109
+ content=checklist.content,
110
+ )
111
+ return checklist
112
+
113
+
114
+ class Catalog:
115
+ """Un ensemble de checklists : les embarquées et/ou celles fournies par l'application."""
116
+
117
+ def __init__(self, checklists: Iterable[Checklist] = ()) -> None:
118
+ self._checklists: list[Checklist] = list(checklists)
119
+
120
+ @classmethod
121
+ def embedded(cls) -> "Catalog":
122
+ """Catalogue des checklists embarquées, auquel l'application peut ajouter les siennes."""
123
+ return cls(_ALL)
124
+
125
+ def add(self, checklist: Checklist) -> "Catalog":
126
+ """Ajoute une checklist ; une externe prime sur les embarquées et sur les précédentes."""
127
+ self._checklists.append(checklist)
128
+ return self
129
+
130
+ def add_content(self, checklist_id: str, content: str) -> "Catalog":
131
+ """Ajoute une checklist fournie par l'application (voir `external_checklist`)."""
132
+ return self.add(external_checklist(checklist_id, content))
133
+
134
+ def add_file(self, path: Path | str) -> "Catalog":
135
+ """Ajoute un fichier de checklist (voir `checklist_from_file`)."""
136
+ return self.add(checklist_from_file(path))
137
+
138
+ def add_dir(self, directory: Path | str) -> "Catalog":
139
+ """Ajoute les fichiers `.yaml`, `.yml` et `.json` d'un dossier, par ordre de nom."""
140
+ directory = Path(directory)
141
+ try:
142
+ paths = sorted(
143
+ path
144
+ for path in directory.iterdir()
145
+ if path.is_file() and path.suffix in _EXTENSIONS
146
+ )
147
+ except OSError as error:
148
+ raise GcheckError("INVALID_INPUT", f"{directory}: {error}") from error
149
+ for path in paths:
150
+ self.add_file(path)
151
+ return self
152
+
153
+ def get(self, checklist_id: str, lang: str) -> Optional[Checklist]:
154
+ """Checklist `checklist_id` en `lang` (`fr`, `fr_FR.UTF-8`…), à défaut en anglais, à défaut
155
+ dans n'importe quelle langue disponible. Dans une langue, une checklist externe est
156
+ préférée à une embarquée validée, elle-même préférée à une en attente."""
157
+ return _best(tuple(reversed(self._checklists)), checklist_id, lang)
158
+
159
+ def __iter__(self) -> Iterator[Checklist]:
160
+ """Les checklists du catalogue, dans leur ordre d'ajout."""
161
+ return iter(self._checklists)
162
+
163
+ def __len__(self) -> int:
164
+ return len(self._checklists)
165
+
166
+
167
+ def _best(
168
+ checklists: Sequence[Checklist], checklist_id: str, lang: str
169
+ ) -> Optional[Checklist]:
170
+ """Voir `Catalog.get` ; à statut égal, la première de `checklists` gagne."""
171
+ wanted = normalize_lang(lang)
172
+ same_id = [checklist for checklist in checklists if checklist.id == checklist_id]
173
+
174
+ def preferred(candidates: Sequence[Checklist]) -> Optional[Checklist]:
175
+ return min(candidates, key=lambda c: _PRIORITY[c.status], default=None)
176
+
177
+ return (
178
+ preferred([c for c in same_id if c.lang == wanted])
179
+ or preferred([c for c in same_id if c.lang == "en"])
180
+ or preferred(same_id)
181
+ )
182
+
183
+
184
+ def normalize_lang(lang: str) -> str:
185
+ """Code de langue d'une étiquette de langue ou d'une locale (`fr_FR.UTF-8` → `fr`)."""
186
+ for separator in ("_", "-", ".", "@"):
187
+ lang = lang.split(separator)[0]
188
+ return lang.lower()
gcheck_core/engine.py ADDED
@@ -0,0 +1,28 @@
1
+ """API principale : un moteur de checklists sans état."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import random as _random
6
+ from typing import Callable, Optional
7
+
8
+ from . import evaluator, parser, resolver, selector
9
+ from .types import Answers, AppDefaults, QuizSchema, Verdict
10
+
11
+
12
+ class GcheckEngine:
13
+ """Moteur de checklists, comme `createEngine` en TypeScript et `GcheckEngine` en Rust."""
14
+
15
+ def __init__(self, app_defaults: Optional[AppDefaults] = None) -> None:
16
+ self.defaults = app_defaults if app_defaults is not None else AppDefaults()
17
+
18
+ def prepare(
19
+ self, content: str, random_float: Callable[[], float] = _random.random
20
+ ) -> QuizSchema:
21
+ """Lit, résout et tire les questions d'une checklist YAML ou JSON."""
22
+ schema = parser.parse(content)
23
+ resolved = resolver.resolve(schema, self.defaults)
24
+ return selector.select(resolved, random_float)
25
+
26
+ def evaluate(self, quiz: QuizSchema, answers: Answers) -> Verdict:
27
+ """Évalue les réponses données à un quiz préparé."""
28
+ return evaluator.evaluate(quiz, answers)
@@ -0,0 +1,33 @@
1
+ """Évaluation des réponses et calcul du verdict."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .types import Answers, Issue, QuizSchema, Verdict
6
+
7
+
8
+ def evaluate(quiz: QuizSchema, answers: Answers) -> Verdict:
9
+ """Compare les réponses aux attentes : `pass`, `warning` ou `fail`."""
10
+ issues = [
11
+ Issue(
12
+ question_id=question.id,
13
+ text=question.text,
14
+ answer=answers.get(question.id),
15
+ expected=question.expected,
16
+ severity=question.severity,
17
+ message=question.message,
18
+ )
19
+ for question in quiz.questions
20
+ if answers.get(question.id) is not question.expected
21
+ ]
22
+
23
+ has_blocking = any(issue.severity == "blocking" for issue in issues)
24
+ has_warning = any(issue.severity == "warning" for issue in issues)
25
+ verdict = "fail" if has_blocking else "warning" if has_warning else "pass"
26
+
27
+ return Verdict(
28
+ verdict=verdict,
29
+ issues=issues,
30
+ success_message=quiz.success_message if verdict == "pass" else None,
31
+ chapeau_error=quiz.chapeau_error if has_blocking else None,
32
+ chapeau_warning=quiz.chapeau_warning if has_warning else None,
33
+ )
gcheck_core/hash.py ADDED
@@ -0,0 +1,81 @@
1
+ """Hash de contenu d'une checklist, pour détecter qu'elle a changé depuis une passation.
2
+
3
+ Même forme canonique que les moteurs TypeScript et Rust : JSON compact, clés dans un ordre fixe,
4
+ champs absents omis, puis SHA-256 en hexadécimal minuscule. Les trois moteurs produisent donc la
5
+ même valeur pour la même checklist, quel que soit son format (YAML ou JSON) ou sa mise en forme.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import hashlib
11
+ import json
12
+ from typing import Any, Optional
13
+
14
+ from .types import InputCategory, InputQuestion, InputSchema
15
+
16
+
17
+ def checklist_hash(schema: InputSchema) -> str:
18
+ """Hash de contenu de la checklist : SHA-256 de sa forme canonique."""
19
+ canonical = json.dumps(_canonical(schema), ensure_ascii=False, separators=(",", ":"))
20
+ return hashlib.sha256(canonical.encode("utf-8")).hexdigest()
21
+
22
+
23
+ def _canonical(schema: InputSchema) -> dict[str, Any]:
24
+ root: dict[str, Any] = {
25
+ "fileFormatVersion": schema.file_format_version,
26
+ "lang": schema.lang,
27
+ "title": schema.title,
28
+ }
29
+ _put(root, "description", schema.description)
30
+ _put(root, "preamble", schema.preamble)
31
+ if schema.labels is not None:
32
+ labels: dict[str, Any] = {}
33
+ _put(labels, "yes", schema.labels.yes)
34
+ _put(labels, "no", schema.labels.no)
35
+ root["labels"] = labels
36
+ _put(root, "checklistVersion", schema.checklist_version)
37
+ _put(root, "sorting", schema.sorting)
38
+ _put(root, "categoryPick", schema.category_pick)
39
+ _put(root, "globalPick", schema.global_pick)
40
+ if schema.defaults is not None:
41
+ defaults: dict[str, Any] = {}
42
+ _put(defaults, "expected", schema.defaults.expected)
43
+ _put(defaults, "severity", schema.defaults.severity)
44
+ _put(defaults, "message", schema.defaults.message)
45
+ root["defaults"] = defaults
46
+ _put(root, "chapeauError", schema.chapeau_error)
47
+ _put(root, "chapeauWarning", schema.chapeau_warning)
48
+ _put(root, "successMessage", schema.success_message)
49
+ root["categories"] = [
50
+ _canonical_category(category_id, category)
51
+ for category_id, category in schema.categories.items()
52
+ ]
53
+ return root
54
+
55
+
56
+ def _canonical_category(category_id: str, category: InputCategory) -> dict[str, Any]:
57
+ canonical: dict[str, Any] = {"id": category_id}
58
+ _put(canonical, "label", category.label)
59
+ _put(canonical, "expected", category.expected)
60
+ _put(canonical, "severity", category.severity)
61
+ _put(canonical, "message", category.message)
62
+ _put(canonical, "pick", category.pick)
63
+ canonical["questions"] = [
64
+ _canonical_question(question_id, question)
65
+ for question_id, question in category.questions.items()
66
+ ]
67
+ return canonical
68
+
69
+
70
+ def _canonical_question(question_id: str, question: InputQuestion) -> dict[str, Any]:
71
+ canonical: dict[str, Any] = {"id": question_id, "text": question.text}
72
+ _put(canonical, "expected", question.expected)
73
+ _put(canonical, "severity", question.severity)
74
+ _put(canonical, "message", question.message)
75
+ return canonical
76
+
77
+
78
+ def _put(target: dict[str, Any], key: str, value: Optional[Any]) -> None:
79
+ """Ajoute un champ à la forme canonique, sauf si la checklist ne le renseigne pas."""
80
+ if value is not None:
81
+ target[key] = value