[ADD] storage: file-system interface, failing test double, contract
Storage code receives its file system as a parameter (§ 13.4): one
interface, and here the implementation tests inject. It keeps files in
memory, shares a disk between two instances to replay a restart, and
fails on demand: a refused rename that leaves the target intact, writes
refused from a moment on, a process killed after n writes, a failing
probe, a removable medium. One contract suite, its assertions passed in,
will run unchanged on the Electron and web implementations.
Checked: 87 tests, the contract suite run on the test double.
--- FR ---
[ADD] stockage : interface du système de fichiers, double, contrat
Le code du stockage reçoit son système de fichiers en paramètre
(§ 13.4) : une interface, et ici l'implémentation que les épreuves
injectent. Elle garde les fichiers en mémoire, partage un disque entre
deux instances pour rejouer un redémarrage, et tombe en panne à la
demande : renommage refusé qui laisse la cible intacte, écritures
refusées à partir d'un instant, processus tué après n écritures, sonde
qui échoue, support amovible. Une suite de contrat, ses assertions
reçues en paramètre, servira telle quelle sous Electron et sous web.
Vérifié : 87 épreuves, la suite de contrat jouée sur le double.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 13:12:25 -04:00
|
|
|
// © 2026 TechnoLibre (http://www.technolibre.ca)
|
|
|
|
|
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
|
|
|
|
|
|
|
|
|
|
// Le système de fichiers, frontière de plateforme du stockage (§ 13.4). Ce
|
|
|
|
|
// module en déclare l'interface, que trois implémentations portent : celle de
|
|
|
|
|
// la coquille electron, livrée ; celle de la plateforme web, sur l'OPFS ; celle
|
|
|
|
|
// d'épreuve, en mémoire, qui sait tomber en panne (test/fichiers_simules.js).
|
|
|
|
|
// Toute fonction du stockage qui touche un fichier reçoit un SystemeFichiers
|
|
|
|
|
// en paramètre (§ 14.10) ; la suite de contrat test/contrat_fichiers.js
|
|
|
|
|
// éprouve les trois implémentations par les mêmes épreuves.
|
|
|
|
|
//
|
|
|
|
|
// Une primitive ne reçoit jamais de chemin absolu : une racine, connue par son
|
|
|
|
|
// identifiant, et un chemin relatif à elle, aux segments séparés par « / »,
|
|
|
|
|
// sans « .. » ni chemin absolu ; '' désigne la racine elle-même. Le chemin
|
|
|
|
|
// d'une racine sert à l'affichage, jamais à résoudre un fichier. Toutes les
|
|
|
|
|
// primitives sont asynchrones. Un échec lève une ErreurStockage :
|
|
|
|
|
//
|
|
|
|
|
// ABSENT {chemin} lecture d'un fichier, ou liste
|
|
|
|
|
// d'un dossier, qui n'existe pas
|
|
|
|
|
// ECRITURE {chemin, dossier, cause} une écriture, un ajout, un
|
|
|
|
|
// renommage refusés ; la cible est
|
|
|
|
|
// intacte. chemin est le chemin
|
|
|
|
|
// relatif reçu, dossier le chemin
|
|
|
|
|
// affichable du dossier qui le
|
|
|
|
|
// porte — la racine elle-même pour
|
|
|
|
|
// '' —, cause celle que rapporte
|
|
|
|
|
// la plateforme (EBUSY, EROFS…)
|
|
|
|
|
// EXISTE {chemin} deplacer vers une cible existante
|
|
|
|
|
// CHEMIN_REFUSE {chemin} chemin absolu ou remontant, racine
|
|
|
|
|
// inconnue
|
[FIX] storage, session: strict UTF-8, lost journal, lock, exclusions
The whole-iteration review found ways to lose or corrupt data. A state
saved in another encoding opened with its accents turned to U+FFFD and was
then overwritten; decoding is now strict, the state opens read-only, and a
gesture from its secours first moves it to the trash. A journal decodes line
by line, so one bad byte drops only the lines after it. A vanished journal
is rewritten whole, never appended to. Deleting refuses an event another
session holds; switching to writing rereads the disk; an import that
changes exclu says which reservations it suspends.
Checked: 1504 node, 35 browser, 29 node-long; depot.js, journal.js 100 %.
--- FR ---
[FIX] stockage, séance : UTF-8 strict, journal perdu, verrou, exclusions
La revue d'ensemble a trouvé des façons de perdre ou d'abîmer des données.
Un état enregistré dans un autre encodage s'ouvrait, ses accents changés en
U+FFFD, puis s'écrasait ; le décodage est strict, l'état s'ouvre en lecture
seule, et un geste depuis son secours le range d'abord à la corbeille. Le
journal se décode ligne par ligne : un octet fautif n'écarte que la suite.
Un journal disparu se réécrit en entier, jamais par ajout. Supprimer refuse
un événement qu'une autre séance tient ; passer en écriture relit le disque ;
un import qui change exclu dit quelles réservations il suspend.
Vérifié : 1504 node, 35 navigateur, 29 node-long ; depot.js, journal.js 100 %.
Assisted-by: Claude Opus 5.5
2026-10-07 01:20:42 -04:00
|
|
|
// LECTURE {chemin, dossier, cause} une lecture refusée autrement que
|
|
|
|
|
// sur un absent ; cause
|
|
|
|
|
// UTF8_INVALIDE pour un fichier dont
|
|
|
|
|
// les octets ne sont pas de l'UTF-8,
|
|
|
|
|
// que lireTexte ne lit jamais en
|
|
|
|
|
// U+FFFD ; les détails portent
|
|
|
|
|
// alors aussi octets, une copie
|
|
|
|
|
// des octets du fichier
|
[ADD] storage: file-system interface, failing test double, contract
Storage code receives its file system as a parameter (§ 13.4): one
interface, and here the implementation tests inject. It keeps files in
memory, shares a disk between two instances to replay a restart, and
fails on demand: a refused rename that leaves the target intact, writes
refused from a moment on, a process killed after n writes, a failing
probe, a removable medium. One contract suite, its assertions passed in,
will run unchanged on the Electron and web implementations.
Checked: 87 tests, the contract suite run on the test double.
--- FR ---
[ADD] stockage : interface du système de fichiers, double, contrat
Le code du stockage reçoit son système de fichiers en paramètre
(§ 13.4) : une interface, et ici l'implémentation que les épreuves
injectent. Elle garde les fichiers en mémoire, partage un disque entre
deux instances pour rejouer un redémarrage, et tombe en panne à la
demande : renommage refusé qui laisse la cible intacte, écritures
refusées à partir d'un instant, processus tué après n écritures, sonde
qui échoue, support amovible. Une suite de contrat, ses assertions
reçues en paramètre, servira telle quelle sous Electron et sous web.
Vérifié : 87 épreuves, la suite de contrat jouée sur le double.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 13:12:25 -04:00
|
|
|
//
|
|
|
|
|
// types.js donne la table entière des codes du stockage.
|
|
|
|
|
import { ErreurStockage } from './erreurs.js';
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Une racine : un dossier que l'implémentation connaît par son identifiant
|
|
|
|
|
* — portable, documents, ou un dossier choisi.
|
|
|
|
|
*
|
|
|
|
|
* @typedef {Object} Racine
|
|
|
|
|
* @property {string} id
|
|
|
|
|
* @property {string} chemin chemin absolu, affichable
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @typedef {Object} Emplacements
|
|
|
|
|
* @property {string|null} executable dossier publié par le lanceur portable
|
|
|
|
|
* @property {string[]} donneesApplicatives AppData, LocalAppData, temporaire, en absolu
|
|
|
|
|
* @property {boolean} insensibleCasse les chemins se comparent sans égard à la casse
|
|
|
|
|
* @property {'\\'|'/'} separateur
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Une entrée d'un dossier.
|
|
|
|
|
*
|
|
|
|
|
* @typedef {Object} Entree
|
|
|
|
|
* @property {string} nom
|
|
|
|
|
* @property {'fichier'|'dossier'} type
|
|
|
|
|
* @property {number} taille octets du fichier en UTF-8 ; 0 pour un dossier
|
|
|
|
|
* @property {number} modifie millisecondes, pour l'affichage seulement :
|
|
|
|
|
* aucune décision ne s'y fonde, puisque recopier un dossier suffit
|
|
|
|
|
* à les fausser (§ 14.10)
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Ce que rend verrouiller. pris : true quand l'appel a créé le verrou. Sinon,
|
|
|
|
|
* la séance qui le tient, l'instant de sa prise, et vivant : vrai quand son
|
[FIX] electron: lock liveness only when certain, retries on every write
A lock judged by its pid alone took a reused pid for a live session after a
reboot, and the event stayed locked. The lock now records the process start
time; a session is dead only when that is certain — no process with the pid,
or two known start times that differ — and alive only when they match.
Otherwise liveness is unknown and the session is not taken over, so two
sessions never write at once (§ 8.8). The pause-and-retry on EPERM, EBUSY
and EACCES that guarded only the rename now guards appends, moves and
deletes too, through one helper.
Checked: 1538 node, 26 node-long tests; the macOS start time is read in UTC.
--- FR ---
[FIX] electron : vie du verrou sûre, réessais sur chaque écriture
Un verrou jugé sur son seul pid prenait un pid réattribué pour une séance
vivante après un redémarrage, et l'événement restait verrouillé. Le verrou
inscrit désormais l'heure de démarrage du processus ; une séance n'est morte
que si c'est certain — aucun processus de ce pid, ou deux heures connues qui
diffèrent — et vivante que si elles concordent. Sinon sa vie est inconnue
et la séance n'est pas reprise : deux séances n'écrivent jamais ensemble
(§ 8.8). La pause et les réessais sur EPERM, EBUSY et EACCES, qui ne
gardaient que le renommage, gardent aussi ajouts, déplacements et
suppressions, par une seule aide.
Vérifié : 1538 node, 26 node-long ; l'heure de démarrage macOS se lit en UTC.
Assisted-by: Claude Opus 5.5
2026-10-07 02:00:36 -04:00
|
|
|
* processus tourne sur cette machine avec l'heure de démarrage inscrite ;
|
|
|
|
|
* faux quand il est mort à coup sûr — aucun processus de ce pid, ou deux
|
|
|
|
|
* heures de démarrage connues qui diffèrent ; null sinon — verrou d'une autre
|
|
|
|
|
* machine, ou vie qui ne se prouve pas. Un verrou qui ne se lit pas rend
|
|
|
|
|
* seance, depuis et vivant à null.
|
[ADD] storage: file-system interface, failing test double, contract
Storage code receives its file system as a parameter (§ 13.4): one
interface, and here the implementation tests inject. It keeps files in
memory, shares a disk between two instances to replay a restart, and
fails on demand: a refused rename that leaves the target intact, writes
refused from a moment on, a process killed after n writes, a failing
probe, a removable medium. One contract suite, its assertions passed in,
will run unchanged on the Electron and web implementations.
Checked: 87 tests, the contract suite run on the test double.
--- FR ---
[ADD] stockage : interface du système de fichiers, double, contrat
Le code du stockage reçoit son système de fichiers en paramètre
(§ 13.4) : une interface, et ici l'implémentation que les épreuves
injectent. Elle garde les fichiers en mémoire, partage un disque entre
deux instances pour rejouer un redémarrage, et tombe en panne à la
demande : renommage refusé qui laisse la cible intacte, écritures
refusées à partir d'un instant, processus tué après n écritures, sonde
qui échoue, support amovible. Une suite de contrat, ses assertions
reçues en paramètre, servira telle quelle sous Electron et sous web.
Vérifié : 87 épreuves, la suite de contrat jouée sur le double.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 13:12:25 -04:00
|
|
|
*
|
|
|
|
|
* @typedef {{pris: true}
|
|
|
|
|
* | {pris: false, seance: string|null, depuis: string|null, vivant: boolean|null}} Verrou
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @typedef {Object} SystemeFichiers
|
|
|
|
|
* @property {'electron'|'web'|'epreuve'} nature
|
|
|
|
|
* @property {boolean} renommageAtomique faux sous web (§ 8.8)
|
|
|
|
|
* @property {boolean} verrouDisponible faux sous web (§ 8.8) : verrouiller
|
|
|
|
|
* rend alors { pris: true } à chaque appel, et deverrouiller n'efface rien
|
|
|
|
|
* @property {() => Promise<Emplacements>} emplacements
|
|
|
|
|
* @property {() => Promise<{portable: Racine|null, documents: Racine}>} racines
|
|
|
|
|
* portable : data/ à côté de l'exécutable publié ; documents : le
|
|
|
|
|
* dossier du produit dans les Documents (§ 8.6)
|
|
|
|
|
* @property {() => Promise<Racine|null>} choisirDossier dialogue natif ;
|
|
|
|
|
* null quand l'opérateur annule
|
|
|
|
|
* @property {(r: Racine) => Promise<{inscriptible: boolean, cause: string|null}>} sonder
|
|
|
|
|
* crée la racine, parents compris, efface un .gtt-temoin resté
|
|
|
|
|
* d'une séance précédente, écrit .gtt-temoin, le relit, l'efface
|
|
|
|
|
* (§ 8.6) ; un échec rend sa cause, sans lever
|
|
|
|
|
* @property {(r: Racine) => Promise<'amovible'|'fixe'|'inconnu'>} typeSupport
|
|
|
|
|
* @property {(r: Racine, chemin: string) => Promise<string>} lireTexte
|
|
|
|
|
* le texte tel qu'écrit : marque d'ordre d'octets, fins de ligne et
|
|
|
|
|
* forme de normalisation comprises ; ABSENT pour un fichier absent
|
[FIX] storage, session: strict UTF-8, lost journal, lock, exclusions
The whole-iteration review found ways to lose or corrupt data. A state
saved in another encoding opened with its accents turned to U+FFFD and was
then overwritten; decoding is now strict, the state opens read-only, and a
gesture from its secours first moves it to the trash. A journal decodes line
by line, so one bad byte drops only the lines after it. A vanished journal
is rewritten whole, never appended to. Deleting refuses an event another
session holds; switching to writing rereads the disk; an import that
changes exclu says which reservations it suspends.
Checked: 1504 node, 35 browser, 29 node-long; depot.js, journal.js 100 %.
--- FR ---
[FIX] stockage, séance : UTF-8 strict, journal perdu, verrou, exclusions
La revue d'ensemble a trouvé des façons de perdre ou d'abîmer des données.
Un état enregistré dans un autre encodage s'ouvrait, ses accents changés en
U+FFFD, puis s'écrasait ; le décodage est strict, l'état s'ouvre en lecture
seule, et un geste depuis son secours le range d'abord à la corbeille. Le
journal se décode ligne par ligne : un octet fautif n'écarte que la suite.
Un journal disparu se réécrit en entier, jamais par ajout. Supprimer refuse
un événement qu'une autre séance tient ; passer en écriture relit le disque ;
un import qui change exclu dit quelles réservations il suspend.
Vérifié : 1504 node, 35 navigateur, 29 node-long ; depot.js, journal.js 100 %.
Assisted-by: Claude Opus 5.5
2026-10-07 01:20:42 -04:00
|
|
|
* ou un dossier. Le décodage est strict : des octets qui ne sont
|
|
|
|
|
* pas de l'UTF-8 lèvent LECTURE {chemin, dossier, cause:
|
|
|
|
|
* 'UTF8_INVALIDE', octets}, octets étant un Uint8Array, copie des
|
|
|
|
|
* octets du fichier, que le dépôt décode ligne par ligne
|
[ADD] storage: file-system interface, failing test double, contract
Storage code receives its file system as a parameter (§ 13.4): one
interface, and here the implementation tests inject. It keeps files in
memory, shares a disk between two instances to replay a restart, and
fails on demand: a refused rename that leaves the target intact, writes
refused from a moment on, a process killed after n writes, a failing
probe, a removable medium. One contract suite, its assertions passed in,
will run unchanged on the Electron and web implementations.
Checked: 87 tests, the contract suite run on the test double.
--- FR ---
[ADD] stockage : interface du système de fichiers, double, contrat
Le code du stockage reçoit son système de fichiers en paramètre
(§ 13.4) : une interface, et ici l'implémentation que les épreuves
injectent. Elle garde les fichiers en mémoire, partage un disque entre
deux instances pour rejouer un redémarrage, et tombe en panne à la
demande : renommage refusé qui laisse la cible intacte, écritures
refusées à partir d'un instant, processus tué après n écritures, sonde
qui échoue, support amovible. Une suite de contrat, ses assertions
reçues en paramètre, servira telle quelle sous Electron et sous web.
Vérifié : 87 épreuves, la suite de contrat jouée sur le double.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 13:12:25 -04:00
|
|
|
* @property {(r: Racine, chemin: string, texte: string) => Promise<void>} ecrireAtomique
|
|
|
|
|
* <chemin>.ecriture, vidé sur le disque, renommé par-dessus ; réessaie
|
|
|
|
|
* le renommage ; un échec laisse la cible intacte (§ 8.8) ; là où
|
|
|
|
|
* renommageAtomique est faux, l'écriture va droit sur la cible. Le
|
|
|
|
|
* dossier qui porte la cible doit exister : un dossier absent lève
|
|
|
|
|
* ECRITURE, car un dossier de travail disparu — un support retiré —
|
|
|
|
|
* ne se recrée pas en silence
|
|
|
|
|
* @property {(r: Racine, chemin: string, ligne: string) => Promise<void>} ajouterLigne
|
|
|
|
|
* ajoute la ligne puis une fin de ligne LF après le dernier octet du
|
|
|
|
|
* fichier, sans rien insérer avant, vidé sur le disque ; crée le
|
|
|
|
|
* fichier absent, jamais son dossier. La ligne ne porte aucune fin
|
|
|
|
|
* de ligne
|
|
|
|
|
* @property {(r: Racine, dossier: string) => Promise<Entree[]>} lister
|
|
|
|
|
* les entrées directes du dossier, triées par nom, unités UTF-16
|
|
|
|
|
* comparées une à une ; ABSENT pour un dossier absent
|
|
|
|
|
* @property {(r: Racine, chemin: string) => Promise<void>} creerDossier
|
[ADD] storage: copy, rename, import, delete events; local settings
§ 8.7 operations reuse the event store's pairing and alignment instead of
copying them. Renaming writes the complete pair under the new name — the
store now writes a gesture with the pair it writes elsewhere — before
deleting the old one, so no cut leaves zero pairs; an unchanged name writes
nothing. Deleting moves state, .precedent, journal, lock and leftovers into a
dated trash, matching names as the case-insensitive disk does. creerDossier
creates only the root it is aimed at, so a vanished working folder never
comes back empty. reglages_locaux.json keeps exactly the two § 8.5 settings;
its deferred writer always keeps the last demand.
Checked: 1314 node, 35 browser, 27 node-long tests; depot.js at 100 %.
--- FR ---
[ADD] stockage : copier, renommer, importer, supprimer ; réglages locaux
Les opérations du § 8.7 reprennent l'appariement et l'alignement du dépôt au
lieu de les recopier. Renommer écrit la paire complète sous le nouveau nom —
le dépôt écrit désormais un geste avec la paire qu'il pose ailleurs — avant
de supprimer l'ancienne : aucune coupure ne laisse zéro paire ; un nom
inchangé n'écrit rien. Supprimer range état, .precedent, journal, verrou et
restes dans une corbeille datée, en comparant les noms comme le disque
insensible à la casse. creerDossier ne crée que la racine visée : un dossier
de travail disparu ne renaît pas vide. reglages_locaux.json garde les deux
réglages du § 8.5 ; son écrivain différé garde la dernière demande.
Vérifié : 1314 node, 35 navigateur, 27 node-long ; depot.js à 100 %.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 23:17:05 -04:00
|
|
|
* '' crée la racine elle-même, parents compris ; un autre chemin
|
|
|
|
|
* crée ce qui lui manque sous la racine, qui doit exister : une
|
|
|
|
|
* racine absente lève ECRITURE, sans rien créer, car un dossier de
|
|
|
|
|
* travail disparu ne renaît pas en silence (§ 8.6). Un dossier
|
|
|
|
|
* existant n'est pas une faute
|
[ADD] storage: file-system interface, failing test double, contract
Storage code receives its file system as a parameter (§ 13.4): one
interface, and here the implementation tests inject. It keeps files in
memory, shares a disk between two instances to replay a restart, and
fails on demand: a refused rename that leaves the target intact, writes
refused from a moment on, a process killed after n writes, a failing
probe, a removable medium. One contract suite, its assertions passed in,
will run unchanged on the Electron and web implementations.
Checked: 87 tests, the contract suite run on the test double.
--- FR ---
[ADD] stockage : interface du système de fichiers, double, contrat
Le code du stockage reçoit son système de fichiers en paramètre
(§ 13.4) : une interface, et ici l'implémentation que les épreuves
injectent. Elle garde les fichiers en mémoire, partage un disque entre
deux instances pour rejouer un redémarrage, et tombe en panne à la
demande : renommage refusé qui laisse la cible intacte, écritures
refusées à partir d'un instant, processus tué après n écritures, sonde
qui échoue, support amovible. Une suite de contrat, ses assertions
reçues en paramètre, servira telle quelle sous Electron et sous web.
Vérifié : 87 épreuves, la suite de contrat jouée sur le double.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 13:12:25 -04:00
|
|
|
* @property {(r: Racine, de: string, vers: string) => Promise<void>} deplacer
|
|
|
|
|
* un fichier ; refuse une cible existante (EXISTE), comparée comme le
|
|
|
|
|
* système compare les noms, si bien que sans égard à la casse une
|
|
|
|
|
* autre casse du même nom est une cible existante ; ABSENT pour une
|
|
|
|
|
* source absente ; le dossier de la cible doit exister
|
|
|
|
|
* @property {(r: Racine, chemin: string) => Promise<void>} supprimer
|
|
|
|
|
* un fichier ; un fichier absent n'est pas une faute
|
|
|
|
|
* @property {(r: Racine, chemin: string, seance: string) => Promise<Verrou>} verrouiller
|
[FIX] electron: lock liveness only when certain, retries on every write
A lock judged by its pid alone took a reused pid for a live session after a
reboot, and the event stayed locked. The lock now records the process start
time; a session is dead only when that is certain — no process with the pid,
or two known start times that differ — and alive only when they match.
Otherwise liveness is unknown and the session is not taken over, so two
sessions never write at once (§ 8.8). The pause-and-retry on EPERM, EBUSY
and EACCES that guarded only the rename now guards appends, moves and
deletes too, through one helper.
Checked: 1538 node, 26 node-long tests; the macOS start time is read in UTC.
--- FR ---
[FIX] electron : vie du verrou sûre, réessais sur chaque écriture
Un verrou jugé sur son seul pid prenait un pid réattribué pour une séance
vivante après un redémarrage, et l'événement restait verrouillé. Le verrou
inscrit désormais l'heure de démarrage du processus ; une séance n'est morte
que si c'est certain — aucun processus de ce pid, ou deux heures connues qui
diffèrent — et vivante que si elles concordent. Sinon sa vie est inconnue
et la séance n'est pas reprise : deux séances n'écrivent jamais ensemble
(§ 8.8). La pause et les réessais sur EPERM, EBUSY et EACCES, qui ne
gardaient que le renommage, gardent aussi ajouts, déplacements et
suppressions, par une seule aide.
Vérifié : 1538 node, 26 node-long ; l'heure de démarrage macOS se lit en UTC.
Assisted-by: Claude Opus 5.5
2026-10-07 02:00:36 -04:00
|
|
|
* création exclusive de {seance, pid, hote, depuis, demarrage} :
|
|
|
|
|
* un verrou présent n'est jamais repris, fût-il de la même séance
|
[ADD] storage: file-system interface, failing test double, contract
Storage code receives its file system as a parameter (§ 13.4): one
interface, and here the implementation tests inject. It keeps files in
memory, shares a disk between two instances to replay a restart, and
fails on demand: a refused rename that leaves the target intact, writes
refused from a moment on, a process killed after n writes, a failing
probe, a removable medium. One contract suite, its assertions passed in,
will run unchanged on the Electron and web implementations.
Checked: 87 tests, the contract suite run on the test double.
--- FR ---
[ADD] stockage : interface du système de fichiers, double, contrat
Le code du stockage reçoit son système de fichiers en paramètre
(§ 13.4) : une interface, et ici l'implémentation que les épreuves
injectent. Elle garde les fichiers en mémoire, partage un disque entre
deux instances pour rejouer un redémarrage, et tombe en panne à la
demande : renommage refusé qui laisse la cible intacte, écritures
refusées à partir d'un instant, processus tué après n écritures, sonde
qui échoue, support amovible. Une suite de contrat, ses assertions
reçues en paramètre, servira telle quelle sous Electron et sous web.
Vérifié : 87 épreuves, la suite de contrat jouée sur le double.
Assisted-by: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01EUXSGcwCLSC69FWEdCtWSb
2026-10-06 13:12:25 -04:00
|
|
|
* @property {(r: Racine, chemin: string, seance: string) => Promise<void>} deverrouiller
|
|
|
|
|
* n'efface que le verrou de cette séance ; un verrou absent n'est
|
|
|
|
|
* pas une faute
|
|
|
|
|
* @property {(r: Racine) => Promise<void>} ouvrirDansExplorateur
|
|
|
|
|
* @property {() => Promise<{nom: string, octets: Uint8Array}|null>} choisirFichierAImporter
|
|
|
|
|
* null quand l'opérateur annule
|
|
|
|
|
* @property {(nomPropose: string, octets: Uint8Array) => Promise<string|null>} enregistrerSous
|
|
|
|
|
* le chemin choisi, ou null quand l'opérateur annule
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
// Motif d'un segment que toute implémentation accepte. Un segment vide
|
|
|
|
|
// vient d'un chemin absolu (« /x »), d'un séparateur doublé ou final. Un
|
|
|
|
|
// segment qui finit par un point ou une espace comprend « . » et « .. », et
|
|
|
|
|
// ce que Windows ramène à eux ou à un autre nom en retirant points et espaces
|
|
|
|
|
// finaux : « ... », « .. » suivi d'une espace, « soiree. ». La barre oblique
|
|
|
|
|
// inverse sépare les segments sous Windows ; le deux-points y désigne un
|
|
|
|
|
// lecteur (« C:\x », « C:x ») ou un flux de données secondaire ; le
|
|
|
|
|
// caractère nul tronque un chemin là où la plateforme le lit en C.
|
|
|
|
|
const SEGMENT_ADMIS = /^[^\\:\u{0}]*[^\\:\u{0}. ]$/u;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Segments d'un chemin relatif à une racine : [] pour '', la racine
|
|
|
|
|
* elle-même. Le contrôle ne lit que le texte du chemin ; il ne connaît ni la
|
|
|
|
|
* racine ni les liens, que résout chaque implémentation.
|
|
|
|
|
*
|
|
|
|
|
* @param {string} chemin
|
|
|
|
|
* @returns {string[]}
|
|
|
|
|
* @throws {ErreurStockage} CHEMIN_REFUSE {chemin} : autre chose qu'une chaîne,
|
|
|
|
|
* ou un segment que SEGMENT_ADMIS refuse
|
|
|
|
|
*/
|
|
|
|
|
export function exigerCheminRelatif(chemin) {
|
|
|
|
|
if (typeof chemin !== 'string') throw new ErreurStockage('CHEMIN_REFUSE', { chemin });
|
|
|
|
|
if (chemin === '') return [];
|
|
|
|
|
const segments = chemin.split('/');
|
|
|
|
|
if (!segments.every((segment) => SEGMENT_ADMIS.test(segment))) {
|
|
|
|
|
throw new ErreurStockage('CHEMIN_REFUSE', { chemin });
|
|
|
|
|
}
|
|
|
|
|
return segments;
|
|
|
|
|
}
|