discovery-media-player 0.1.167 → 0.1.169

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.
@@ -182,10 +182,10 @@ these numbers from their side.
182
182
  | `fenetreS` | seconds this process has been running — **the window every total below was counted over** |
183
183
  | `seauxMs` | the bucket ladder the percentiles are read off, published **with** the numbers |
184
184
  | `familles` | **the denominator of `routes`** — every family this build measures, whether or not it was exercised. It does not move with traffic; that is what makes it a denominator |
185
- | `routes` | one entry per family of work — `document`, `presentation`, `action`, `fichier`, `carte`, `autre`. Families absent from the object were never exercised in this process |
185
+ | `routes` | one entry per family of work — `document`, `presentation`, `action`, `fichier`, `carte`, `autre`. Families absent from the object were never exercised in this process. ⚠️ **`familles` is the scale, `routes` is the measure: `familles` never varies; what was seen is in `routes`.** A host displayed the scale as the measure for a whole day — "6 familles", permanently, on an instance that had just restarted — because nothing on the card said which of the two was which (STUDIO, 14/09) |
186
186
  | `base` | the same shape, for calls through the `db` capability **you** supply — measured at the seam, so it covers every call, including ones nobody has written yet |
187
187
  | `statuts` | responses by class: `ok` (<400), `refus4xx`, `debit429`, `occupe503`, `erreur5xx` |
188
- | `memoireMio` | `rss`, `heap` (heap used), `tampons` (`arrayBuffers`) in MiB, read at the moment of the request. ⚠️ **Half a number**: it is only judicable against the memory ceiling of the process, which the player does not know and no platform serves the same way — on Lambda-based functions (Vercel included) read `AWS_LAMBDA_FUNCTION_MEMORY_SIZE`; in a container, the cgroup limit. A host spent half a day finding that its project API, its logs and its `vercel.json` all left it out (14/09). Display the ceiling beside the RSS, or the RSS says nothing about the relay ceiling you can afford |
188
+ | `memoireMio` | `rss`, `heap` (heap used), `tampons` (`arrayBuffers`) in MiB, read at the moment of the request. ⚠️ **Half a number**: it is only judicable against the memory ceiling of the process, which the player does not know and no platform serves the same way — on Lambda-based functions (Vercel included) read `AWS_LAMBDA_FUNCTION_MEMORY_SIZE`; in a container, the cgroup limit. A host spent half a day finding that its project API, its logs and its `vercel.json` all left it out (14/09). Display the ceiling beside the RSS, or the RSS says nothing about the relay ceiling you can afford. And the ceiling makes the RSS **comparable, not the question decidable**: whether 64 relays fit is settled only by a reading under load, never by a gauge read at rest (STUDIO, 14/09) |
189
189
  | `boucleMs` | event-loop **delay** — `moyen` and `p99` in ms, with `n` samples and the sampler's `resolutionMs` |
190
190
 
191
191
  ⚠️ **A percentile over buckets is a bound, not a value.** `p95sousMs: 250` reads *"95% of calls
@@ -284,6 +284,15 @@ by the card, structured and dated, and never by a search through logs for `relai
284
284
  log line stays for diagnosis, the card is the way to *notice*. Process-local like everything on this
285
285
  card, and **never reset by `init`**, exactly like the counter of open relays.
286
286
 
287
+ ⚠️ **A zero is informative only beside the traffic that could have produced the event — `fenetreS`
288
+ alone is not enough.** A host read `total: 0` over a 999-second window and showed *no relay
289
+ refused*, *no saturated read* in green; over that window `mesures.routes` carried only `action`
290
+ (3 calls) — not one file, not one presentation read, so neither counter had had a single occasion
291
+ (STUDIO, 14/09). Read `mesures.routes.fichier.n` next to `relaisRefuses` and
292
+ `mesures.routes.presentation.n` next to `lectureSaturee` — the families are `familleDe`'s, not
293
+ guessed — and the timer wraps the whole handler, so `n` counts the refused calls too: it is the
294
+ denominator you want. Both hosts' cards now say *no occasion yet* instead of green.
295
+
287
296
  ⚠️ **Before you upgrade, do not read `presenceDurcissement` or `presenceFusion`.** They are *reports
288
297
  of execution*: on an instance where nothing is running they say `inconnu`, which means *nobody
289
298
  looked* — not *the migration is there*. A pre-flight check built on one of them silently passes on
@@ -647,7 +656,21 @@ context shipped in this package already implements it** — if you build your co
647
656
  `discovery-media-player/context/standalone`, you get it on your next upgrade and there is nothing
648
657
  to decide or write. This section is for a host that implements the `db` capability itself. A host
649
658
  asked which of the two it was, and the answer was missing from this page: *"the two look alike in
650
- your code and not at all alike at your hosts."* If your `db` capability
659
+ your code and not at all alike at your hosts."* ⚠️ **The same line decides which zone of a release
660
+ reaches you, and it is read per capability, not per host.** Three forms. A host that runs
661
+ `context/standalone` as is (ADV does, unchanged since August — **read in its public wiring
662
+ repository, not taken from a message**: for a host whose wiring is public, the file is the source,
663
+ and a replaced context would show in a commit before it showed in a message) executes every change to the `context`
664
+ zone — the environment pass-through, the journal helper — and its `errors.capture` is the player's
665
+ own. A host that **composes** its context from `createStandaloneContext` and replaces some
666
+ capabilities (the Vercel example in this repository does: `identity` and `branding` are its own,
667
+ everything else inherited) is reached by every change to a capability it inherits, and by none to a
668
+ capability it replaced; `creerLimites` is exported for exactly that host. A host whose context
669
+ imports nothing from `context/` (STUDIO) is touched by `server/` only. That line was missing from
670
+ what the player held about its hosts, and a release note told one of them a change to its own file
671
+ was "without effect on your side" (14/09); the first version of this paragraph was binary, and an
672
+ audit pointed at the repository's own example as the third case. Say your form once; it is the
673
+ line the notes are written from. If your `db` capability
651
674
  exposes it, the player asks it first and publishes an **exact** count — no bound, no `tronque`, and
652
675
  no rows transported at all. If it is absent, everything above still applies unchanged: the bounded
653
676
  read with its cursor probe. **That fallback is the whole design.** Third-party hosts implement this
@@ -888,7 +911,12 @@ per process before answering **503 busy** to everyone. A database call that hang
888
911
  "slow", it is an availability incident for the whole instance — the exact mechanism an audit
889
912
  reproduced inside the test suite with a never-settling promise (13/09). Time out your own calls
890
913
  (the standalone context bounds its own with `AbortSignal`), and never return a promise you cannot
891
- guarantee will settle.
914
+ guarantee will settle. ⚠️ **The bound must cover the body, not only the headers.** `fetch` resolves
915
+ as soon as the headers arrive; `response.text()` then hangs on a stream left open, so a timeout
916
+ that stops at the headers bounds half the path. Pass the same `AbortSignal` to the fetch, which
917
+ aborts the body read too (the standalone context does), and if you retry an abandoned call, retry a
918
+ read only, never a write. A host (STUDIO, 13/09) found its own `db.request` bounded that way — at
919
+ the headers — and rewrote it; the rule is theirs.
892
920
 
893
921
  **Your document-opening doors reappear.** A host has more than one place that opens a file, and new
894
922
  ones get written. Keep the list and hunt it periodically — and note that **your search criteria
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "discovery-media-player",
3
- "version": "0.1.167",
3
+ "version": "0.1.169",
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",
@@ -84,6 +84,7 @@
84
84
  "devDependencies": {
85
85
  "@eslint/js": "^10.0.1",
86
86
  "@vitest/coverage-v8": "^4.1.11",
87
+ "ajv": "8.20.0",
87
88
  "axe-core": "^4.13.0",
88
89
  "dockerfile-ast": "0.7.1",
89
90
  "esbuild": "^0.28.2",
package/server/cache.js CHANGED
@@ -85,6 +85,21 @@ function creerCache(options) {
85
85
  const entrees = new Map();
86
86
  let poidsTotal = 0;
87
87
  let nEnVol = 0; // tenu à jour à chaque transition — compter la Map à la demande serait O(n)
88
+ // ⚠️ CE QUE LE CACHE A FAIT, PAS SEULEMENT CE QU'IL A REFUSÉ. `satures()` disait les refus ; rien ne
89
+ // disait combien de lectures avaient été servies de la mémoire, regroupées sur une production en
90
+ // vol, ou produites. L'artefact de charge (audit, 14/09) demande les trois et le pic en vol :
91
+ // sans eux, « le cache tient » est une phrase, pas une mesure. Compteurs de processus, comme tout
92
+ // ici : jamais remis à zéro, à lire en deltas.
93
+ let nServies = 0, nRegroupees = 0, nProduites = 0, picEnVol = 0;
94
+ // ⚠️ LES OBSERVATEURS DE FENÊTRE, ET POURQUOI ILS NE TOUCHENT PAS À L'ÉTAT DU CACHE. `picEnVol`
95
+ // est un maximum DEPUIS LE DÉMARRAGE : quand le rapport de charge le recopiait tel quel à côté de
96
+ // hits/misses/coalesced convertis en DELTAS, une position calme héritait du pic d'une position
97
+ // chargée et affirmait une concurrence qu'elle n'avait jamais vue. Remettre `picEnVol` à zéro
98
+ // entre deux positions serait pire : on modifierait le SYSTÈME MESURÉ pour arranger l'instrument,
99
+ // et le pic historique — qui a son utilité — disparaîtrait. Un observateur est donc un compteur
100
+ // SÉPARÉ, ouvert et refermé par qui mesure, sans que le cache change de comportement.
101
+ // Relevé par un audit externe (CODEX, 15/09).
102
+ const observateurs = new Set();
88
103
 
89
104
  const oublier = (k) => {
90
105
  const e = entrees.get(k);
@@ -127,7 +142,10 @@ function creerCache(options) {
127
142
  // qui décide : refuser d'abord ferait échouer des appelants que le regroupement pouvait servir
128
143
  // gratuitement — la saturation punirait alors la rafale légitime, exactement ce que ce cache
129
144
  // existe pour absorber. On ne refuse que ce qui coûterait une requête DE PLUS.
130
- if (vue && (vue.enVol || vue.echeance > t)) return vue.promesse;
145
+ if (vue && (vue.enVol || vue.echeance > t)) {
146
+ if (vue.enVol) nRegroupees += 1; else nServies += 1;
147
+ return vue.promesse;
148
+ }
131
149
 
132
150
  if (nEnVol >= maxEnVol) {
133
151
  nSatures += 1;
@@ -143,6 +161,9 @@ function creerCache(options) {
143
161
  let placeRendue = false;
144
162
  const rendrePlace = () => { if (!placeRendue) { placeRendue = true; nEnVol -= 1; } };
145
163
  nEnVol += 1;
164
+ nProduites += 1;
165
+ if (nEnVol > picEnVol) picEnVol = nEnVol;
166
+ for (const o of observateurs) if (nEnVol > o.pic) o.pic = nEnVol;
146
167
  // ⚠️ L'ÉCHÉANCE PART DE LA RÉSOLUTION, PAS DE LA DEMANDE. Posée à la demande, elle expirait
147
168
  // AVANT que la production ne réponde dès que celle-ci dépassait le TTL — et le regroupement ne
148
169
  // servait alors plus à rien pour exactement les producteurs lents, les seuls qu'il valait la
@@ -193,6 +214,23 @@ function creerCache(options) {
193
214
  poids: () => poidsTotal,
194
215
  /** Demandes actuellement en vol — ce que le plafond d'admission borne. */
195
216
  enVol: () => nEnVol,
217
+ /**
218
+ * Ce que ce cache a fait depuis le démarrage du processus : `hits` servies de la mémoire,
219
+ * `coalesced` regroupées sur une production déjà en vol, `misses` produites, `peakInFlight` le
220
+ * plus grand nombre de productions simultanées. Les refus sont dans `satures()`.
221
+ */
222
+ compteurs: () => ({ hits: nServies, coalesced: nRegroupees, misses: nProduites, peakInFlight: picEnVol }),
223
+ /**
224
+ * Ouvre une fenêtre d'observation du pic en vol : `{ pic(), fermer() }`. Le cache n'est pas
225
+ * modifié — ni remis à zéro, ni ralenti ; seul un compteur parallèle suit les productions
226
+ * simultanées TANT QUE la fenêtre est ouverte. `fermer()` est impératif : un observateur oublié
227
+ * survit au relevé qui l'a demandé.
228
+ */
229
+ observerEnVol: () => {
230
+ const o = { pic: nEnVol };
231
+ observateurs.add(o);
232
+ return { pic: () => o.pic, fermer: () => observateurs.delete(o) };
233
+ },
196
234
  /**
197
235
  * Ce que le plafond a refusé depuis le démarrage de ce processus.
198
236
  *
package/server/handler.js CHANGED
@@ -1249,6 +1249,19 @@ module.exports = { __contexte: () => PLAYER, handler, init, TIERS, POLITIQUE_PER
1249
1249
  // ⚠️ COUTURE DE BANC, PAS D'API : le cache de lecture est global au module, et un banc qui laisse des
1250
1250
  // lectures en vol contamine le suivant (128 promesses éternelles, 503 partout — trouvé par un audit
1251
1251
  // externe sous mélange, graine 20260913). Un banc doit pouvoir VÉRIFIER qu'il rend le cache vide.
1252
- __cacheLecture: cacheLecture };
1252
+ __cacheLecture: cacheLecture,
1253
+ // ⚠️ COUTURE DE MESURE : LIRE LES COMPTEURS SANS LES INCRÉMENTER. Le rapport de charge relevait
1254
+ // ces grandeurs par un `GET ?contract=1` — c'est-à-dire par une requête qui TRAVERSE le handler et
1255
+ // incrémente `mesures.statuts.ok` au passage. Le delta d'une fenêtre de 1 000 requêtes valait donc
1256
+ // 1 001, systématiquement, et pour chaque position : l'observateur se comptait lui-même. Une
1257
+ // soustraction cachée aurait corrigé le chiffre en aggravant le problème — un instrument qui se
1258
+ // retranche discrètement est plus difficile à auditer qu'un instrument faux. La couture rend l'état
1259
+ // tel qu'il est, sans le modifier ; ce que la fenêtre contient d'autre que la charge se DIT, dans
1260
+ // `counters.observerOverheadRequests`. Défaut relevé par un audit externe (CODEX, 15/09).
1261
+ __compteursSansObserver: () => ({
1262
+ lectureSaturee: { total: cacheLecture.satures().total },
1263
+ relaisRefuses: { total: relaisRefusesTotal },
1264
+ mesures: mesures.relever(),
1265
+ }) };
1253
1266
 
1254
1267
  // redeploy: forcer le build production (Vercel a sauté la prod du merge #463 — wording re-partage).