Skip to content

scellement

scellement

Sceller ce qu'une commande DIT, sans toucher à ce qui la route.

26 août 2026. Les enveloppes de commande étaient signées mais pas chiffrées : personne ne pouvait en fabriquer une sans la clé privée, mais quiconque était sur le même Wi-Fi lisait le verbe et ses arguments — titres de notification, adresses ouvertes, noms de fichiers. Sur la loopback c'était sans objet ; le maillage a été ouvert au réseau le jour même.

Aucun champ nouveau sur le fil. Le triplet tool / arguments / requiresConfirmation est remplacé par une sentinelle et un blob, dans les champs qui existent déjà. C'est tout le mécanisme, et ce n'est pas un raffinement : un champ ajouté au niveau supérieur ne serait pas reproduit par RemoteCommand.from_dict, qui lit une liste fixe de clés — le récepteur réémettrait donc une enveloppe amputée et la signature tomberait. Ici il n'y a rien à arracher.

Une SECONDE paire X25519 statique, jamais une conversion. L'en-tête de coffre.py refuse de convertir Ed25519 → X25519, pour deux raisons qui tiennent : la conversion n'existe pas dans cryptography, l'écrire serait de l'arithmétique de corps à la main — la cryptographie maison qu'interdit le §40 ; et détourner une clé de signature pour de l'accord de clé se paie. On ne convertit rien : on génère. Une clé, un usage.

Statique et non éphémère, et c'est un prix assumé. Une commande est POUSSÉE : un pair endormi ne peut pas fournir de moitié éphémère, et une commande attend jusqu'à QUEUED_TTL_MS (six heures) dans la file. Un design éphémère laisserait donc partir en clair, en silence, précisément le chemin qui compte. Le prix : plus de confidentialité persistante côté destinataire — qui archive le trafic puis vole seal_key déchiffre rétroactivement. Atténué côté émetteur, dont la moitié reste éphémère, et relativisé par le fait que ce voleur tient aussi la clé Ed25519 et la base du maillage.

Sceller PUIS signer. La signature reste la créance et doit rester vérifiable SANS la clé de déchiffrement : un pair révoqué, une enveloppe expirée ou mal adressée sont refusés sur l'identité avant qu'un octet de chiffré ne soit touché — un inconnu du réseau ne peut pas nous faire calculer un X25519 par paquet.

Ce que ce module NE protège pas est écrit dans docs/spatial-mesh/, et la première ligne de cette liste mérite d'être répétée ici : le client mobile ne publie aucune clé, donc rien ne lui est scellé. L'exclusion est structurelle — on ne scelle que vers un pair dont on DÉTIENT une clé fraîche — et non une règle qu'il faudrait penser à respecter.

Classes

ScellementExige

Bases: RuntimeError

Le réglage exige le chiffrement et ce pair n'a pas de clé fraîche.

Une exception plutôt qu'un None de plus : sous exige, partir en clair serait exactement la panne silencieuse que ce réglage veut empêcher. L'appelant doit avoir à s'en occuper, pas à y penser.

Functions:

paire_locale

paire_locale()

La paire de scellement de CETTE machine, créée au premier appel.

Écrite par le chemin durci d'identity — dossier 0700, fichier 0600, O_EXCL, O_NOFOLLOW — et non par une copie de ce durcissement : deux copies divergent.

Source code in src/diapason/mesh/scellement.py
def paire_locale():
    """La paire de scellement de CETTE machine, créée au premier appel.

    Écrite par le chemin durci d'``identity`` — dossier 0700, fichier 0600,
    ``O_EXCL``, ``O_NOFOLLOW`` — et non par une copie de ce durcissement :
    deux copies divergent.
    """
    from diapason.mesh.coffre import nouvelle_demi_cle
    from diapason.mesh.identity import identity_dir

    chemin = _chemin(_FICHIER_CLE)
    existante = _lire(_FICHIER_CLE)
    if existante is not None:
        return existante

    if chemin.exists():
        # Le fichier est là mais illisible. CONSTATÉ sur la machine de
        # Carlito le 26 août 2026, à la toute première mise en service : un
        # redémarrage du service a tué le serveur en pleine écriture et
        # laissé vingt-deux octets sur quarante-quatre. La version d'avant
        # retombait alors sur un `FileExistsError` à CHAQUE appel — le
        # scellement était mort, définitivement et sans un mot.
        #
        # On remplace, et on le DIT fort. Remplacer une clé oblige les pairs
        # à réapprendre la nouvelle ; le taire les laisserait sceller vers
        # une clé qui n'ouvre plus rien.
        logger.warning(
            "clé de scellement illisible (%s) : elle est remplacée. Vos "
            "pairs l'apprendront à leur prochaine annonce.",
            chemin.name,
        )
        chemin.unlink(missing_ok=True)

    dossier = identity_dir()
    dossier.mkdir(mode=0o700, parents=True, exist_ok=True)
    neuve = nouvelle_demi_cle()
    try:
        _ecrire_atomiquement(chemin, neuve.privee)
    except FileExistsError:
        # Une autre session l'a écrite entre notre lecture et notre écriture.
        # La sienne fait foi : deux clés pour une machine, ce serait deux
        # machines pour ses pairs.
        relue = _lire(_FICHIER_CLE)
        if relue is None:
            raise
        return relue
    return neuve

paire_precedente

paire_precedente()

La paire d'avant un renouvellement — TANT QU'ELLE EST CONSERVÉE.

Cette dernière proposition n'était qu'une phrase : RETENTION_PRECEDENTE_MS était déclarée, exportée, documentée comme un invariant… et lue par aucun code. La clé précédente survivait en réalité jusqu'au renouvellement SUIVANT — une minute ou dix ans — pendant que l'aide de la commande promettait sept jours à l'utilisateur. Constaté le 26 août 2026, dans du code écrit le jour même.

Et ce n'est pas qu'une question d'exactitude : renouveler existe pour fermer la fenêtre de déchiffrement rétroactif. Une clé précédente qui vit indéfiniment ne la ferme qu'à moitié.

La date du fichier fait foi — elle est posée par le renommage, sans fichier supplémentaire à tenir d'accord avec elle.

Source code in src/diapason/mesh/scellement.py
def paire_precedente():
    """La paire d'avant un renouvellement — TANT QU'ELLE EST CONSERVÉE.

    Cette dernière proposition n'était qu'une phrase : ``RETENTION_PRECEDENTE_MS``
    était déclarée, exportée, documentée comme un invariant… et lue par aucun
    code. La clé précédente survivait en réalité jusqu'au renouvellement
    SUIVANT — une minute ou dix ans — pendant que l'aide de la commande
    promettait sept jours à l'utilisateur. Constaté le 26 août 2026, dans du
    code écrit le jour même.

    Et ce n'est pas qu'une question d'exactitude : ``renouveler`` existe pour
    fermer la fenêtre de déchiffrement rétroactif. Une clé précédente qui vit
    indéfiniment ne la ferme qu'à moitié.

    La date du fichier fait foi — elle est posée par le renommage, sans
    fichier supplémentaire à tenir d'accord avec elle.
    """
    chemin = _chemin(_FICHIER_PRECEDENT)
    if not chemin.exists():
        return None
    try:
        age_ms = _maintenant_ms() - int(chemin.stat().st_mtime * 1000)
    except OSError:
        return None
    if age_ms > RETENTION_PRECEDENTE_MS:
        # Périmée : on l'efface plutôt que de la laisser traîner. Garder une
        # clé privée dont on a décidé qu'elle ne servait plus, c'est garder
        # exactement ce que le renouvellement voulait retirer.
        logger.info("clé de scellement précédente périmée : effacée")
        chemin.unlink(missing_ok=True)
        return None
    return _lire(_FICHIER_PRECEDENT)

kid

kid(publique: bytes) -> str

Huit hexadécimaux qui désignent une clé sans la révéler.

Le destinataire s'en sert pour choisir entre sa clé courante et la précédente, sans essayer les deux à l'aveugle.

Source code in src/diapason/mesh/scellement.py
def kid(publique: bytes) -> str:
    """Huit hexadécimaux qui désignent une clé sans la révéler.

    Le destinataire s'en sert pour choisir entre sa clé courante et la
    précédente, sans essayer les deux à l'aveugle.
    """
    import hashlib

    return hashlib.sha256(publique).hexdigest()[:8]

renouveler

renouveler() -> None

Frapper une clé neuve, en gardant la précédente déchiffrable.

Geste EXPLICITE, jamais automatique : une rotation périodique n'achèterait rien qu'une clé statique n'ait déjà perdu, et créerait une panne différée — un kid périmé refusé des heures après coup.

Source code in src/diapason/mesh/scellement.py
def renouveler() -> None:
    """Frapper une clé neuve, en gardant la précédente déchiffrable.

    Geste EXPLICITE, jamais automatique : une rotation périodique n'achèterait
    rien qu'une clé statique n'ait déjà perdu, et créerait une panne différée —
    un ``kid`` périmé refusé des heures après coup.
    """
    from diapason.mesh.coffre import nouvelle_demi_cle

    courante = _chemin(_FICHIER_CLE)
    precedente = _chemin(_FICHIER_PRECEDENT)
    if courante.exists():
        # DÉPLACER avant d'écrire : le fichier final ne doit jamais exister
        # à moitié, et l'écriture atomique passe par un renommage.
        precedente.unlink(missing_ok=True)
        courante.replace(precedente)
    _ecrire_atomiquement(courante, nouvelle_demi_cle().privee)

oublier_locale

oublier_locale() -> None

Effacer les deux clés. Le prochain appel en frappera une neuve.

Source code in src/diapason/mesh/scellement.py
def oublier_locale() -> None:
    """Effacer les deux clés. Le prochain appel en frappera une neuve."""
    _chemin(_FICHIER_CLE).unlink(missing_ok=True)
    _chemin(_FICHIER_PRECEDENT).unlink(missing_ok=True)

bloc_sceau

bloc_sceau() -> dict[str, Any]

La clé publique de cette machine, signée par son identité.

Ce bloc s'attache à des corps de RÉPONSE qui circulent déjà — celui de /v1/mesh/presence, celui du jumelage — plutôt qu'à une route neuve. Une route de plus coûterait une porte au mur du réseau, un seau de limitation, une régénération d'instantané de contrat, et rendrait faux trois commentaires qui comptent les dix portes. Un corps de réponse ne coûte rien de tout cela.

Il n'est JAMAIS servi à un inconnu : la route qui le porte refuse en 403 avant de répondre quoi que ce soit. Ce dépôt vient de colmater une fuite d'identifiant sur une route ouverte — la remarque n'est pas théorique.

Source code in src/diapason/mesh/scellement.py
def bloc_sceau() -> dict[str, Any]:
    """La clé publique de cette machine, signée par son identité.

    Ce bloc s'attache à des corps de RÉPONSE qui circulent déjà — celui de
    ``/v1/mesh/presence``, celui du jumelage — plutôt qu'à une route neuve.
    Une route de plus coûterait une porte au mur du réseau, un seau de
    limitation, une régénération d'instantané de contrat, et rendrait faux
    trois commentaires qui comptent les dix portes. Un corps de réponse ne
    coûte rien de tout cela.

    Il n'est JAMAIS servi à un inconnu : la route qui le porte refuse en 403
    avant de répondre quoi que ce soit. Ce dépôt vient de colmater une fuite
    d'identifiant sur une route ouverte — la remarque n'est pas théorique.
    """
    from diapason.mesh.identity import device_identity, owner_id
    from diapason.mesh.signed import sign_payload

    return sign_payload(
        {
            "version": SCEAU_VERSION,
            "ownerId": owner_id(),
            "deviceId": device_identity().device_id,
            "sealKey": paire_locale().publique_b64,
            "sentAtMs": _maintenant_ms(),
        },
        _CHAMPS_SCEAU,
    )

lire_bloc_sceau

lire_bloc_sceau(
    corps: Any,
    *,
    registry: Any = None,
    pair_attendu: str = "",
) -> bool

Enregistrer la clé qu'un pair vient de publier. NE LÈVE JAMAIS.

Appelée depuis announce_to, qui promet de ne jamais lever : une annonce qui n'aboutit pas n'est pas une erreur que l'utilisateur doive entendre. Un corps malformé ne doit donc pas remonter d'ici.

Le contrôle qui compte : la clé est rangée sous le device_id que verify_payload RETOURNE, et l'on vérifie qu'il vaut bien celui qu'on interrogeait. Sans cela, un sceau capté chez A et recollé dans une réponse de B écrirait la clé de A dans la ligne de B — et l'on scellerait ensuite vers A ce qui était destiné à B.

Source code in src/diapason/mesh/scellement.py
def lire_bloc_sceau(
    corps: Any, *, registry: Any = None, pair_attendu: str = ""
) -> bool:
    """Enregistrer la clé qu'un pair vient de publier. NE LÈVE JAMAIS.

    Appelée depuis ``announce_to``, qui promet de ne jamais lever : une
    annonce qui n'aboutit pas n'est pas une erreur que l'utilisateur doive
    entendre. Un corps malformé ne doit donc pas remonter d'ici.

    Le contrôle qui compte : la clé est rangée sous le device_id que
    ``verify_payload`` RETOURNE, et l'on vérifie qu'il vaut bien celui qu'on
    interrogeait. Sans cela, un sceau capté chez A et recollé dans une
    réponse de B écrirait la clé de A dans la ligne de B — et l'on scellerait
    ensuite vers A ce qui était destiné à B.
    """
    try:
        from diapason.mesh.identity import device_identity, owner_id
        from diapason.mesh.registry import DeviceRegistry
        from diapason.mesh.signed import SignedRejected, verify_payload

        if not isinstance(corps, dict):
            return False
        bloc = corps.get("sceau")
        if not isinstance(bloc, dict):
            return False

        registre = registry if registry is not None else DeviceRegistry()
        try:
            signataire = verify_payload(
                bloc,
                fields=_CHAMPS_SCEAU,
                version=SCEAU_VERSION,
                registry=registre,
                local_owner_id=owner_id(),
                local_device_id=device_identity().device_id,
                now_ms=_maintenant_ms(),
                subject="publication de clé",
            )
        except SignedRejected as exc:
            logger.debug("bloc de sceau refusé : %s", exc)
            return False

        if pair_attendu and signataire != pair_attendu:
            logger.warning(
                "bloc de sceau signé par %s dans une réponse de %s : ignoré",
                signataire,
                pair_attendu,
            )
            return False

        return bool(
            registre.record_seal_key(
                signataire,
                str(bloc.get("sealKey") or ""),
                int(bloc.get("sentAtMs") or 0),
            )
        )
    except Exception:  # noqa: BLE001 - cette fonction ne lève jamais
        logger.debug("bloc de sceau illisible", exc_info=True)
        return False

mode_de_chiffrement

mode_de_chiffrement() -> str

Ce que la configuration demande. « opportuniste » à défaut.

Source code in src/diapason/mesh/scellement.py
def mode_de_chiffrement() -> str:
    """Ce que la configuration demande. « opportuniste » à défaut."""
    try:
        from diapason.core.config import load_config

        mode = str(load_config().mesh.chiffrement or "").strip().lower()
    except Exception:  # noqa: BLE001 - une config illisible ne casse pas le maillage
        return "opportuniste"
    return mode if mode in {"opportuniste", "exige", "jamais"} else "opportuniste"

doit_sceller

doit_sceller(
    device: Any,
    *,
    registry: Any = None,
    maintenant_ms: int | None = None,
) -> str | None

La clé vers laquelle sceller — ou None pour partir en clair.

LE point de décision, et le seul. Il ne lit QUE trois choses : le réglage, le registre, et l'horloge. Jamais un corps de réponse.

C'est la correction la plus importante du plan. La première version rendait obligatoire une rétrogradation vers le clair quand le pair répondait « je ne connais pas cet outil » — mais cette réponse est un corps HTTP NON SIGNÉ. Un paquet forgé suffisait alors à éteindre le chiffrement, commande par commande, et de façon persistante. Un chiffrement qu'un attaquant coupe à volonté est pire que pas de chiffrement, parce qu'on croit l'avoir.

Le seul repli est donc l'expiration d'un fait SIGNÉ : la clé d'un pair vue il y a plus de FRAICHEUR_MS ne s'emploie plus. Un attaquant qui supprime les publications doit tenir sept jours, pas une seconde — une usure, pas un interrupteur. Et le repli se voit, au lieu de se taire.

Source code in src/diapason/mesh/scellement.py
def doit_sceller(
    device: Any, *, registry: Any = None, maintenant_ms: int | None = None
) -> str | None:
    """La clé vers laquelle sceller — ou None pour partir en clair.

    LE point de décision, et le seul. Il ne lit QUE trois choses : le
    réglage, le registre, et l'horloge. Jamais un corps de réponse.

    C'est la correction la plus importante du plan. La première version
    rendait obligatoire une rétrogradation vers le clair quand le pair
    répondait « je ne connais pas cet outil » — mais cette réponse est un
    corps HTTP NON SIGNÉ. Un paquet forgé suffisait alors à éteindre le
    chiffrement, commande par commande, et de façon persistante. Un
    chiffrement qu'un attaquant coupe à volonté est pire que pas de
    chiffrement, parce qu'on croit l'avoir.

    Le seul repli est donc l'expiration d'un fait SIGNÉ : la clé d'un pair
    vue il y a plus de ``FRAICHEUR_MS`` ne s'emploie plus. Un attaquant qui
    supprime les publications doit tenir sept jours, pas une seconde — une
    usure, pas un interrupteur. Et le repli se voit, au lieu de se taire.
    """
    mode = mode_de_chiffrement()
    if mode == "jamais":
        return None

    from diapason.mesh.registry import DeviceRegistry

    registre = registry if registry is not None else DeviceRegistry()
    device_id = str(
        (device.get("deviceId") if isinstance(device, dict) else device) or ""
    )
    retenue = registre.seal_key_of(device_id) if device_id else None

    if retenue is not None:
        cle, vue_a = retenue
        instant = _maintenant_ms() if maintenant_ms is None else int(maintenant_ms)
        if instant - vue_a <= FRAICHEUR_MS:
            return cle

    if mode == "exige":
        nom = device.get("name") if isinstance(device, dict) else device_id
        raise ScellementExige(
            f"Le chiffrement des commandes est exigé et « {nom} » ne publie "
            "pas de clé de scellement (téléphone, ou version antérieure de "
            "Diapason) : rien ne lui a été envoyé."
        )
    return None

contenu_scelle

contenu_scelle(
    tool: str,
    arguments: dict[str, Any],
    requires_confirmation: bool,
    *,
    cle_pair_b64: str,
    command_id: str,
    associe: bytes,
) -> dict[str, str]

Le triplet, chiffré vers un pair, prêt à occuper arguments.

Le sel de dérivation est command_id — neuf à chaque commande — donc la clé AES est neuve elle aussi, ce qui rend sûr le nonce constant de coffre. Deux garanties indépendantes de fraîcheur : le sel ET la moitié éphémère.

Source code in src/diapason/mesh/scellement.py
def contenu_scelle(
    tool: str,
    arguments: dict[str, Any],
    requires_confirmation: bool,
    *,
    cle_pair_b64: str,
    command_id: str,
    associe: bytes,
) -> dict[str, str]:
    """Le triplet, chiffré vers un pair, prêt à occuper ``arguments``.

    Le sel de dérivation est ``command_id`` — neuf à chaque commande — donc la
    clé AES est neuve elle aussi, ce qui rend sûr le nonce constant de
    ``coffre``. Deux garanties indépendantes de fraîcheur : le sel ET la
    moitié éphémère.
    """
    from diapason.mesh.coffre import cle_de_session, nouvelle_demi_cle, sceller
    from diapason.mesh.identity import canonical_bytes

    demi = nouvelle_demi_cle()
    cle = cle_de_session(demi, cle_pair_b64, command_id, info=_INFO_COMMANDE)
    clair = canonical_bytes(
        {"t": tool, "a": arguments, "c": bool(requires_confirmation)}
    )
    blob = sceller(cle, 0, _rembourrer(clair), associe=associe)

    import base64

    return {
        "s": base64.b64encode(blob).decode("ascii"),
        "e": demi.publique_b64,
        "k": kid(_cle_publique_pair(cle_pair_b64)),
    }

ouvrir_contenu

ouvrir_contenu(
    scelle: dict[str, Any],
    *,
    command_id: str,
    associe: bytes,
) -> tuple[str, dict[str, Any], bool]

Rouvrir un triplet scellé, avec la clé courante ou la précédente.

Le kid désigne laquelle : sans lui, il faudrait les essayer à l'aveugle et un échec ne dirait pas s'il vient de la clé ou du contenu.

Source code in src/diapason/mesh/scellement.py
def ouvrir_contenu(
    scelle: dict[str, Any], *, command_id: str, associe: bytes
) -> tuple[str, dict[str, Any], bool]:
    """Rouvrir un triplet scellé, avec la clé courante ou la précédente.

    Le ``kid`` désigne laquelle : sans lui, il faudrait les essayer à
    l'aveugle et un échec ne dirait pas s'il vient de la clé ou du contenu.
    """
    import base64
    import json

    from diapason.mesh.coffre import cle_de_session, desceller

    if not isinstance(scelle, dict):
        raise ValueError("Contenu scellé illisible.")
    demande = str(scelle.get("k") or "")
    demi = None
    for candidate in (paire_locale(), paire_precedente()):
        if candidate is not None and kid(candidate.publique) == demande:
            demi = candidate
            break
    if demi is None:
        raise ValueError(
            "Cette commande est scellée pour une clé que cette machine n'a plus."
        )

    try:
        blob = base64.b64decode(str(scelle.get("s") or ""), validate=True)
    except Exception as exc:  # noqa: BLE001
        raise ValueError("Contenu scellé illisible.") from exc

    cle = cle_de_session(
        demi, str(scelle.get("e") or ""), command_id, info=_INFO_COMMANDE
    )
    clair = _derembourrer(desceller(cle, 0, blob, associe=associe))
    charge = json.loads(clair.decode("utf-8"))
    if not isinstance(charge, dict):
        raise ValueError("Contenu scellé illisible.")
    outil = str(charge.get("t") or "")
    arguments = charge.get("a")
    if not outil or not isinstance(arguments, dict):
        raise ValueError("Contenu scellé incomplet.")
    return outil, arguments, bool(charge.get("c"))

etat_de_chiffrement

etat_de_chiffrement(
    device: Any, *, registry: Any = None
) -> str

Ce qui arrivera VRAIMENT à la prochaine commande vers ce pair.

Calculé à chaque lecture, jamais stocké : une colonne dirait ce qui a été décidé un jour, pas ce qui se passera tout à l'heure.

CINQ réponses, et il en faut cinq. La première version n'en avait que trois et rangeait sous « CLAIR » des situations opposées : un pair qui ne publie pas de clé, un chiffrement désactivé localement, et un pair auquel plus RIEN n'est envoyé. Le commentaire du code avouait lui-même « CLAIR serait faux, et rassurant à tort » — deux lignes avant de rendre « CLAIR ». Constaté le 26 août 2026.

  • SCELLE : une clé fraîche est détenue, la commande partira chiffrée.
  • CLAIR : aucune clé fraîche. La commande partira lisible par quiconque écoute — c'est le seul état qui décrit une fuite réelle.
  • DESACTIVE : le réglage local vaut « jamais ». Le pair publie peut-être une clé ; c'est nous qui refusons de nous en servir. Envoyer quelqu'un vérifier la machine d'en face serait l'envoyer au mauvais endroit.
  • BLOQUE : le réglage vaut « exige » et ce pair n'a pas de clé. Rien ne lui est envoyé du tout — dire « CLAIR » décrirait un trafic qui n'existe pas.
  • INCONNU : on n'a pas pu le déterminer.

C'est la moitié VISIBLE du repli. Un repli qu'on ne voit pas est celui qu'on ne corrige jamais — mais un repli mal nommé envoie le chercher ailleurs, ce qui n'est pas mieux.

Source code in src/diapason/mesh/scellement.py
def etat_de_chiffrement(device: Any, *, registry: Any = None) -> str:
    """Ce qui arrivera VRAIMENT à la prochaine commande vers ce pair.

    Calculé à chaque lecture, jamais stocké : une colonne dirait ce qui a été
    décidé un jour, pas ce qui se passera tout à l'heure.

    CINQ réponses, et il en faut cinq. La première version n'en avait que
    trois et rangeait sous « CLAIR » des situations opposées : un pair qui
    ne publie pas de clé, un chiffrement désactivé localement, et un pair
    auquel plus RIEN n'est envoyé. Le commentaire du code avouait lui-même
    « CLAIR serait faux, et rassurant à tort » — deux lignes avant de rendre
    « CLAIR ». Constaté le 26 août 2026.

    * ``SCELLE``    : une clé fraîche est détenue, la commande partira chiffrée.
    * ``CLAIR``     : aucune clé fraîche. La commande partira lisible par
      quiconque écoute — c'est le seul état qui décrit une fuite réelle.
    * ``DESACTIVE`` : le réglage local vaut « jamais ». Le pair publie
      peut-être une clé ; c'est nous qui refusons de nous en servir. Envoyer
      quelqu'un vérifier la machine d'en face serait l'envoyer au mauvais
      endroit.
    * ``BLOQUE``    : le réglage vaut « exige » et ce pair n'a pas de clé.
      Rien ne lui est envoyé du tout — dire « CLAIR » décrirait un trafic
      qui n'existe pas.
    * ``INCONNU``   : on n'a pas pu le déterminer.

    C'est la moitié VISIBLE du repli. Un repli qu'on ne voit pas est celui
    qu'on ne corrige jamais — mais un repli mal nommé envoie le chercher
    ailleurs, ce qui n'est pas mieux.
    """
    try:
        if mode_de_chiffrement() == "jamais":
            return "DESACTIVE"
        return "SCELLE" if doit_sceller(device, registry=registry) else "CLAIR"
    except ScellementExige:
        return "BLOQUE"
    except Exception:  # noqa: BLE001
        return "INCONNU"

entete_de_routage

entete_de_routage(command: Any) -> bytes

Les octets qui collent un sceau à SON enveloppe.

Construits depuis les CHAMPS de l'objet, jamais depuis son dictionnaire : confirmationId est conditionnel dans to_dict mais toujours présent dans la dataclasse. Les deux côtés doivent calculer les mêmes octets, et « parfois absent » n'est pas la même chose que « vide ».

Ce qui est ici est authentifié sans être chiffré : déplacer le blob vers une autre commande casse le tag avant même que la signature ait son mot à dire.

Source code in src/diapason/mesh/scellement.py
def entete_de_routage(command: Any) -> bytes:
    """Les octets qui collent un sceau à SON enveloppe.

    Construits depuis les CHAMPS de l'objet, jamais depuis son dictionnaire :
    ``confirmationId`` est conditionnel dans ``to_dict`` mais toujours présent
    dans la dataclasse. Les deux côtés doivent calculer les mêmes octets, et
    « parfois absent » n'est pas la même chose que « vide ».

    Ce qui est ici est authentifié sans être chiffré : déplacer le blob vers
    une autre commande casse le tag avant même que la signature ait son mot
    à dire.
    """
    from diapason.mesh.identity import canonical_bytes

    return canonical_bytes(
        {
            "version": command.version,
            "commandId": command.command_id,
            "ownerId": command.owner_id,
            "originDeviceId": command.origin_device_id,
            "targetDeviceId": command.target_device_id,
            "createdAtMs": command.created_at_ms,
            "expiresAtMs": command.expires_at_ms,
            "nonce": command.nonce,
            "idempotencyKey": command.idempotency_key,
            "confirmationId": command.confirmation_id,
        }
    )

sceller_commande

sceller_commande(command: Any, cle_pair_b64: str) -> Any

La même commande, dont le triplet part chiffré.

Les champs de l'objet gardent le CLAIR ; _extra["scelle"] porte ce qui voyagera. C'est l'invariant décrit au-dessus de to_dict.

Source code in src/diapason/mesh/scellement.py
def sceller_commande(command: Any, cle_pair_b64: str) -> Any:
    """La même commande, dont le triplet part chiffré.

    Les champs de l'objet gardent le CLAIR ; ``_extra["scelle"]`` porte ce
    qui voyagera. C'est l'invariant décrit au-dessus de ``to_dict``.
    """
    from dataclasses import replace

    scelle = contenu_scelle(
        command.tool,
        command.arguments,
        command.requires_confirmation,
        cle_pair_b64=cle_pair_b64,
        command_id=command.command_id,
        associe=entete_de_routage(command),
    )
    return replace(
        command,
        _extra={
            **dict(command._extra or {}),
            "scelle": {
                "tool": SENTINELLE,
                "arguments": scelle,
                # TOUJOURS vrai sur une enveloppe scellée : sinon ce booléen
                # désignerait `desktop.open`, le seul outil du catalogue qui
                # l'exige — c'est-à-dire précisément celui qu'on cache.
                "requiresConfirmation": True,
            },
        },
    )

desceller_commande

desceller_commande(command: Any) -> Any

La commande rouverte, ses champs portant de nouveau le clair.

_extra["scelle"] conserve ce qui a été signé, pour que to_dict réémette l'enveloppe exacte : celle que le récepteur range dans sa file doit encore vérifier sa propre signature.

Source code in src/diapason/mesh/scellement.py
def desceller_commande(command: Any) -> Any:
    """La commande rouverte, ses champs portant de nouveau le clair.

    ``_extra["scelle"]`` conserve ce qui a été signé, pour que ``to_dict``
    réémette l'enveloppe exacte : celle que le récepteur range dans sa file
    doit encore vérifier sa propre signature.
    """
    from dataclasses import replace

    outil, arguments, confirmation = ouvrir_contenu(
        command.arguments,
        command_id=command.command_id,
        associe=entete_de_routage(command),
    )
    return replace(
        command,
        tool=outil,
        arguments=arguments,
        requires_confirmation=confirmation,
        _extra={
            **dict(command._extra or {}),
            "scelle": {
                "tool": command.tool,
                "arguments": command.arguments,
                "requiresConfirmation": command.requires_confirmation,
            },
        },
    )