Écrire et exploiter les politiques
Tout ce que la passerelle refuse ou autorise vient d’un seul artefact signé : le bundle de politiques. Cette page explique comment le rédiger, le modifier sans risque, et le récupérer après la perte d’une machine. Pour les raisons de cette conception, voir Politique & catalogue d’outils.
Deux propriétés gouvernent tout le flux de travail, et les deux sont délibérées :
- La source de vérité est un checkout git, pas un serveur en fonctionnement. Les politiques sont des fichiers texte revus comme du code, compilés en un bundle signé estampillé du sha du commit. Rien n’est édité sur place sur un hôte de production.
- La compilation est déterministe octet pour octet. Le même arbre, la même ref, la même clé produisent le même bundle. Une release peut donc être reproduite, comparée par empreinte, et reconstruite de zéro après un sinistre.
L’arbre source
Section intitulée « L’arbre source »my-policies/ ← a git repository├── policies/│ ├── 00-base.cedar ← concatenated in lexicographic file order│ ├── 10-finance.cedar│ └── obsign.cedarschema ← generated; what your editor type-checks against├── tools.json ← the signed catalogue├── fail-mode.json ← what to do when the engine cannot decide├── identity/ ← optional: who may mint identities│ ├── provider.json ← issuer + audience│ └── jwks.json ← the IdP's public keys└── deployment/ ← optional: enrolled gateway origin keys ├── origin-keys.json └── attestation.json ← optional: TPM enrollments (v3)Seuls policies/ et tools.json sont obligatoires. Les préfixes numériques
dans les noms de fichiers sont une convention. Comme les fichiers sont
concaténés dans l’ordre lexicographique, ils gardent cet ordre lisible et
stable. Seuls les fichiers *.cedar sont lus comme des règles, si bien que le
schéma généré peut sans dommage se trouver à côté d’eux.
Chaque champ de chacun de ces fichiers (son type, son caractère obligatoire ou non, sa valeur par défaut, les valeurs qu’accepte une énumération, et l’erreur de compilation que produit une faute) est documenté dans L’arbre source des politiques, fichier par fichier. Cette page reste le récit : comment penser les règles et comment les exploiter.
Le modèle que voient vos règles
Section intitulée « Le modèle que voient vos règles »Une règle est un permit/forbid portant sur un principal, une
action, une ressource, gardé par une clause when sur le contexte.
Tout ce que décrit cette section existe aussi sous une forme lisible par une
machine, comme un schéma Cedar dérivé de votre propre tools.json :
obsign-control schema --source ./my-policies # → policies/obsign.cedarschemaCommittez-le. obsign-control compile vérifie le typage de chaque règle
contre lui et refuse de signer une règle qui lit quelque chose que la
passerelle n’expose pas. Pointer votre éditeur sur ce même fichier vous
donne ce contrôle au fil de la frappe. Voir Votre éditeur
plus bas. Régénérez-le à chaque changement de tools.json ; --check échoue
au lieu d’écrire, ce qui est exactement ce que vous voulez en CI.
Les principaux. User::"<subject>", avec Group::"<name>" comme
parents, si bien que principal in Group::"dba" fonctionne, groupes
imbriqués compris. Le sujet et les groupes viennent du jeton vérifié, mappés
par la table de claims du bundle d’identité (les formes Keycloak, Entra et
Okta fonctionnent sans aucune configuration). L’entité User ne porte
aucun attribut : les permissions s’expriment par appartenance à un groupe
ou par scopes, jamais par principal.<something>.
Les actions.
| Action | Déclenchée par | Type de ressource |
|---|---|---|
tool_call | tools/call | Tool::"<name>" |
resource_read | resources/read, resources/subscribe, resources/unsubscribe, et completion/complete sur un modèle de ressource | Resource::"<uri>" |
prompt_get | prompts/get, et completion/complete sur un prompt | Prompt::"<name>" |
sampling | sampling/createMessage initié par le serveur | Server::"mcp://wrapped" |
elicitation | elicitation/create initié par le serveur | Server::"mcp://wrapped" |
notify | notifications/message initié par le serveur | Server::"mcp://wrapped" |
Server::"mcp://wrapped" est un littéral fixe, et non le nom du serveur du
déploiement : ces canaux sont accordés par serveur, et la requête ne nomme
aucun objet stable sur lequel s’indexer. Le --server-id de l’opérateur
parvient aux règles sous la forme de context.server et entre dans chaque
enregistrement, mais aucune ressource n’est indexée dessus. Rien de ce
qu’un opérateur tape sur une ligne de commande ne décide un verdict que le
bundle signé n’avait pas déjà décidé. Filtrez sur context.server si vous
voulez une règle qui ne s’applique qu’à un seul déploiement ; n’attendez pas
qu’une entité Server::"mcp://crm.internal" existe.
Les attributs de ressource. Seul Tool porte des attributs, et
seulement parce que le catalogue le décrit : resource.destructive (bool),
resource.server (chaîne), resource.required_scope (chaîne, vide en
l’absence de scope). Resource et Prompt n’en ont aucun ; le serveur forge
ces URI à l’exécution, il n’y a donc rien de signé à y rattacher. Arbitrez
sur l’identifiant via context.target.
Le contexte, disponible pour chaque règle :
| Attribut | Type | Signification |
|---|---|---|
context.env | chaîne | environnement déclaré à la passerelle (--env : prod, staging, …) |
context.server | chaîne | le serveur encapsulé tel que l’opérateur l’a nommé (--server-id) ; descriptif, jamais une clé de ressource |
context.session | chaîne | identifiant de session, également l’identifiant de la chaîne d’audit |
context.scopes | ensemble de chaînes | scopes accordés par la délégation |
context.target | chaîne | URI de ressource ou nom de prompt (actions de capacité) |
context.principal_kind | chaîne | human, delegated_human ou machine |
context.has_human_delegation | bool | un humain identifiable se trouve à la racine de la chaîne |
context.delegation_depth | long | nombre de sauts de délégation (0 sans claim act) |
context.actor_chain | ensemble de chaînes | la chaîne RFC 8693 attestée |
context.args.<name> | selon le catalogue | arguments d’appel déclarés — voir ci-dessous |
Écrire des règles
Section intitulée « Écrire des règles »Chaque règle a besoin d’un @id, et la compilation refuse une règle qui n’en
a pas. Cet identifiant n’est pas décoratif : c’est lui qui entre dans
l’enregistrement d’audit comme motif de l’autorisation ou du refus d’un
appel. Une règle anonyme produit une décision inexplicable, ce qui ruine
l’objet même du produit.
// Deny wins over permit, always. Start with what must never happen.@id("forbid_destructive_prod")forbid (principal, action == Action::"tool_call", resource)when { resource.destructive && context.env == "prod" };
// Permission by scope, driven by the catalogue: one rule covers every tool// that declares a required_scope.@id("allow_scoped")permit (principal, action == Action::"tool_call", resource)when { resource.required_scope != "" && context.scopes.contains(resource.required_scope)};
// Permission by group (RBAC), narrowed by environment.@id("allow_dba_nonprod")permit (principal in Group::"dba", action == Action::"tool_call", resource)when { context.env != "prod" };
// Nothing irreversible without a human behind the agent. The distinction// comes from the token: a client_credentials token has no human at the root.@id("forbid_robot_destructive")forbid (principal, action == Action::"tool_call", resource)when { resource.destructive && !context.has_human_delegation };
// Resource families, matched on the identifier.@id("allow_public_docs")permit (principal, action == Action::"resource_read", resource)when { context.target like "docs://public/*" };
// Server-initiated channels are default-deny like everything else.@id("allow_sampling_for_support")permit (principal in Group::"support", action == Action::"sampling", resource);Cedar est en refus par défaut : un acte que personne n’autorise est refusé.
Vous n’avez jamais besoin d’un forbid fourre-tout, et vous ne devriez pas
en écrire un. Il fait paraître conditionnel chaque permit qui suit, alors
qu’il ne l’est pas.
Le catalogue (tools.json)
Section intitulée « Le catalogue (tools.json) »Un outil absent du catalogue est refusé avant que Cedar ne s’exécute : la passerelle ne relaie pas ce qu’elle ne sait pas décrire. Le catalogue est aussi ce qui rend possibles les règles génériques, en attachant à chaque outil des métadonnées revues.
[ { "name": "delete_production_db", "server": "mcp://db", "destructive": true, "required_scope": "db:admin" }, { "name": "send_message", "server": "mcp://chat", "required_scope": "chat:write", "policy_args": [ { "name": "channel", "kind": "string" }, { "name": "amount_cents", "kind": "long", "default": 0 } ] }]destructive et required_scope sont à vous de définir ; le moteur ne fait
que les exposer. Marquer un outil comme destructeur ne coûte rien et permet à
une seule règle de protéger d’un coup tous les outils dangereux, y compris
ceux ajoutés plus tard.
Les arguments (obsign-policy/2)
Section intitulée « Les arguments (obsign-policy/2) »policy_args déclare quels arguments d’appel la politique peut lire. Cette
liste d’autorisation est une frontière de confidentialité : ce qui n’est
pas déclaré n’atteint jamais le moteur, et le journal conserve
args_hash, jamais les valeurs.
@id("support_channel_only")forbid (principal, action == Action::"tool_call", resource == Tool::"send_message")when { context.args.channel != "#support" };| Champ | Signification |
|---|---|
name | le nom sous context.args |
kind | string, long (entiers uniquement — les flottants sont refusés, jamais arrondis), bool, string_set |
at | pointeur JSON dans les arguments de l’appel ; vaut /<name> par défaut |
default | injecté lorsque l’appel omet l’argument |
context.args est un espace de noms unique pour tout le catalogue, chaque
appel d’outil étant la même action Cedar, si bien que deux outils ne peuvent
pas donner deux types à un même nom. Déclarer amount en long sur un outil
et en string sur un autre est une erreur de compilation qui nomme les deux ;
renommez-en un, et servez-vous de at si le nom sur le fil doit rester tel
quel.
Un argument déclaré sans valeur par défaut est obligatoire : un appel qui l’omet est refusé avant que Cedar ne s’exécute. C’est le sens sûr. Une règle qui lit un champ absent échouerait de toute façon en mode fermé, mais avec un message d’erreur bien pire.
Le mode de défaillance (fail-mode.json)
Section intitulée « Le mode de défaillance (fail-mode.json) »Ce qui se passe quand le moteur ne peut pas décider (un bundle illisible, ou une règle qui lève une erreur d’évaluation) :
{ "default": "closed", "tools": { "search_docs": "open" } }La valeur par défaut est closed, et un client qui veut autre chose le
déclare explicitement, de sorte que cela apparaisse dans une pull request.
Par outil, parce qu’il n’existe pas de bonne réponse universelle : bloquer
une recherche en lecture seule casse la production pour rien, laisser passer
une suppression est indéfendable.
Une dégradation n’est jamais silencieuse : un appel autorisé sous une règle
en défaillance ouverte est enregistré comme AllowFailOpen, jamais comme un
Allow franc.
Votre éditeur
Section intitulée « Votre éditeur »Cedar est un langage qui a déjà un éditeur. AWS publie l’extension Cedar
pour VS Code
(cedar-policy.vscode-cedar), bâtie sur le même moteur Cedar 4.x auquel
Obsign se lie : coloration syntaxique, formatage, plan de vos règles, aller à
la définition sur les types d’entités et les noms d’actions ; et, dès qu’elle
voit un schéma, pour l’essentiel la validation qu’exécute
obsign-control compile, en direct, au fil de la frappe. (Pour l’essentiel,
pas exactement : l’extension est stricte sur deux formes d’expression que
compile accepte avec un avertissement ; voir Tester avant de
déployer. Elle penche vers le refus, jamais vers
l’acceptation, si bien qu’une règle qui lui plaît est toujours une règle
signable.)
C’est ce dernier point qui justifie que le schéma soit un fichier committé.
Générez-le, puis pointez l’extension dessus, dans .vscode/settings.json, à
l’intérieur du dépôt de politiques :
{ "cedar.schemaFile": "policies/obsign.cedarschema", "cedar.autodetectSchemaFile": true}Committez-le aussi. Un collègue qui clone le dépôt obtient une configuration qui fonctionne sans rien à régler, et c’est tout l’intérêt.
Concrètement, ce que vous y gagnez : context.enviroment et
principal.department sont soulignés en rouge plutôt que découverts en
production sous la forme d’un événement de mode de défaillance ;
context.args. complète avec les arguments que votre catalogue déclare
réellement, et leurs types ; Server::"mcp://crm.internal" est refusé, parce
que la seule entité de ce type est le littéral fixe.
Deux choses à savoir :
- L’éditeur ne fait pas autorité. Il lit le schéma sur le disque, qui est
un fichier généré : s’il est périmé, l’extension se trompe avec aplomb.
obsign-control schema --source . --checken CI est le garde-fou, et le chemin de signature (compile) régénère lui-même le modèle depuistools.jsonet ne fait jamais confiance au fichier. - Sites air-gap : emportez le
.vsix. Le Marketplace n’est pas joignable depuis un réseau cloisonné. Téléchargez une fois le paquet de l’extension, transportez-le avec le reste, et installez-le aveccode --install-extension cedar-*.vsix. Elle ne fait aucun appel réseau de son côté ; la validation est locale.
Ni l’extension ni le schéma ne sont nécessaires pour rédiger des politiques :
compile attrape les mêmes erreurs, et c’est lui qui décide. L’éditeur ne
fait que ramener la découverte de quelques minutes à quelques secondes.
Compiler, publier, déployer
Section intitulée « Compiler, publier, déployer »# Regenerate the schema after any change to tools.json, and commit it.obsign-control schema --source ./my-policies
# Compile only — signed artifacts in ./out, nothing published.obsign-control compile --source ./my-policies \ --key ./ops-key.hex --key-id ops-2026 --out ./out
# Compile and publish an immutable release the gateways read.obsign-control publish --source ./my-policies \ --key ./ops-key.hex --key-id ops-2026 --dist /srv/obsign/distschema n’a besoin d’aucune clé de signature : il dérive le modèle depuis
tools.json et écrit un fichier, rien de plus. En CI, lancez-le avec
--check ; il n’écrit rien et sort avec un code non nul si le schéma committé
ne correspond plus au catalogue, ce qui est le seul moyen d’empêcher un schéma
périmé d’induire un éditeur en erreur en silence.
--key-id vaut ops-key par défaut. Nommez-le tout de même : l’identifiant
est lié pour de bon à ce matériel de clé, c’est donc lui qu’une rotation doit
changer. Une publication qui garde l’identifiant alors que le fichier de clé
a changé est refusée.
Le libellé de version vaut par défaut le sha court de HEAD, et la
compilation refuse d’estampiller ce sha sur un arbre de travail modifié :
une citation policies@<sha> dans un enregistrement d’audit doit désigner
les octets que ce commit contient. Utilisez --label pour un arbre qui n’est
pas dans git.
policies/obsign.cedarschema est exempté de ce refus, et lui seul : le schéma
est dérivé, ses octets n’entrent jamais dans le bundle signé, et compile
reconstruit le modèle depuis tools.json plutôt que de lire le fichier. Les
deux commandes ci-dessus s’enchaînent donc (régénérer, puis compiler) sans
commit entre les deux. Une règle .cedar modifiée reste, elle, un arbre de
travail modifié.
Le répertoire de distribution :
dist/├── policy-bundle.json ← current, atomically replaced├── identity-bundle.json ← current├── deployment-bundle.json ← current (when the tree has deployment/)├── manifest.json ← current, signed├── trusted-keys.json ← accumulated ops public keys└── releases/<version>/ ← immutable history, one directory per versionPublier deux fois le même arbre est idempotent. Un répertoire de version est écrit une seule fois et jamais réécrit ; le rollback consiste à republier un sha plus ancien. Un identifiant de clé ne peut pas être relié à un autre matériel de clé : une rotation signifie un nouvel identifiant.
Un changement de politique exige un redémarrage de la passerelle
Section intitulée « Un changement de politique exige un redémarrage de la passerelle »La passerelle lit le bundle de politiques une seule fois, au démarrage,
et en vérifie la signature contre --trusted-keys avant de le charger.
Publier un nouveau bundle ne change rien au comportement d’une passerelle en
cours d’exécution : redémarrez-la (ou faites tourner vos conteneurs) pour que
le changement prenne effet.
C’est l’inverse du bundle d’identité, qui, lui, est relu à l’exécution :
une rotation de clé de l’IdP ne doit pas exiger de redémarrage, et chaque
rechargement, appliqué ou rejeté, entre dans le journal sous la forme d’un
enregistrement config_reload.
Planifiez vos changements en conséquence : une mise en production de politique est un déploiement.
L’ordre compte la première fois que vous déclarez des arguments
Section intitulée « L’ordre compte la première fois que vous déclarez des arguments »Le plan de contrôle émet le format de bundle /2 dès qu’un outil déclare
policy_args, et une passerelle non mise à niveau refuse un bundle /2 au
démarrage plutôt que d’appliquer en silence moins que ce que le bundle
prescrit. Donc : mettez d’abord à niveau toutes les images de passerelle,
publiez ensuite le bundle qui déclare des arguments. Une flotte qui ne
déclare jamais d’arguments continue de recevoir /1 et n’a besoin de rien.
Tester avant de déployer
Section intitulée « Tester avant de déployer »Compilez d’abord. La plupart des erreurs sont des erreurs de compilation,
par conception : une règle sans @id, un outil en double, une surcharge de
fail-mode nommant un outil inexistant, un JWKS inutilisable, et toute règle
qui lit un attribut que le modèle ne porte pas.
Cette dernière catégorie était autrefois invisible jusqu’en production. Une
règle qui dit when { principal.department == "eng" } s’analyse, compile,
part en production, puis lève une erreur d’évaluation à chaque appel qu’elle
garde, ce qui bascule sur le mode de défaillance. Sous "default": "open",
cela donne un forbid qui n’interdit jamais rien, et le journal enregistre
AllowFailOpen pour une règle que son auteur croit appliquée. Le contrôle de
typage refuse de la signer, quel que soit le mode de défaillance : celui-ci
répond à « le moteur n’a pas pu décider », il ne répond pas à « cette
règle ne veut rien dire ».
Ce refus signifie toujours que la règle ne fait rien aujourd’hui : elle lève une erreur à chaque appel qu’elle garde, si bien que la supprimer ou la corriger ne change aucune application en vigueur. Cela compte quand vous tombez dessus en plein incident : le dépôt n’est pas bloqué, la règle était déjà inerte.
Ce contrôle est la validation stricte propre à Cedar, avec deux constats délibérément rétrogradés en avertissements, parce que le mode strict contraint aussi la forme d’une expression pour que les politiques restent analysables automatiquement, et une règle peut échouer là-dessus tout en s’évaluant parfaitement :
// Accepted, and it works. Strict Cedar wants a literal in `ip()`.@id("private_ranges_only")forbid (principal, action == Action::"tool_call", resource == Tool::"connect")when { !ip(context.args.src).isInRange(ip("10.0.0.0/8")) };Il en va de même pour un littéral d’ensemble vide []. Les deux compilent,
les deux sont affichés sous la forme [control] warning: …, et aucun des deux
n’est silencieux. L’alternative aurait été de supprimer une capacité qui
fonctionne : like "10.*" n’est pas équivalent à un CIDR, il ne sait pas
exprimer une frontière /12 ou /24.
Une conséquence pour l’éditeur : l’extension Cedar valide strictement et
soulignera ces deux formes là où obsign-control compile les accepte. Le
désaccord ne va jamais que dans ce sens (l’éditeur est plus strict que le
signataire, jamais l’inverse), si bien qu’une règle que votre éditeur accepte
est toujours une règle signable.
Exercez ensuite les vrais binaires contre un WAL jetable, ce que fait le démarrage rapide. Provoquez les appels qui vous intéressent, et lisez les décisions dans le journal :
obsign-ledger export --wal /tmp/t/wal --chain-id test \ --store /tmp/t/ledger --out /tmp/t/evidence.jsonpython3 - <<'EOF'import jsonfor r in json.load(open('/tmp/t/evidence.json'))['records']: p = r['payload'] if p.get('kind') == 'decision': print(p['outcome'], p.get('policy_id'), p.get('reason'))EOFLa colonne policy_id est tout l’intérêt : elle dit quelle règle a décidé,
si bien qu’un appel autorisé par une règle à laquelle vous ne pensiez pas se
voit immédiatement.
Sauvegarde et reprise
Section intitulée « Sauvegarde et reprise »La question à laquelle il faut répondre n’est pas « puis-je redémarrer la
passerelle » mais « puis-je encore montrer, dans deux ans, ce que contenait
policies@a3f19c2 ? » Chaque enregistrement de décision cite la version du
bundle qui l’a produite. Perdez l’arbre qui a produit cette version, et la
piste d’audit pointe vers quelque chose que plus personne ne peut relire.
| Artefact | Reproductible ? | Comment le protéger |
|---|---|---|
| Arbre source des politiques | — c’est la source | Un remote git. Poussez-le. C’est ça, la sauvegarde. |
| Clé de signature ops | Non | Le seul secret irremplaçable — voir ci-dessous |
Fichiers courants de dist/ | Oui, depuis la source + la clé | Recompilez ; déterministe, identique à l’octet près |
dist/releases/<sha>/ | Oui, si vous avez gardé l’arbre et la clé | Sauvegardez-le quand même : c’est le relevé de ce qui a réellement été publié, et c’est petit |
trusted-keys.json | Accumulé au fil du temps | Sauvegardez-le avec votre configuration ; une passerelle en a besoin pour faire confiance aux bundles |
La clé de signature ops
Section intitulée « La clé de signature ops »La perdre n’invalide rien de ce qui est déjà signé : la vérification
utilise la moitié publique, qui vit dans trusted-keys.json et à l’intérieur
de chaque pack. Ce que vous perdez, c’est la capacité à signer de nouveaux
bundles.
La reprise est une rotation : générez une nouvelle clé sous un nouvel
identifiant de clé (relier un ancien identifiant est refusé),
republiez, et distribuez le trusted-keys.json mis à jour à toute
la flotte. Prévoyez que ce soit un changement de configuration à l’échelle de
la flotte : c’est exactement pourquoi la clé a sa place dans un KMS/HSM en
production, et pourquoi la forme « graine dans un fichier » est documentée
comme réservée au développement.
Organisation recommandée
Section intitulée « Organisation recommandée »- L’arbre source vit dans un dépôt git doté d’au moins un remote. Pas « une copie sur la VM » : un remote, sur une infrastructure qui tombe en panne indépendamment. Sur un site air-gap, cela veut dire une deuxième machine et une étape de miroir documentée.
- La clé ops ne vit jamais uniquement sur la machine qui l’utilise. HSM en production. Si une graine dans un fichier est inévitable pendant un pilote, gardez-en une copie hors ligne sous scellé, et traitez sa perte comme un exercice de rotation plutôt que comme une catastrophe.
dist/est sauvegardé avec votre configuration — petit, peu changeant, et il permet de répondre à « qu’est-ce qui était publié à cette date ? » sans reconstruction.- Le WAL, le store du ledger et les packs de preuves ont leur propre procédure : Sauvegarde, restauration & rétention. Ceux-là protègent la preuve ; cette page protège la capacité à l’expliquer.
Reconstruire après la perte de la VM
Section intitulée « Reconstruire après la perte de la VM »git clone <remote> my-policies && cd my-policiesgit checkout <the sha an audit record cites> # e.g. a3f19c2obsign-control compile --source . \ --key ./ops-key.hex --key-id ops-2026 --out ./rebuiltsha256sum rebuilt/policy-bundle.jsonL’identifiant de clé doit être celui qui a signé la release d’origine, et non celui du jour : il fait partie de ce qui a été signé, donc une reconstruction sous un autre identifiant produit un autre artefact et la comparaison d’empreinte ci-dessous ne prouve rien.
Parce que la compilation est déterministe, cette empreinte correspond à l’artefact publié à l’origine, ce qui transforme « nous pensons que la règle disait ceci » en une affirmation vérifiable. Faites-le une fois en exercice, avant d’en avoir besoin : cela prouve que votre remote, la garde de votre clé et vos libellés de version fonctionnent bien ensemble.
Modes de défaillance à connaître
Section intitulée « Modes de défaillance à connaître »| Symptôme | Cause | Que faire |
|---|---|---|
tool "x" absent from signed catalogue | l’outil n’est pas dans tools.json | ajoutez-le et republiez — le refus, c’est la fonctionnalité |
compilation : attribute … not found / attribute … on entity type … not found | une règle lit quelque chose que le modèle n’expose pas (p. ex. principal.permissions) | utilisez context.scopes ou un groupe ; voir le tableau du modèle plus haut |
warning: … extension constructors may not be called with non-literal expressions (accepted: …) | ip(context.args.x) — compile et fonctionne ; votre éditeur le signalera quand même | rien à faire ; c’est une remarque sur la forme stricte de Cedar |
compilation : … is not declared as a valid eid | une règle indexée sur Server::"<your server>" | la clé de ressource est le littéral fixe ; filtrez plutôt sur context.server |
obsign.cedarschema is out of date with tools.json | le catalogue a changé, le schéma non | obsign-control schema --source . puis committez |
evaluation failed, fail-closed: … | une règle qui passe le typage mais lève une erreur sur certaines entrées (dépassement i64) | resserrez l’expression ; un outil aux arguments déclarés refuse plutôt que de basculer en défaillance ouverte |
compilation : règle sans @id | une règle anonyme | nommez-la — l’identifiant est le motif d’audit |
| la compilation refuse le sha | des changements non commités | committez, ou passez --label |
key id "…" already recorded with different key material | le fichier de clé a été remplacé alors que --key-id restait le même | une rotation prend un nouvel identifiant ; l’ancienne clé publique reste dans trusted-keys.json, donc les releases qu’elle a signées se vérifient toujours |
la passerelle refuse de démarrer sur un bundle /2 | passerelle plus ancienne que le format de bundle | mettez d’abord les passerelles à niveau, puis publiez |
| un changement reste sans effet | la politique est lue au démarrage | redémarrez la passerelle |
AllowFailOpen dans le journal | le moteur n’a pas pu décider et le mode de défaillance disait open | corrigez la règle ; la dégradation est visible à dessein |