GLO-4002 2026

Carnet · Iter 3b · consul

CONSUL — Le Consulat ouvre ses portes

Description

En tant que consul, je peux tenir une audience et permettre aux voyageurs de rentrer dans leur dimension (chez eux).

Tel que stipulé dans le Traité, comme consul, je dois assurer le retour des voyageurs dans leur dimension respective.

Je suis un fonctionnaire de vieille école. Je siège en audiences, à mon rythme, et le Traité ne m’autorise pas à m’adresser à l’Aubergiste. Je ne lui téléphonerai pas, je ne lui demanderai rien, et je ne lui dois aucune réponse.

Ce que j’accepte de lui, c’est une chose et une seule : le journal. Chaque nuit, l’auberge consigne ce qui s’est passé, referme le cahier et me le fait porter. Un cahier refermé ne se rouvre plus, et un cahier porté ne se reprend pas.

Je le range sur la pile et je n’y touche pas. Recevoir n’est pas traiter. Vingt cahiers peuvent m’attendre sans que j’en aie lu un seul : je les lis en audience, et pas avant.

⚠️ Les audiences et les nuits n’ont rien à voir. Sept nuits peuvent s’écouler avant qu’une audience ne se tienne ; trois audiences peuvent se suivre sans qu’aucune nuit ne passe. Chaque cahier porte le numéro de sa nuit, alors je sais parfaitement quelle nuit l’auberge vient de terminer. Ça ne me sert qu’à ranger les cahiers dans l’ordre. Je ne compte rien en nuits, et ce que je date, je le date d’une audience.

Ce que le journal contient

Fait Quand l’auberge l’inscrit
TRAVELLER_ADMITTED un séjour commence, le voyageur est arrivé
TRAVELLER_LEFT un voyageur s’en va, quelle qu’en soit la raison

Chaque fait porte le numéro d’existence du voyageur. Le fait d’admission porte en plus sa dimension d’origine et sa masse : c’est tout ce que je saurai jamais à propos de ce voyageur.

Les faits d’un cahier sont dans l’ordre où ils ont eu lieu, et cet ordre compte : un voyageur admis puis reparti la même nuit ne se lit que dans ce sens.

Les cahiers arrivent en ordre

Un cahier porte le numéro de sa nuit. Je n’accepte un cahier que si sa nuit est strictement postérieure à celle du dernier cahier reçu. Un cahier plus ancien, ou un cahier dont la nuit m’a déjà été portée, je le refuse à la porte : il n’entre pas sur la pile, et rien de ce qu’il contient ne me parvient (voir critère 10).

👨‍🏫 Note pédagogique. Un vrai système traiterait ce cas : file de réémission, journal idempotent, réconciliation des deux registres. Ici, non : on refuse, on répond 400, et on passe à autre chose. Ça vous économise un mécanisme de reprise dont le projet n’a rien à tirer, et ça vous garantit que les cahiers arrivent dans l’ordre où ils ont été scellés. Ne construisez aucun mécanisme de rejeu : il n’y en a pas dans ce projet. L’ordre croissant des nuits est une précondition sur laquelle vous avez le droit de vous appuyer partout ailleurs.

👨‍🏫 Note pédagogique. « Strictement supérieur » ne veut pas dire « suivant ». Le cahier 8 est accepté après le cahier 3, et les nuits 4 à 7 restent absentes pour toujours. Ce cas n’arrive pas : l’auberge avance d’une nuit à la fois et transmet même les cahiers vides (voir critères 2 et 21). Ne le gérez pas. Pas de détection de trou, pas de réclamation, pas de journal des nuits manquantes.

Les dossiers

À chaque audience, je dépouille les cahiers que je n’ai pas encore lus, du plus ancien au plus récent, et je mets à jour mes dossiers de passage, un par venue de voyageur.

État d’un dossier Ce que ça veut dire
IN_STAY il dort à l’auberge, je n’ai rien à faire
TO_BOARD il est parti de l’auberge et attend son passage
CLOSED il est rentré chez lui

Un dossier retient l’audience où il a été ouvert pour pouvoir baser son ancienneté dessus.

Comme le nombre de nuits entre deux audiences est quelconque, un même lot de cahiers peut contenir l’admission et le départ du même voyageur. Son dossier atteint alors TO_BOARD en une seule audience (voir critère 5).

Un dossier n’est pas une personne

Un dossier suit une venue au Carrefour, pas un voyageur. Rien n’empêche quelqu’un de revenir.

Quand un voyageur embarque, son dossier passe à CLOSED et je le range. Un dossier clos est archivé et ne se rouvre plus jamais : ni son ancienneté, ni ses droits perçus, ni son état ne changeront plus, quoi qu’il arrive au voyageur ensuite. Un TRAVELLER_LEFT ne clôt rien : il fait passer le dossier à TO_BOARD.

S’il revient alors que son dossier précédent est clos, l’auberge l’admet de nouveau, un fait TRAVELLER_ADMITTED me parvient, et j’ouvre un dossier neuf. Ce dossier porte le même numéro d’existence, mais un numéro de venue nouveau, et son ancienneté est celle de l’audience courante. Un habitué de dix venues n’a donc aucune priorité sur un premier arrivant : au Carrefour, la fidélité ne se récompense pas (voir critères 14 à 17 et l’exemple 4).

La réadmission avant la clôture

Rien ne garantit qu’un dossier TO_BOARD corresponde à quelqu’un qui attend encore. L’auberge réadmet qui elle veut, et je n’ai aucun moyen de le savoir ni de le lui demander.

Un fait d’admission n’ouvre donc une venue neuve que si la venue précédente est close. Si elle est encore ouverte, quel que soit son état, le fait met à jour la masse et la dimension de cette venue et la laisse dans son état, TO_BOARD compris (voir critères 15 et 16).

⚠️ Conséquence à connaître : un voyageur réadmis avant que son dossier ait été clos conserve l’ancienneté de son passage précédent. Deux voyageurs au parcours identique n’ont donc pas la même ancienneté selon qu’une audience s’est tenue ou non entre leur départ et leur retour.

Les règles d’entrée d’une dimension

Note: ici une entrée fait référence au passage vers la dimension donc son entrée dans la dimension et non pas au Carrefour.

Je ne décide pas des conditions d’entrées. Une dimension décide seule les quantités, à quel prix, etc. Ce sont ses règles d’entrée, et le Traité ne me donne aucun pouvoir dessus : je les enregistre, je les applique, et je n’en discute pas. Une délégation les dépose à mon guichet quand ça lui chante, parfois sans prévenir, souvent sans explication.

Les règles d’entrée d’une dimension tiennent en deux chiffres :

Règle Ce qu’elle dit
Quota d’entrée combien de voyageurs la dimension accepte de reprendre à chaque ouverture du passage
Tarif de traversée ce que la dimension réclame pour ouvrir le passage, quel que soit le nombre de voyageurs

Une dimension qui n’a jamais rien déposé n’a aucune règle d’entrée. Son passage ne s’ouvre pas, et ses ressortissants attendent. Je le regrette, mais c’est ainsi (voir critère 12).

L’ouverture du passage

À chaque audience, le passage vers chaque dimension pourvue de règles d’entrée s’ouvre exactement une fois, avec le quota d’entrée que cette dimension impose. Les dossiers TO_BOARD de cette dimension embarquent, puis le passage se referme.

⚠️ Ce qui n’a pas servi est perdu. Le passage ne garde rien d’une ouverture à l’autre : un quota de six dont deux ont servi ne donne pas dix la fois suivante, il redonne six (voir critère 9 et l’exemple 3).

J’attribue les entrées aux dossiers les plus anciens d’abord ; à égalité, par numéro d’existence croissant (voir critère 8).

Les droits consulaires

Le tarif de traversée est le prix de l’ouverture, pas le prix d’un siège. Je le divise également entre ceux qui embarquent, arrondi par tête. Je ne perçois donc jamais tout à fait le tarif affiché (voir l’exemple 2).

Et comme le tarif fixé par la Dimension ne dépend pas du nombre de partants, il est nettement plus douloureux quand on part à deux qu’à six. On me le fait remarquer. Je réponds que c’est la dimension qui fixe le tarif, ce qui est vrai et n’a jamais consolé personne.

Déroulement d’une nuit (Auberge)

Conséquences d’une nuit (en ordre)
1. Faire arriver les voyageurs dont le séjour commence cette nuit.
2. Ranger les livraisons attendues.
3. Jeter les denrées périmées.
4. Servir le repas du soir.
5. Faire partir les voyageurs et libérer leurs places.
6. Facturer la nuitée de chaque voyageur ayant passé la nuit.
7. Sceller et transmettre le journal de la nuit.

Déroulement d’une audience (Consulat)

Actions possibles
Déposer les règles d’entrée d’une dimension : son quota d’entrée et son tarif de traversée.
Conséquences d’une audience (en ordre)
1. Dépouiller les cahiers non encore lus.
2. Ouvrir le passage de chaque dimension, avec son quota d’entrée.
3. Attribuer les entrées et faire embarquer.
4. Percevoir les droits consulaires.
5. Refermer les passages ; les quotas non utilisés sont perdus.

Conditions de succès

📎 Ce que « croissant », « ordre alphabétique » et « arrondi » veulent dire exactement : les départages et les arrondis, dans le fonctionnement.

# Description
1 Avant toute exécution, le Consulat se trouve à l’audience 0. Chaque exécution incrémente ce numéro de 1 et le retourne.
2 Une nuit sans rien à consigner produit tout de même un cahier, il est vide, et il est transmis quand même.
3 Un cahier scellé n’est plus jamais modifié, par quoi que ce soit.
4 Les cahiers sont dépouillés en ordre de nuit croissant
5 Un voyageur dont l’admission et le départ figurent dans les cahiers traités à la même audience atteint l’état TO_BOARD en une seule audience.
6 Un dossier retient l’audience où il a été ouvert, et cette valeur ne change jamais.
7 Seuls les dossiers TO_BOARD reçoivent une entrée.
8 Les entrées sont attribuées par ancienneté de dossier croissante, puis par numéro d’existence croissant.
9 Le passage vers une dimension s’ouvre une fois par audience, avec le quota d’entrée que cette dimension impose. Ce quota ne s’accumule jamais : ce qui n’a pas servi est perdu à la fermeture du passage.
10 Un cahier dont le numéro de nuit n’est pas strictement supérieur à celui du dernier cahier reçu est refusé : il n’est pas empilé, aucun de ses faits n’est lu, et le registre du Consulat reste strictement inchangé.
11 Des règles d’entrée déposées entre deux audiences s’appliquent dès l’audience suivante, et remplacent intégralement les précédentes. Déposer plusieurs fois avant la même audience ne laisse subsister que le dernier dépôt. La consultation retourne toujours les règles en vigueur, soit celles appliquées à la dernière audience, jamais un dépôt en attente.
12 Une dimension sans règles d’entrée n’ouvre aucun passage ; ses dossiers TO_BOARD restent TO_BOARD sans qu’aucune erreur ne soit levée.
13 Le tarif de traversée est divisé entre ceux qui embarquent, puis arrondi par voyageur, jamais sur le total.
14 Un voyageur qui a embarqué passe à CLOSED. Un dossier clos est archivé : aucun fait et aucune audience ne le rouvre ni ne le modifie.
15 Un voyageur peut revenir au Carrefour autant de fois qu’il le veut. Un fait d’admission portant sur un voyageur sans venue ouverte ouvre un nouveau dossier, avec un numéro de venue incrémenté et l’ancienneté de l’audience courante.
16 Un fait d’admission portant sur un voyageur dont la venue courante est encore ouverte, quel que soit son état, n’ouvre aucun dossier : il met à jour la masse et la dimension de cette venue et la laisse dans son état, TO_BOARD compris. L’ancienneté de la venue ne change pas.
17 Un même voyageur n’a donc jamais plus d’un dossier non clos. C’est une conséquence des critères 15 et 16, pas une règle à faire respecter séparément.
18 Le Consulat n’émet aucune requête vers l’auberge et ne consulte aucun de ses états. L’auberge n’appelle du Consulat que POST /night-logs, ne consulte aucun de ses états, et ne lit pas le corps de sa réponse.
19 Tant qu’aucune audience n’a été tenue, le rapport de la dernière audience n’existe pas : la ressource retourne 404 en entier, jamais un rapport partiel.
20 Recevoir un cahier ne le dépouille pas. La dernière nuit reçue et la dernière nuit dépouillée sont deux compteurs distincts, et le second ne rattrape le premier qu’en audience.
21 L’auberge transmet chaque cahier exactement une fois, à la fin de la nuit, et ne retransmet jamais. Le déroulement d’une nuit ne dépend en rien de ce que le Consulat a répondu, ni du fait qu’il ait répondu.
22 Les faits d’un cahier sont dépouillés dans l’ordre où ils y figurent.

🖥️ À l’écran

  • Le guichet : tous les dossiers vivants, leur état, leur ancienneté, leur dimension. C’est ce qu’un voyageur vient consulter.
  • Sur un dossier, son numéro de venue et la liste de ses venues antérieures closes. C’est ce qui rend visible qu’un habitué recommence à zéro.
  • Pour chaque dimension : ses règles d’entrée en vigueur, le quota consommé à la dernière ouverture, et ce qui a été perdu à la fermeture. Une dimension sans règles d’entrée doit se distinguer à l’œil d’une dimension au quota nul
  • Ce que la dernière audience a donné : qui est parti, vers où, ce que ça a rapporté.
  • Le numéro d’audience, le numéro de la dernière nuit reçue et celui de la dernière nuit dépouillée, côte à côte. Voir les trois compteurs dériver les uns des autres est la meilleure façon de comprendre qu’ils ne sont pas liés, et que la pile de cahiers en attente n’est pas vide.

API

⚠️ Le Consulat est un système distinct. La route ci-dessous est la seule que l’auberge connaisse, et elle ne va que dans un sens.


✅ Transmettre le journal d’une nuit

POST /night-logs

Appelée par l’auberge à la fin de chaque nuit, comme dernière conséquence du tour. Elle n’est jamais appelée deux fois pour la même nuit.

{
  "night": 5::int,
  "facts": [
    {
      "type": "TRAVELLER_ADMITTED"::string,
      "existenceNumber": "EX-01"::string,
      "dimension": "SALARIA"::string(SALARIA | BOUCANIA | HYLEE | NIMBE | NOCTAMBULIE | TESSELLE),
      "massKg": 100::int
    },
    {
      "type": "TRAVELLER_LEFT"::string,
      "existenceNumber": "EX-04"::string
    }
  ]
}

facts est dans l’ordre où les faits ont eu lieu, et peut être vide.

➡️ HTTP 202 Accepted — le cahier est empilé, et il attend la prochaine audience.

➡️ HTTP 400 Bad Request — le numéro de nuit n’est pas strictement supérieur au dernier reçu

{
  "error": "NIGHT_OUT_OF_ORDER"::string,
  "description": "night 4 is not after last received night 7"::string
}

⚠️ L’auberge ne lit pas cette réponse. Elle scelle, elle envoie, elle passe à la nuit suivante. Le 400 existe pour vos tests, pas pour elle : aucune nuit ne se déroule différemment selon ce que le Consulat renvoie, et rien ne remonte du Consulat vers l’auberge.


✅ Tenir une audience

POST /hearings

➡️ HTTP 200 Ok

{
  "number": 1::int
}

✅ Connaître l’état du Consulat

GET /consulate

➡️ HTTP 200 Ok

{
  "currentHearing": 3::int,
  "lastReceivedNight": 12::int,
  "lastReadNight": 7::int
}

Les cahiers 8 à 12 sont reçus et attendent sur la pile. Ils ne seront lus qu’à l’audience 4.


✅ Déposer les règles d’entrée d’une dimension

PUT /dimensions/<dimension::string(SALARIA | BOUCANIA | HYLEE | NIMBE | NOCTAMBULIE | TESSELLE)>/entry-rules

{
  "entryQuota": 4::int,
  "crossingFee": 500.00::float
}

➡️ HTTP 202 Accepted

Le dépôt est une action : il est accumulé et exécuté au début de l’audience suivante, avant que les passages ne s’ouvrent. Il remplace intégralement les règles d’entrée précédentes de cette dimension.


✅ Consulter les règles d’entrée

GET /dimensions/<dimension::string(SALARIA | BOUCANIA | HYLEE | NIMBE | NOCTAMBULIE | TESSELLE)>/entry-rules

Retourne les règles en vigueur, c’est-à-dire celles appliquées à la dernière audience. Un dépôt reçu depuis n’apparaît pas ici : il n’existe qu’au début de l’audience suivante.

➡️ HTTP 200 Ok

{
  "dimension": "SALARIA"::string(SALARIA | BOUCANIA | HYLEE | NIMBE | NOCTAMBULIE | TESSELLE),
  "entryQuota": 4::int,
  "crossingFee": 500.00::float
}

➡️ HTTP 404 Not Found — aucune règle d’entrée n’est en vigueur pour cette dimension : soit elle n’a jamais rien déposé, soit son premier dépôt attend encore son audience.

{
  "error": "NO_ENTRY_RULES"::string,
  "description": "dimension SALARIA has no entry rules in effect"::string
}

✅ Consulter un dossier

GET /files/<existenceNumber::string>/visits/<visit::int>

Un dossier est identifié par le couple numéro d’existence et numéro de venue. La numérotation des venues commence à 1 et est propre à chaque voyageur.

➡️ HTTP 200 Ok

{
  "existenceNumber": "EX-01"::string,
  "visit": 2::int,
  "dimension": "SALARIA"::string(SALARIA | BOUCANIA | HYLEE | NIMBE | NOCTAMBULIE | TESSELLE),
  "massKg": 100::int,
  "status": "IN_STAY"::string(IN_STAY | TO_BOARD | CLOSED),
  "openedAtHearing": 9::int,
  "boardedAtHearing": null || 12::int,
  "feePaid": null || 166.67::float
}

➡️ HTTP 404 Not Found

{
  "error": "FILE_NOT_FOUND"::string,
  "description": "visit 2 for existence number EX-01 not found"::string
}

✅ Consulter le dossier courant d’un voyageur

GET /files/<existenceNumber::string>/current

C’est la requête du guichet : un voyageur se présente avec son seul numéro d’existence et demande où il en est. Retourne sa venue la plus récente, close ou non.

➡️ HTTP 200 Ok — même charge utile que ci-dessus

➡️ HTTP 404 Not Found — ce voyageur n’a jamais eu de dossier

{
  "error": "FILE_NOT_FOUND"::string,
  "description": "no file for existence number EX-01"::string
}

✅ Consulter la dernière audience

GET /hearings/last

➡️ HTTP 200 Ok

{
  "number": 5::int,
  "nightsRead": [4, 5, 6, 7]::int[],
  "crossings": [
    {
      "dimension": "SALARIA"::string(SALARIA | BOUCANIA | HYLEE | NIMBE | NOCTAMBULIE | TESSELLE),
      "entryQuota": 4::int,
      "quotaUsed": 2::int,
      "quotaLost": 2::int,
      "boardedTravellers": ["EX-01", "EX-04"]::string[],
      "crossingFee": 500.00::float,
      "feePerTraveller": 250.00::float,
      "collected": 500.00::float
    }, ...
  ],
  "totalCollected": 500.00::float
}

entryQuota, quotaUsed et quotaLost décrivent le quota : ce que la dimension a offert, ce qui a servi, ce qui a été perdu à la fermeture. quotaUsed + quotaLost = entryQuota, toujours. boardedTravellers est d’une autre nature : il énumère des voyageurs.

boardedTravellers ne porte que des numéros d’existence, alors qu’un dossier s’identifie par le couple numéro d’existence et numéro de venue. C’est voulu, et c’est sans ambiguïté : le dépouillement est la première conséquence d’une audience et l’embarquement la troisième, donc une venue close par cette audience ne peut pas en ouvrir une nouvelle avant la suivante. Un voyageur embarque au plus une fois par audience. La même remarque vaut partout où un rapport décrit une seule audience ou un état courant.

crossingFee est le tarif déclaré par la dimension, feePerTraveller ce que chacun paie, collected ce qui est réellement entré dans les coffres. feePerTraveller et collected sont null quand personne n’a embarqué.

⚠️ collected ne vaut presque jamais crossingFee. Le tarif se divise, le quotient s’arrondit, puis il se remultiplie par le nombre d’embarqués. Trois embarqués sur un tarif de 500,00 paient 166,67 chacun, ce qui fait rentrer 500,01. L’écart est le comportement attendu et il sera testé : le Consulat perçoit ce que la division produit, pas ce que la dimension a annoncé. totalCollected est la somme des collected, jamais la somme des crossingFee.

➡ Lister toutes les dimensions configurées mêmes si elles n’ont aucun voyageur y étant passé.

➡️ HTTP 404 Not Found — aucune audience n’a encore été tenue

{
  "error": "NO_HEARING_HELD"::string,
  "description": "no hearing has been held yet"::string
}

Toute l’information sur la dernière audience passe par cette seule ressource, et les stories suivantes y ajoutent des sections dans le même JSON. Le 404 porte donc sur le rapport entier.

💡 Exemple 1 — deux compteurs qui n’ont rien à voir

L’auberge exécute les nuits 1 à 4. Elle transmet donc quatre cahiers, un par nuit. Personne ne tient d’audience pendant ce temps : GET /consulate retourne currentHearing: 0, lastReceivedNight: 4, lastReadNight: 0. Quatre cahiers reçus, aucun lu.

POST /hearings{ "number": 1 }

À cette seule audience, je dépouille les quatre cahiers d’un coup, dans l’ordre des nuits. Tous les dossiers ouverts par ces quatre nuits portent donc l’ancienneté 1, quelle que soit la nuit où le voyageur est arrivé.

Si un voyageur est arrivé à la nuit 2 et reparti à la nuit 3, son dossier est ouvert puis passé à TO_BOARD pendant la même audience, et il est candidat immédiatement.

💡 Exemple 2 — l’arrondi qui ne se recompose pas

Salaria impose un tarif de traversée de 500 pièces et un quota d’entrée de 3.

Trois voyageurs embarquent. Le tarif est divisé entre eux : 500 ÷ 3 = 166,666…, arrondi à 166,67 pièces par voyageur.

Je perçois donc 500,01 pièces, et non 500. Personne ne s’en est jamais plaint.

💡 Exemple 3 — le quota qui ne suffit pas

Boucania impose un quota d’entrée de 2 et un tarif de traversée de 300 pièces. Nous sommes à l’audience 7, et cinq dossiers TO_BOARD attendent Boucania :

Dossier Ouvert à l’audience Résultat
EX-19 3 embarque, c’est le plus ancien, et l’ancienneté passe avant tout
EX-100 5 embarque, trois dossiers sont à égalité d’ancienneté et EX-100 porte le plus petit des trois numéros
EX-22 5 reste TO_BOARD
EX-99 5 reste TO_BOARD
EX-02 6 reste TO_BOARD

⚠️ Le second embarqué est le piège. Les trois dossiers d’ancienneté 5 se départagent au numéro d’existence, qui est une chaîne et non un nombre : EX-100 < EX-22 < EX-99, parce que la comparaison s’arrête au quatrième caractère et que 1 < 2 < 9. Une équipe qui trie sur l’entier obtient 22, 99, 100 et fait embarquer EX-22. Les deux implémentations divergent sur cette seule ligne, et sur rien d’autre de l’exemple (voir les départages).

Le tarif se divise entre les embarqués, pas entre les candidats. 300 ÷ 2 = 150,00 pièces chacun, et non 300 ÷ 5 = 60,00. Les trois qui restent ne paient rien : ils ne sont allés nulle part.

⚠️ Cas inverse, tout aussi piégeux : si le quota de Boucania était de 8 et qu’il ne se présentait que ces cinq dossiers, les cinq embarqueraient, le tarif serait divisé par 5, et les trois entrées restantes seraient perdues à la fermeture du passage.

💡 Exemple 4 — le négociant qui revient

EX-11 est un négociant de Salaria. Il vient trois ou quatre fois par an, et il estime que ça devrait lui valoir quelque chose.

  1. Audience 4 : j’ouvre son dossier, venue 1, ancienneté 4. Il repart à l’audience 5, son dossier passe à CLOSED et je l’archive.
  2. Quatorze audiences plus tard, il revient au Carrefour. L’auberge l’admet, le fait TRAVELLER_ADMITTED me parvient, et j’ouvre un dossier neuf : venue 2, ancienneté 19.
  3. EX-40, arrivé au Carrefour pour la première fois de sa vie et dont le dossier a été ouvert à l’audience 18, passe donc devant lui.

GET /files/EX-11/current me retourne la venue 2. La venue 1 existe toujours, intacte, à GET /files/EX-11/1, avec ses 166,67 pièces perçues à l’audience 5. Elle ne bougera plus jamais.

Il a demandé si son ancienneté de la venue 1 pouvait être reportée. J’ai répondu que le dossier était clos. Il a demandé à parler à mon supérieur. Le Traité n’en prévoit pas.