@mostajs/recette 0.2.0 → 0.2.1

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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,26 @@
2
2
 
3
3
  **Auteur** : Dr Hamid MADANI <drmdh@msn.com>
4
4
 
5
+ ## 0.2.1 — 2026-09-10
6
+
7
+ Correction née d'une panne **en production**, invisible depuis les essais.
8
+
9
+ ### Corrigé
10
+
11
+ - **Le contenu du dossier revenait en CHAÎNE** de plusieurs dialectes SQL (sqljs le premier) là
12
+ où un dépôt en mémoire rend l'objet. L'enveloppe exportée transportait donc une chaîne, le
13
+ destinataire recalculait l'empreinte sur autre chose, et **refusait tout dossier** — avec un
14
+ message parlant de falsification. Le moteur normalise désormais à la lecture : `dossiers.of()`
15
+ et l'export rendent toujours un objet.
16
+ - **`dossiers.exporter()` vérifie l'empreinte avant de signer.** Un document dont l'empreinte ne
17
+ recouvre plus le contenu relu est refusé ici, en nommant la cause, plutôt que d'aller se faire
18
+ rejeter chez le destinataire qui, lui, n'aura que « signature ou empreinte invalide » à offrir.
19
+
20
+ ### Ajouté aux essais
21
+
22
+ - **T-REC-14** monte un dépôt qui imite sqljs — écriture d'objet, lecture de texte. C'est le cas
23
+ qui manquait : quatorze essais verts et rien ne fonctionnait en ligne.
24
+
5
25
  ## 0.2.0 — 2026-09-10
6
26
 
7
27
  Le sceau savait fermer un pli ; il ne savait pas l'ouvrir. C'est ce qui manquait pour que QaTrax
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mostajs/recette",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Validation & recette applicative : trame `mostajs-devtest/1` reçue signée du certificateur, points enrichis POUR LE CLIENT (l'import n'écrase jamais l'enrichissement), feuillets composés à l'image du manifeste PDF (ok/ko/er), anomalies déclarées puis qualifiées, dossier figé et EXPORTÉ SIGNÉ — aucun appel réseau —, certificat reçu et vérifié, feu d'exploitation DÉRIVÉ du certificat. Powers ValiTrax (valitrax.mostajs.dev).",
5
5
  "author": "Dr Hamid MADANI <drmdh@msn.com>",
6
6
  "license": "AGPL-3.0-or-later",
package/src/recette.js CHANGED
@@ -31,6 +31,17 @@ const rang = (e) => PARCOURS.indexOf(e);
31
31
  const need = (v, msg) => { if (v === undefined || v === null || v === '') throw new Error(msg); return v; };
32
32
  const asDate = (d) => (d ? new Date(d) : null);
33
33
 
34
+ /**
35
+ * Le CONTENU d'un dossier, quel que soit ce que le stockage a bien voulu en faire.
36
+ *
37
+ * Un champ `json` revient en OBJET d'un dépôt en mémoire, et en CHAÎNE de plusieurs dialectes
38
+ * SQL — sqljs le premier. Sans cette normalisation, l'enveloppe exportée transportait une
39
+ * chaîne là où le destinataire attend un objet, et l'empreinte recalculée à l'arrivée ne
40
+ * pouvait plus correspondre : tout dossier était refusé, pour une raison invisible depuis les
41
+ * essais, qui n'utilisent que des dépôts en mémoire.
42
+ */
43
+ const contenuDe = (d) => (typeof d?.contenu === 'string' ? JSON.parse(d.contenu) : d?.contenu);
44
+
34
45
  export function createRecette({ repositories, ports = {}, clock = () => new Date(), numeroteur } = {}) {
35
46
  const repo = need(repositories, 'repositories requis');
36
47
  for (const c of ['campagnes', 'points', 'feuillets', 'releves', 'anomalies', 'dossiers', 'certificats']) {
@@ -392,12 +403,21 @@ export function createRecette({ repositories, ports = {}, clock = () => new Date
392
403
  const sceau = ports.sceau;
393
404
  if (!sceau?.signer) throw new Error("aucun sceau configuré : un export non signé ne prouve rien, il est refusé");
394
405
 
406
+ // ON VÉRIFIE AVANT DE SIGNER. Exporter un document dont l'empreinte ne recouvre plus le
407
+ // contenu, c'est fabriquer un refus chez le destinataire — et un refus qu'il mettra sur le
408
+ // compte d'une falsification. Mieux vaut refuser ici, en nommant la cause.
409
+ const contenu = contenuDe(d);
410
+ const verif = createHash('sha256').update(JSON.stringify(contenu)).digest('hex');
411
+ if (verif !== d.empreinte) {
412
+ throw new Error(`empreinte du dossier ${d.no} incohérente avec son contenu relu (${verif.slice(0, 12)}… ≠ ${String(d.empreinte).slice(0, 12)}…) : le stockage n'a pas rendu ce qui lui a été confié`);
413
+ }
414
+
395
415
  const enveloppe = {
396
416
  type: 'mostajs-recette-dossier/1',
397
417
  emisLe: clock().toISOString(),
398
418
  campagne: { no: c.no, releaseRef: c.releaseRef },
399
419
  dossier: { no: d.no, empreinte: d.empreinte, figeLe: d.figeLe },
400
- contenu: d.contenu,
420
+ contenu,
401
421
  };
402
422
  const charge = JSON.stringify(enveloppe);
403
423
  const signature = await sceau.signer(charge);
@@ -416,7 +436,12 @@ export function createRecette({ repositories, ports = {}, clock = () => new Date
416
436
  const d = need(await dossiers.of(campagneId), 'aucun dossier pour cette campagne');
417
437
  return repo.dossiers.update(d.id, { depotRef: need(depotRef, 'depotRef requis') });
418
438
  },
419
- of: async (campagneId) => (await repo.dossiers.find((x) => x.campagneId === campagneId))[0] || null,
439
+ of: async (campagneId) => {
440
+ const d = (await repo.dossiers.find((x) => x.campagneId === campagneId))[0] || null;
441
+ // On rend TOUJOURS un objet : l'appelant n'a pas à connaître le dialecte SQL sous-jacent
442
+ // pour relire le contenu du dossier qu'il vient de figer.
443
+ return d ? { ...d, contenu: contenuDe(d) } : null;
444
+ },
420
445
  };
421
446
 
422
447
  const certificats = {