0.3.5-alpha.4

This commit is contained in:
2026-09-21 23:37:18 +02:00
parent fd6ffecdf5
commit ce3811f8ec
14 changed files with 1284 additions and 147 deletions

View File

@@ -1,11 +1,11 @@
<!-- file: docs/plans/005-V0_3_5_WEBTRANSPORT_QUIC_POC_PLAN.md -->
<!-- version: 4 -->
<!-- version: 5 -->
# Plan 0.3.5 — POC WebTransport/QUIC et fallback WebSocket
## Statut
Plan actif créé pendant `0.3.5-alpha.1` à partir de l'archive taggée `v0.3.4`, puis réconcilié pour l'implémentation native de `0.3.5-alpha.2`, son correctif de validation `0.3.5-alpha.2.fix.1` et le chemin fiable candidat de `0.3.5-alpha.3`.
Plan actif créé pendant `0.3.5-alpha.1` à partir de l'archive taggée `v0.3.4`, puis réconcilié pour l'implémentation native de `0.3.5-alpha.2`, son correctif de validation `0.3.5-alpha.2.fix.1`, le chemin fiable validé de `0.3.5-alpha.3` et la robustesse candidate de `0.3.5-alpha.4`.
Le cadrage détaillé et la comparaison des stacks actuelles sont conservés dans `docs/studies/026-V0_3_5_WEBTRANSPORT_QUIC_STACK_AUDIT.md`. Le présent document porte les décisions opérationnelles, le scope, les gates et le forecast vivant de la version.
@@ -92,6 +92,35 @@ Le FIN du stream primaire constitue la fermeture logique de base de `alpha.3` et
`web-transport-quinn` écrit l'en-tête WebTransport du stream durant `open_bi()` avant de rendre le stream au code appelant. L'accept serveur peut donc terminer avant la première frame applicative ; le framing games.sasedev commence directement au premier octet applicatif et n'ajoute aucun préambule de visibilité.
La gate utilisateur de `alpha.3` a ensuite confirmé fmt, audits, check workspace, Clippy strict, les sept tests du contrat commun, les sept tests unitaires/établissement WebTransport déjà présents et le round-trip primaire dédié. Le graphe inverse confirme aussi que `game-realtime-webtransport-lib` reste un backend feuille et ne remonte pas dans engine/gameplay.
### Fermeture robustesse et lifecycle en alpha.4
`alpha.4` remplace la borne interne figée par `WebTransportConfig`, partagé par les configs client/server. Les valeurs par défaut restent volontairement conservatrices pour le POC : 1 MiB par message, 10 s pour la connexion/fin de réponse CONNECT, 5 s pour l'ouverture ou l'accept du stream primaire et 5 s pour un envoi complet. Les valeurs nulles ou une taille incompatible avec le champ de longueur `u32` sont rejetées avant démarrage.
La notion de deadline est volontairement opérationnelle et non un timeout d'inactivité global :
- le client borne l'établissement de session ;
- côté serveur, l'attente du prochain pair reste non bornée comme un listener normal, puis la réponse WebTransport après matérialisation de la requête CONNECT est bornée ;
- ouverture/accept du stream primaire sont bornés ;
- un envoi header + payload est borné, de sorte qu'un blocage durable sous flow-control/backpressure devient un `TransportErrorKind::Timeout` visible ;
- `receive()` n'a pas de timeout d'idle implicite : l'absence de message n'est pas une panne de transport et l'appelant peut annuler sa future sans perdre l'état du framing.
Le receiver utilise donc un parseur incrémental persistant fondé sur la lecture cancel-safe du backend. Une future `receive()` abandonnée après une partie du header ou du payload peut être relancée et reprend au bon octet. EOF au milieu d'une frame devient `Protocol`, tandis qu'un EOF à frontière de frame reste `TransportReceive::Closed`. Une longueur supérieure à la borne est refusée avant allocation puis la direction de réception est stoppée.
Le lifecycle distingue désormais explicitement FIN propre et abandon :
- `RealtimeSender::close()` émet le FIN propre ;
- `WebTransportSender::abort(code)` reset la direction d'envoi ;
- `WebTransportReceiver::abort(code)` stoppe la direction de réception ;
- drop d'un sender/receiver encore actif provoque reset/stop au lieu de synthétiser une fermeture propre ;
- cancellation d'un `send()` en cours arme un guard terminal qui reset le stream, car une frame partiellement écrite ne peut pas être reprise sans ambiguïté ;
- cancellation d'un `receive()` reste non terminale grâce au parseur incrémental.
Les erreurs amont restent confinées au backend : reset/STOP observables -> `Aborted`, FIN/stream/session fermé proprement -> `Closed`, framing/état de stream invalide -> `Protocol`, dépassement -> `MessageTooLarge`, deadline -> `Timeout`, et autres erreurs de session -> `Io`. Aucun type Quinn/WebTransport n'entre dans `game-realtime-transport-lib`.
Aucune file applicative n'est ajoutée pour fabriquer artificiellement `Backpressure` : la pression est celle du flow-control QUIC. Lorsqu'elle empêche un envoi de terminer dans sa deadline, l'erreur observable est `Timeout`. Un test synthétique de saturation n'est pas imposé tant qu'il ne peut pas être rendu déterministe sans dépendre d'internals amont.
### Ownership physique
Nouveau backend durable candidat :
@@ -397,7 +426,7 @@ Pas encore d'implémentation complète `RealtimeConnection` si cela rend la tran
### `0.3.5-alpha.3` — stream fiable et contrat commun
Tranche candidate matérialisée avec :
Tranche validée avec :
- stream bidirectionnel principal ;
- framing `u32 + payload` borné avant allocation ;
@@ -408,18 +437,25 @@ Tranche candidate matérialisée avec :
- tests loopback ciblés ;
- `USAGE.md` pour la séquence session -> stream primaire -> contrat commun.
La gate utilisateur reste requise avant d'ouvrir `alpha.4`.
La gate utilisateur du 2026-09-21 est conservée dans `history/0.3.5/alpha.3.md`.
### `0.3.5-alpha.4` — robustesse et lifecycle
- limites ;
- deadlines ;
- backpressure/erreurs observables ;
- close/reset/abort ;
- cancellation/drop ;
- cas négatifs de framing ;
- mapping d'erreurs ;
- tests de robustesse ciblés.
Tranche candidate matérialisée avec :
- `WebTransportConfig` et borne de message configurable, validée avant démarrage ;
- deadlines de connexion/réponse CONNECT, stream primaire et send ;
- flow-control QUIC conservé comme backpressure naturelle, avec timeout observable si un send reste bloqué ;
- FIN propre distinct des reset/stop explicites ;
- drop actif -> abort au lieu de FIN synthétique ;
- cancellation d'un send partiel -> reset terminal ;
- receive incrémental cancel-safe et reprenable ;
- EOF au milieu d'une frame et framing invalide -> `Protocol` ;
- dépassement entrant refusé avant allocation ;
- mapping backend -> `Timeout`, `MessageTooLarge`, `Closed`, `Protocol`, `Aborted` ou `Io` ;
- tests ciblés sur limites, reset/drop, deadline du stream, cancellation de receive et frame tronquée.
Un timeout d'idle de `receive()` n'est volontairement pas imposé au contrat : l'appelant peut borner/annuler l'attente sans corrompre l'état du frame parser. Un test artificiel de saturation/backpressure n'est pas ajouté tant qu'il n'est pas déterministe. La gate utilisateur reste requise avant d'ouvrir `alpha.5`.
### `0.3.5-alpha.5` — smoke natif hors harness