0.3.5-alpha.5

This commit is contained in:
2026-09-22 06:17:39 +02:00
parent 48de882f70
commit bbffb7fa60
7 changed files with 378 additions and 15 deletions

View File

@@ -1,5 +1,5 @@
# file: Cargo.toml # file: Cargo.toml
# version: 100 # version: 101
[workspace] [workspace]
resolver = "3" resolver = "3"
@@ -10,6 +10,7 @@ members = [
"crates/games/game-reflex-poc", "crates/games/game-reflex-poc",
"crates/games/game-snake-poc", "crates/games/game-snake-poc",
"crates/apps/game-realtime-websocket-smoke", "crates/apps/game-realtime-websocket-smoke",
"crates/apps/game-realtime-webtransport-smoke",
"crates/apps/game-reflex-poc-desktop", "crates/apps/game-reflex-poc-desktop",
"crates/apps/game-snake-poc-desktop", "crates/apps/game-snake-poc-desktop",
"crates/common/game-assets-lib", "crates/common/game-assets-lib",
@@ -25,7 +26,7 @@ members = [
] ]
[workspace.package] [workspace.package]
version = "0.3.5-alpha.4.fix.1" version = "0.3.5-alpha.5"
edition = "2024" edition = "2024"
license = "MIT" license = "MIT"
repository = "https://git.sasedev.com/Sasedev/games" repository = "https://git.sasedev.com/Sasedev/games"

View File

@@ -1,5 +1,5 @@
<!-- file: README.md --> <!-- file: README.md -->
<!-- version: 68 --> <!-- version: 69 -->
# games.sasedev # games.sasedev
@@ -27,9 +27,9 @@ Workspace expérimental puis productif pour des jeux multiplateformes principale
Version stable de référence : `0.3.4`. Version stable de référence : `0.3.4`.
Version active : `0.3.5-alpha.4.fix.1`. `0.3.2` reste différée. Version active : `0.3.5-alpha.5`. `0.3.2` reste différée.
La stable `0.3.4` livre la première baseline realtime : contrat binaire transport-neutral, backend WebSocket Tokio/tokio-tungstenite, limites et deadlines, tests loopback/robustesse, smoke runtime localhost public et frontières de dépendances empêchant moteurs et gameplay de dépendre d'un backend concret. `0.3.5-alpha.4.fix.1` consolide le backend WebTransport natif avec limite de message configurable, deadlines connexion/stream/send, mapping d'erreurs, reset/abort/drop explicites et parseur de framing incrémental résistant à l'annulation ; le fix ferme uniquement le lint strict `implicit-return` révélé par la gate de `alpha.4` sans changer le comportement runtime. Navigateur/WASM, smoke public, fallback et datagrams restent réservés aux tranches suivantes. La stable `0.3.4` livre la première baseline realtime : contrat binaire transport-neutral, backend WebSocket Tokio/tokio-tungstenite, limites et deadlines, tests loopback/robustesse, smoke runtime localhost public et frontières de dépendances empêchant moteurs et gameplay de dépendre d'un backend concret. `0.3.5-alpha.5` ajoute au backend WebTransport natif déjà robuste un smoke runtime localhost hors harness : identité TLS locale en mémoire, pin SHA-256 exact, session WebTransport réelle, stream fiable principal, round-trip binaire bidirectionnel et fermeture propre avec verdict `PASS` déterministe. Navigateur/WASM, fallback et datagrams restent réservés aux tranches suivantes.
Les deux premiers jeux sont des POC structurels : `game-reflex-poc` et `game-snake-poc`. Ils existent d'abord pour valider les frontières du workspace, le moteur, les assets et le packaging multiplateforme. Les deux premiers jeux sont des POC structurels : `game-reflex-poc` et `game-snake-poc`. Ils existent d'abord pour valider les frontières du workspace, le moteur, les assets et le packaging multiplateforme.
@@ -45,4 +45,4 @@ Les deux premiers jeux sont des POC structurels : `game-reflex-poc` et `game-sna
## Diagnostics et tests ## Diagnostics et tests
Les socles transverses `crates/common/game-assets-lib` et `crates/common/game-logging-lib` fournissent respectivement la résolution logique des assets et le tracing commun. Le realtime est séparé entre `game-realtime-transport-lib`, contrat binaire transport-neutral, `game-realtime-websocket-lib`, backend Tokio/tokio-tungstenite, et `game-realtime-webtransport-lib`, backend WebTransport/QUIC candidat dont le chemin natif couvre désormais TLS/pinning, établissement de session et stream fiable principal adapté au contrat commun. Leurs responsabilités sont documentées dans leurs README/USAGE locaux lorsqu'un guide d'usage est justifié. `game-realtime-websocket-smoke` fournit la preuve runtime localhost hors harness de test ; le smoke WebTransport public est planifié après la tranche de robustesse. Les tests unitaires résident hors `src/` sous `unit_tests/`; les tests dintégration/environnement résident sous `tests/`. Les socles transverses `crates/common/game-assets-lib` et `crates/common/game-logging-lib` fournissent respectivement la résolution logique des assets et le tracing commun. Le realtime est séparé entre `game-realtime-transport-lib`, contrat binaire transport-neutral, `game-realtime-websocket-lib`, backend Tokio/tokio-tungstenite, et `game-realtime-webtransport-lib`, backend WebTransport/QUIC candidat dont le chemin natif couvre désormais TLS/pinning, établissement de session et stream fiable principal adapté au contrat commun. Leurs responsabilités sont documentées dans leurs README/USAGE locaux lorsqu'un guide d'usage est justifié. `game-realtime-websocket-smoke` et `game-realtime-webtransport-smoke` fournissent les preuves runtime localhost hors harness des deux backends fiables natifs. Les tests unitaires résident hors `src/` sous `unit_tests/`; les tests dintégration/environnement résident sous `tests/`.

View File

@@ -0,0 +1,21 @@
# file: crates/apps/game-realtime-webtransport-smoke/Cargo.toml
# version: 1
[package]
name = "game-realtime-webtransport-smoke"
version.workspace = true
edition.workspace = true
license.workspace = true
repository.workspace = true
authors.workspace = true
publish.workspace = true
[dependencies]
game-logging-lib = { path = "../../common/game-logging-lib" }
game-realtime-transport-lib = { path = "../../common/game-realtime-transport-lib" }
game-realtime-webtransport-lib = { path = "../../common/game-realtime-webtransport-lib" }
tokio = { workspace = true, features = ["macros", "rt", "time"] }
tracing.workspace = true
[lints]
workspace = true

View File

@@ -0,0 +1,134 @@
// file: crates/apps/game-realtime-webtransport-smoke/src/main.rs
// version: 1
#![warn(missing_docs)]
#![deny(unreachable_pub)]
#![forbid(unsafe_code)]
//! Executable localhost smoke for the public games.sasedev native WebTransport realtime path.
use game_realtime_transport_lib::RealtimeConnection; // rust-rules: trait-import
use game_realtime_transport_lib::RealtimeReceiver; // rust-rules: trait-import
use game_realtime_transport_lib::RealtimeSender; // rust-rules: trait-import
const CLIENT_PAYLOAD: &[u8] = b"games.sasedev-webtransport-client-smoke";
const SERVER_PAYLOAD: &[u8] = b"games.sasedev-webtransport-server-smoke";
const SMOKE_TIMEOUT: std::time::Duration = std::time::Duration::from_secs(10);
const TRACING_TARGET: &str = "games::realtime::webtransport::smoke";
#[tokio::main(flavor = "current_thread")]
async fn main() -> std::process::ExitCode {
let _logging_guard = match game_logging_lib::init_console_tracing() {
std::result::Result::Ok(guard) => guard,
std::result::Result::Err(error) => {
eprintln!("failed to initialize realtime WebTransport smoke tracing: {error}");
return std::process::ExitCode::FAILURE;
},
};
tracing::info!(target: TRACING_TARGET, "realtime WebTransport smoke started");
let result = tokio::time::timeout(SMOKE_TIMEOUT, run_smoke()).await;
return match result {
std::result::Result::Ok(std::result::Result::Ok(())) => {
tracing::info!(target: TRACING_TARGET, "realtime WebTransport smoke passed");
println!("game-realtime-webtransport-smoke: PASS");
std::process::ExitCode::SUCCESS
},
std::result::Result::Ok(std::result::Result::Err(error)) => {
tracing::error!(target: TRACING_TARGET, detail = error.as_str(), "realtime WebTransport smoke failed");
eprintln!("game-realtime-webtransport-smoke: FAIL: {error}");
std::process::ExitCode::FAILURE
},
std::result::Result::Err(_) => {
tracing::error!(target: TRACING_TARGET, timeout_ms = SMOKE_TIMEOUT.as_millis(), "realtime WebTransport smoke timed out");
eprintln!("game-realtime-webtransport-smoke: FAIL: smoke timed out");
std::process::ExitCode::FAILURE
},
};
}
async fn run_smoke() -> std::result::Result<(), String> {
let identity = match game_realtime_webtransport_lib::WebTransportServerIdentity::generate_loopback() {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(format!("loopback identity generation failed: {error}")),
};
let certificate_hash = identity.certificate_hash().clone();
let bind_address = std::net::SocketAddr::from(([127, 0, 0, 1], 0));
let server_config = game_realtime_webtransport_lib::WebTransportServerConfig::new(bind_address, identity);
let mut listener = match game_realtime_webtransport_lib::WebTransportListener::bind(server_config) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(format!("listener bind failed: {error}")),
};
let endpoint = format!("https://{}/smoke", listener.local_addr());
let client_config = match game_realtime_webtransport_lib::WebTransportClientConfig::new(endpoint.as_str(), certificate_hash) {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(format!("client configuration failed: {error}")),
};
tracing::info!(target: TRACING_TARGET, endpoint = endpoint.as_str(), "loopback endpoint bound with exact certificate pin");
let (server_result, client_result) = tokio::join!(listener.accept(), game_realtime_webtransport_lib::connect(&client_config));
let server_session = match server_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(format!("server accept failed: {error}")),
};
let client_session = match client_result {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(format!("client connect failed: {error}")),
};
let client_connection = match client_session.open_primary_connection().await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(format!("client primary stream open failed: {error}")),
};
let server_connection = match server_session.accept_primary_connection().await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(format!("server primary stream accept failed: {error}")),
};
let (mut server_sender, mut server_receiver) = server_connection.split();
let (mut client_sender, mut client_receiver) = client_connection.split();
if let std::result::Result::Err(error) = client_sender.send(game_realtime_transport_lib::TransportMessage::new(CLIENT_PAYLOAD.to_vec())).await {
return std::result::Result::Err(format!("client send failed: {error}"));
}
let server_received = match server_receiver.receive().await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(format!("server receive failed: {error}")),
};
if !receive_matches(server_received, CLIENT_PAYLOAD) {
return std::result::Result::Err(String::from("server did not receive the expected client payload"));
}
if let std::result::Result::Err(error) = server_sender.send(game_realtime_transport_lib::TransportMessage::new(SERVER_PAYLOAD.to_vec())).await {
return std::result::Result::Err(format!("server send failed: {error}"));
}
let client_received = match client_receiver.receive().await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(format!("client receive failed: {error}")),
};
if !receive_matches(client_received, SERVER_PAYLOAD) {
return std::result::Result::Err(String::from("client did not receive the expected server payload"));
}
if let std::result::Result::Err(error) = client_sender.close().await {
return std::result::Result::Err(format!("client close failed: {error}"));
}
let server_close = match server_receiver.receive().await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(format!("server close observation failed: {error}")),
};
if server_close != game_realtime_transport_lib::TransportReceive::Closed {
return std::result::Result::Err(String::from("server did not observe the client FIN"));
}
if let std::result::Result::Err(error) = server_sender.close().await {
return std::result::Result::Err(format!("server close failed: {error}"));
}
let client_close = match client_receiver.receive().await {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => return std::result::Result::Err(format!("client close observation failed: {error}")),
};
if client_close != game_realtime_transport_lib::TransportReceive::Closed {
return std::result::Result::Err(String::from("client did not observe the server FIN"));
}
return std::result::Result::Ok(());
}
fn receive_matches(receive: game_realtime_transport_lib::TransportReceive, expected: &[u8]) -> bool {
return match receive {
game_realtime_transport_lib::TransportReceive::Message(message) => message.as_bytes() == expected,
game_realtime_transport_lib::TransportReceive::Closed => false,
};
}

126
deltas/0.3.5/alpha.5.md Normal file
View File

@@ -0,0 +1,126 @@
<!-- file: deltas/0.3.5/alpha.5.md -->
<!-- version: 1 -->
# Delta 0.3.5-alpha.5
## Base requise
`0.3.5-alpha.4.fix.1`, validée par l'utilisateur le 2026-09-21 avec fmt, audits, `cargo check`, Clippy strict, les sept tests du contrat realtime commun et les vingt tests WebTransport.
La preuve est enregistrée dans `history/0.3.5/alpha.4.fix.1.md`.
## Objectif
Fermer la preuve runtime native WebTransport hors harness de test sans ajouter de fonctionnalité à la bibliothèque.
Cette tranche doit démontrer avec les APIs publiques réellement distribuées que le chemin localhost complet fonctionne depuis un exécutable normal : TLS local, pinning, session WebTransport, stream fiable principal, contrat realtime commun, round-trip bidirectionnel et fermeture propre.
## Version
La version workspace passe à :
```text
0.3.5-alpha.5
```
## Launcher technique
Nouvelle crate :
```text
crates/apps/game-realtime-webtransport-smoke
```
Elle dépend uniquement de :
```text
game-logging-lib
game-realtime-transport-lib
game-realtime-webtransport-lib
tokio
tracing
```
Aucune nouvelle dépendance externe n'est introduite.
Le launcher :
1. initialise le tracing commun ;
2. génère une identité TLS loopback ECDSA P-256 courte durée en mémoire ;
3. dérive le hash SHA-256 exact exposé par cette identité ;
4. bind le listener WebTransport sur `127.0.0.1:0` ;
5. construit le client `https://<adresse>/smoke` avec ce hash pin ;
6. établit simultanément les sessions client et serveur ;
7. ouvre/accepte le stream bidirectionnel primaire ;
8. échange un payload client puis un payload serveur via le contrat `RealtimeConnection` ;
9. ferme proprement le sender client et vérifie `Closed` côté serveur ;
10. ferme proprement le sender serveur et vérifie `Closed` côté client ;
11. imprime exactement :
```text
game-realtime-webtransport-smoke: PASS
```
Une deadline globale de 10 secondes empêche le smoke de rester bloqué indéfiniment.
## Frontière de dépendances
Le backend WebTransport est désormais consommé par un launcher technique public, ce qui est intentionnel. Il doit toujours rester absent des crates engine et gameplay.
Le graphe inverse attendu peut donc contenir :
```text
game-realtime-webtransport-lib
game-realtime-webtransport-smoke
```
mais aucune crate `engine-v1-*`, `game-snake-poc` ou `game-reflex-poc`.
## Fichiers modifiés
```text
Cargo.toml
README.md
docs/plans/005-V0_3_5_WEBTRANSPORT_QUIC_POC_PLAN.md
```
Nouveaux fichiers :
```text
crates/apps/game-realtime-webtransport-smoke/Cargo.toml
crates/apps/game-realtime-webtransport-smoke/src/main.rs
deltas/0.3.5/alpha.5.md
history/0.3.5/alpha.4.fix.1.md
```
## Validation attendue
```bash
cargo fmt --all
cargo fmt --all -- --check
python3 scripts/audit_rust_workspace_rules.py
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates Android Web deltas history
python3 scripts/audit_distribution_layout.py
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p game-realtime-transport-lib --all-targets --all-features
cargo test -p game-realtime-webtransport-lib --all-targets --all-features
cargo run -p game-realtime-webtransport-smoke
cargo tree -p game-realtime-webtransport-smoke --edges normal
cargo tree -i game-realtime-webtransport-lib --workspace --edges normal
```
Le smoke doit se terminer avec :
```text
game-realtime-webtransport-smoke: PASS
```
## Après validation
Créer `history/0.3.5/alpha.5.md` à partir des sorties réellement fournies, puis ouvrir `0.3.5-alpha.6` pour le chemin WASM compilable. Le smoke navigateur/TLS réel reste réservé à `alpha.7`.

View File

@@ -1,11 +1,11 @@
<!-- file: docs/plans/005-V0_3_5_WEBTRANSPORT_QUIC_POC_PLAN.md --> <!-- file: docs/plans/005-V0_3_5_WEBTRANSPORT_QUIC_POC_PLAN.md -->
<!-- version: 6 --> <!-- version: 7 -->
# Plan 0.3.5 — POC WebTransport/QUIC et fallback WebSocket # Plan 0.3.5 — POC WebTransport/QUIC et fallback WebSocket
## Statut ## 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`, le chemin fiable validé de `0.3.5-alpha.3`, la robustesse candidate de `0.3.5-alpha.4` et son correctif de gate `0.3.5-alpha.4.fix.1`. 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`, la robustesse validée par `0.3.5-alpha.4.fix.1` et le smoke natif candidat de `0.3.5-alpha.5`.
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. 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.
@@ -457,17 +457,23 @@ Tranche candidate matérialisée avec :
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. 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 de `alpha.4` a confirmé fmt, audits, `cargo check`, tous les tests du contrat commun et les vingt tests WebTransport, mais Clippy strict a détecté un unique `clippy::implicit-return` dans la future retournée par `RealtimeReceiver::receive()`. `alpha.4.fix.1` explicite ce `return` sans changement sémantique, d'API, de framing, de lifecycle ou de dépendance. La gate complète du fix reste requise avant d'ouvrir `alpha.5`. La gate utilisateur de `alpha.4` a confirmé fmt, audits, `cargo check`, tous les tests du contrat commun et les vingt tests WebTransport, mais Clippy strict a détecté un unique `clippy::implicit-return` dans la future retournée par `RealtimeReceiver::receive()`. `alpha.4.fix.1` a explicité ce `return` sans changement sémantique, d'API, de framing, de lifecycle ou de dépendance. Sa gate complète est ensuite passée : audits, check workspace, Clippy strict, sept tests du contrat commun et vingt tests WebTransport. La preuve est conservée dans `history/0.3.5/alpha.4.fix.1.md`.
### `0.3.5-alpha.5` — smoke natif hors harness ### `0.3.5-alpha.5` — smoke natif hors harness
- créer ou finaliser `game-realtime-webtransport-smoke` ; Tranche candidate matérialisée avec :
- round-trip réel localhost ;
- certificat/hash de développement reproductible ;
- `PASS` déterministe ;
- contrôle du graphe de dépendances.
Cette tranche reste distincte pour ne pas mélanger robustesse de bibliothèque et preuve runtime publique. - nouvelle app technique `game-realtime-webtransport-smoke` ;
- bind UDP loopback sur port éphémère ;
- identité TLS ECDSA P-256 locale générée en mémoire et pin SHA-256 dérivé de cette identité ;
- établissement WebTransport client/server réel sur `https://127.0.0.1:<port>/smoke` ;
- ouverture/accept du stream fiable principal via l'API publique du backend ;
- round-trip binaire bidirectionnel via `RealtimeConnection` ;
- FIN propre client puis serveur, chacun observé comme `TransportReceive::Closed` par le pair ;
- timeout global du launcher et verdict terminal déterministe `game-realtime-webtransport-smoke: PASS` ;
- aucune nouvelle dépendance externe et aucun couplage engine/gameplay.
Cette tranche reste distincte pour ne pas mélanger robustesse de bibliothèque et preuve runtime publique. La gate doit exécuter le smoke hors harness puis contrôler les graphes direct et inverse : l'inverse de `game-realtime-webtransport-lib` peut désormais contenir le launcher technique, mais aucune crate engine ou gameplay.
### `0.3.5-alpha.6` — chemin WASM compilable ### `0.3.5-alpha.6` — chemin WASM compilable

View File

@@ -0,0 +1,75 @@
<!-- file: history/0.3.5/alpha.4.fix.1.md -->
<!-- version: 1 -->
# Historique 0.3.5-alpha.4.fix.1
## Statut
`0.3.5-alpha.4.fix.1` a été validée par l'utilisateur le 2026-09-21. La tranche robustesse/lifecycle WebTransport est donc fermée : configuration, deadlines, framing négatif, cancellation, reset/abort/drop et mapping d'erreurs passent avec Clippy strict.
La suite peut ouvrir `0.3.5-alpha.5` pour le smoke natif hors harness sans réouvrir le scope de la bibliothèque.
## Gates statiques et compilation
Les commandes utilisateur ont terminé proprement :
```text
cargo fmt --all
cargo fmt --all -- --check
python3 scripts/audit_rust_workspace_rules.py
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates Android Web deltas history
python3 scripts/audit_distribution_layout.py
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
```
La sortie confirme :
```text
General Rust rule audit: clean
Rust export completeness audit: 0 candidate(s)
games.sasedev workspace audit: clean
Markdown table audit: clean (5 table(s), 292 file(s))
Distribution layout audit: clean (50 required path(s), 8 forbidden path(s) absent)
cargo check --workspace: clean
cargo clippy --workspace --all-targets --all-features -- -D warnings: clean
```
Le workspace compilé porte `0.3.5-alpha.4.fix.1`, y compris `game-realtime-webtransport-lib` et l'ensemble des targets workspace affichées par la gate.
## Tests du contrat realtime commun
La commande :
```bash
cargo test -p game-realtime-transport-lib --all-targets --all-features
```
est entièrement propre :
```text
7 passed; 0 failed
```
## Tests WebTransport
La commande :
```bash
cargo test -p game-realtime-webtransport-lib --all-targets --all-features
```
est entièrement propre :
```text
unit tests: 11 passed; 0 failed
establishment integration tests: 2 passed; 0 failed
realtime_connection integration tests: 1 passed; 0 failed
robustness integration tests: 6 passed; 0 failed
```
Les vingt tests WebTransport couvrent donc l'établissement et le pinning, le framing fiable, le round-trip primaire, les limites configurables, les frames tronquées, la cancellation de receive, les reset/abort/drop et la deadline d'accept du stream primaire.
## Conséquence
`alpha.5` peut se limiter à une preuve runtime publique hors harness : launcher technique dédié, bind éphémère, identité TLS locale en mémoire, pin SHA-256 exact, round-trip binaire bidirectionnel, FIN propre dans les deux directions et verdict `PASS` déterministe.