discovery-media-player 0.1.58 → 0.1.59

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.
@@ -32,7 +32,7 @@ need.
32
32
  "hostShare": true,
33
33
  "hostMail": true,
34
34
  "plugins": { "bot": false, "visitors": false, "brandIntro": false, "botBrowser": false, "providerQuotas": false },
35
- "schema": { "attendues": 3, "sondees": 1, "manquant": [] }
35
+ "schema": { "attendues": 3, "sondees": 1, "verdict": "partiel", "manquant": [] }
36
36
  }
37
37
  ```
38
38
 
@@ -47,10 +47,34 @@ mid-migration. That silence is the point: an operator whose write ordering and m
47
47
  are both switched off sees an instance that looks perfectly healthy. Each entry names the file to
48
48
  apply and the feature that is asleep.
49
49
 
50
- ⚠️ **`sondees` is not decoration.** The card **reports** what this process has already asked; it
51
- never probes, because a diagnostic must answer when the database does not. A freshly started
52
- process therefore knows nothing, and `manquant: []` on `sondees: 0` means *no question asked yet*,
53
- not *nothing missing*. Compare `sondees` with `attendues` before you conclude.
50
+ ⚠️ **`manquant: []` has four meanings, so read `verdict`, not the array.** The card **reports**
51
+ what this process has already asked; it never probes on its own, because a diagnostic must answer
52
+ when the database does not — and on a serverless host that means it is usually empty. Ask a
53
+ different question when you want a real answer:
54
+
55
+ ```
56
+ GET /api/doc?contract=1&schema=1
57
+ ```
58
+
59
+ That parameter **is** the one part of this card that needs the database, and only when you ask for
60
+ it. `verdict` is then one of:
61
+
62
+ | verdict | meaning |
63
+ |---|---|
64
+ | `non-sonde` | nothing asked yet — **not** *nothing missing* |
65
+ | `partiel` | some expectations checked, none of them missing |
66
+ | `complet` | all checked, all present |
67
+ | `incomplet` | at least one is missing — `manquant` names the file and the sleeping feature |
68
+ | `indetermine` | the database did not answer; this measurement did not happen |
69
+
70
+ ⚠️ **`incomplet` wins over `partiel`**: a missing column is a positive fact and settles the verdict
71
+ on its own, even when the rest has not been checked.
72
+
73
+ ⚠️ **`indetermine` is not a failure of the card.** A control column — the primary key of the oldest
74
+ table — is queried first. If *it* does not answer, nothing is missing: the database is. Without that
75
+ control, an unreachable database would make all three probes fail and the card would announce three
76
+ missing migrations **that exist**, sending you to apply what you already have. In that state nothing
77
+ is cached either, so a passing outage does not switch features off for the life of the process.
54
78
 
55
79
  ⚠️ **`frameAncestors` matters more than it looks.** A host that is not listed will never see the
56
80
  viewer: the browser blocks the iframe **before any script runs**, so no message can be emitted and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "discovery-media-player",
3
- "version": "0.1.58",
3
+ "version": "0.1.59",
4
4
  "description": "Self-hosted document viewer: per-recipient tracked links, reading analytics, live presentation. The core knows nothing about the application hosting it — everything it borrows arrives through an injected context.",
5
5
  "keywords": [
6
6
  "pdf-viewer",
package/server/handler.js CHANGED
@@ -3790,6 +3790,10 @@ async function handler(req, res) {
3790
3790
  // en booléens parce qu'un hôte doit pouvoir refuser de démarrer si le mur d'accès manque
3791
3791
  // alors qu'il compte dessus.
3792
3792
  if (String(q.contract || "") === "1") {
3793
+ // ⚠️ `&schema=1` : la SEULE partie de cette carte qui demande la base, et seulement quand on
3794
+ // la réclame. Sans le paramètre, la route garde sa propriété de répondre quand plus rien ne
3795
+ // répond ; avec lui, l'appelant choisit d'en avoir besoin. Un échec ici ne fait pas échouer
3796
+ // la carte — il devient le verdict « indetermine ».
3793
3797
  res.statusCode = 200;
3794
3798
  res.setHeader("Content-Type", "application/json; charset=utf-8");
3795
3799
  res.setHeader("Cache-Control", "no-store, max-age=0");
@@ -3828,7 +3832,9 @@ async function handler(req, res) {
3828
3832
  // carte est ce qu'un hôte interroge déjà pour épingler sa version ; c'est donc ici.
3829
3833
  // Elle RAPPORTE ce qui est connu, elle ne sonde pas : un diagnostic ne doit pas tomber en
3830
3834
  // même temps que ce qu'il diagnostique.
3831
- schema: require("./schema").etatDuSchema(),
3835
+ schema: String(q.schema || "") === "1"
3836
+ ? await require("./schema").sonderTout()
3837
+ : require("./schema").etatDuSchema(),
3832
3838
  // « L'hôte peut-il créer un lien en son nom propre ? » — configuré, pas seulement
3833
3839
  // possible. Un hôte qui oublie le secret reçoit un 401 qui ressemble à un droit
3834
3840
  // manquant ; ce booléen le lui dit sans qu'il ait à essayer.
package/server/schema.js CHANGED
@@ -44,6 +44,21 @@ const ATTENDUES = {
44
44
  },
45
45
  };
46
46
 
47
+ /**
48
+ * LE TÉMOIN — une colonne dont l'absence est impossible.
49
+ *
50
+ * ⚠️ IL DISTINGUE « ABSENTE » DE « INJOIGNABLE » SANS LIRE UN MESSAGE D'ERREUR. La sonde ne fait
51
+ * pas cette différence, et c'est juste pour DÉCIDER (les deux mènent à ne pas écrire le champ).
52
+ * Pour RAPPORTER, les confondre serait faux dans les deux sens : une base momentanément muette
53
+ * ferait annoncer trois migrations manquantes qui existent — une fausse alerte qui envoie
54
+ * l'exploitant appliquer ce qu'il a déjà.
55
+ *
56
+ * Le témoin est la clé primaire de la plus ancienne table : si LUI ne répond pas, ce n'est pas une
57
+ * migration qui manque, c'est la base. Mesure différentielle, aucune dépendance au texte d'un
58
+ * service tiers — la raison même pour laquelle la sonde refusait de distinguer.
59
+ */
60
+ const TEMOIN = { table: "doc_presentations", colonne: "slug" };
61
+
47
62
  let PLAYER = null;
48
63
  /**
49
64
  * Une question posée une fois, retenue pour le processus.
@@ -99,9 +114,15 @@ function attendue(nom) {
99
114
  function etatDuSchema() {
100
115
  const manquant = [];
101
116
  for (const [, r] of reponses) if (!r.present) manquant.push({ migration: r.migration, fonction: r.fonction });
117
+ const attendues = Object.keys(ATTENDUES).length;
102
118
  return {
103
- attendues: Object.keys(ATTENDUES).length,
119
+ attendues,
104
120
  sondees: reponses.size,
121
+ // ⚠️ UN MOT, PAS UN TABLEAU VIDE À INTERPRÉTER. `manquant: []` a quatre sens selon ce qu'on
122
+ // sait par ailleurs — rien demandé, tout vérifié, vérifié en partie, base muette — et forcer
123
+ // le lecteur à les reconstituer en croisant deux champs, c'est lui laisser la faute. Le second
124
+ // hôte l'a posé comme condition à ce paramètre, et il avait raison avant même de le voir.
125
+ verdict: verdict(manquant.length, reponses.size, attendues),
105
126
  // ⚠️ ON NOMME LE FICHIER, alors que cette route est publique. Même raison que `frameAncestors`
106
127
  // juste au-dessus d'elle : l'exploitant n'a AUCUN autre moyen d'apprendre laquelle manque, et
107
128
  // un compte nu le laisserait deviner. Ce qu'on révèle en échange — qu'une fonction de
@@ -159,6 +180,45 @@ async function sonder(table, colonne, migration, cle) {
159
180
  }
160
181
  }
161
182
 
183
+ function verdict(manque, sondees, attendues) {
184
+ if (manque) return "incomplet"; // un manque est un fait positif : il tranche seul
185
+ if (!sondees) return "non-sonde";
186
+ return sondees < attendues ? "partiel" : "complet";
187
+ }
188
+
189
+ /**
190
+ * SONDER TOUT, À LA DEMANDE — `?contract=1&schema=1`.
191
+ *
192
+ * ⚠️ LE COÛT EST SUR L'APPELANT QUI VEUT LA RÉPONSE. Sonder au démarrage mettrait un aller-retour
193
+ * base sur chaque démarrage à froid, donc sur le chemin critique de la première vraie requête,
194
+ * pour un diagnostic que presque personne ne lit — et ferait dépendre le contenu de la carte de ce
195
+ * que la base a répondu, c'est-à-dire déplacerait le couplage que sa doctrine interdit au lieu de
196
+ * le supprimer. Arbitrage tranché avec le second hôte, sur ses trois raisons.
197
+ *
198
+ * ⚠️ ET UN DIAGNOSTIC NE DOIT PAS ÉTEINDRE CE QU'IL DIAGNOSTIQUE. `aLaColonne` retient sa réponse
199
+ * pour la vie du processus : appelé pendant un hoquet de la base, ce paramètre aurait mis en cache
200
+ * « absente » pour les trois attentes — désactivant l'ordre des écritures et l'idempotence des
201
+ * messages jusqu'au prochain démarrage. Une route de contrôle qui casse la production. Si le
202
+ * témoin ne répond pas, on ne sonde RIEN et on ne retient RIEN.
203
+ */
204
+ async function sonderTout() {
205
+ // ⚠️ LA PART ENCODÉE EST CALCULÉE À PART, comme dans `sonder()` dix lignes plus haut, et pour la
206
+ // même raison : la garde de portabilité traque une parenthèse après « select= », et un appel de
207
+ // fonction écrit dans le gabarit en produit une. J'ai reproduit ici le défaut dont le correctif
208
+ // était commenté juste au-dessus — la forme exacte de la migration 0004, où dix lignes
209
+ // expliquaient la prudence qu'un revoke six lignes plus haut n'appliquait pas.
210
+ const champTemoin = encodeURIComponent(TEMOIN.colonne);
211
+ try {
212
+ await PLAYER.db.request(`${TEMOIN.table}?select=${champTemoin}&limit=0`);
213
+ } catch {
214
+ // On rend ce qu'on savait déjà — un manque constaté plus tôt reste un fait — mais le verdict
215
+ // dit que cette mesure-ci n'a pas eu lieu. Taire l'un ou l'autre serait mentir d'un côté.
216
+ return { ...etatDuSchema(), verdict: "indetermine" };
217
+ }
218
+ for (const nom of Object.keys(ATTENDUES)) await attendue(nom);
219
+ return etatDuSchema();
220
+ }
221
+
162
222
  /** La réponse, retenue pour qui la demandera — sans repasser par la base. */
163
223
  function noter(cle, present, migration) {
164
224
  const a = Object.values(ATTENDUES).find((x) => `${x.table}.${x.colonne}` === cle);
@@ -180,4 +240,4 @@ function signaler(cle, migration) {
180
240
  /** Pour les tests et l'exploitation : reposer la question. */
181
241
  function oublier() { connues.clear(); reponses.clear(); }
182
242
 
183
- module.exports = { init, aLaColonne, attendue, etatDuSchema, oublier, ATTENDUES };
243
+ module.exports = { init, aLaColonne, attendue, etatDuSchema, sonderTout, oublier, ATTENDUES, TEMOIN };