v0.3.2-pre.009

This commit is contained in:
2026-08-29 22:00:20 +02:00
parent 80eea82398
commit 9ec4f26874
6 changed files with 753 additions and 35 deletions

View File

@@ -1,12 +1,12 @@
# file: Cargo.toml # file: Cargo.toml
# version: 342 # version: 343
[workspace] [workspace]
resolver = "3" resolver = "3"
members = ["crates/ksp-app-config-desk", "crates/ksp-app-solprices-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-interface-lib", "crates/ksp-logging-lib", "crates/ksp-offchain-transport-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-program-api", "crates/ksp-store-api", "crates/ksp-store-lib", "crates/ksp-store-postgres-lib", "crates/ksp-wallet-lib"] members = ["crates/ksp-app-config-desk", "crates/ksp-app-solprices-desk", "crates/ksp-app-wallet-desk", "crates/ksp-config-lib", "crates/ksp-core-lib", "crates/ksp-interface-lib", "crates/ksp-logging-lib", "crates/ksp-offchain-transport-lib", "crates/ksp-onchain-transport-lib", "crates/ksp-program-api", "crates/ksp-store-api", "crates/ksp-store-lib", "crates/ksp-store-postgres-lib", "crates/ksp-wallet-lib"]
[workspace.package] [workspace.package]
version = "0.3.2-pre.8" version = "0.3.2-pre.9"
edition = "2024" edition = "2024"
license = "MIT" license = "MIT"
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project" repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"

View File

@@ -0,0 +1,256 @@
// file: crates/ksp-store-lib/tests/hardening_completeness.rs
// version: 1
#![warn(missing_docs)]
#![deny(unreachable_pub)]
#![forbid(unsafe_code)]
//! Hardening, exact-surface and feature-matrix canaries for the common Store facade.
const SECRET_CANARY: &str = "KSP-STORE-SECRET-CANARY-PRE009";
fn poll_ready<T>(future: impl std::future::Future<Output = T>) -> T {
let mut future = std::boxed::Box::pin(future);
let waker = std::task::Waker::noop();
let mut context = std::task::Context::from_waker(waker);
return match std::future::Future::poll(future.as_mut(), &mut context) {
std::task::Poll::Ready(value) => value,
std::task::Poll::Pending => panic!("Store hardening pre-I/O rejection unexpectedly became pending"),
};
}
fn valid_network() -> ksp_store_lib::RawNetworkId {
return match ksp_store_lib::RawNetworkId::new("devnet") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("valid hardening network rejected: {error:?}"),
};
}
fn hostile_settings(connection_uri: &str) -> ksp_store_lib::StoreSettings {
let postgres = ksp_store_lib::PostgresStoreSettings::new(
connection_uri,
ksp_store_lib::PostgresPoolSettings::default(),
ksp_store_lib::PostgresTlsMode::VerifyFull,
ksp_store_lib::PostgresBootstrapSettings::default(),
);
return ksp_store_lib::StoreSettings::with_default_shutdown(valid_network(), ksp_store_lib::StoreBackendSettings::Postgres(postgres));
}
fn public_reexport_names(source: &str) -> std::vec::Vec<&str> {
let mut names = std::vec::Vec::new();
for line in source.lines() {
let trimmed = line.trim();
if !trimmed.starts_with("pub use ") || !trimmed.ends_with(';') {
continue;
}
let without_semicolon = trimmed.trim_end_matches(';');
let name = match without_semicolon.rsplit("::").next() {
std::option::Option::Some(value) => value,
std::option::Option::None => continue,
};
names.push(name);
}
names.sort_unstable();
return names;
}
fn manifest_dependency_names(source: &str) -> std::vec::Vec<&str> {
let dependencies_tail = match source.split("[dependencies]").nth(1) {
std::option::Option::Some(value) => value,
std::option::Option::None => return std::vec::Vec::new(),
};
let dependencies = match dependencies_tail.split("[lints]").next() {
std::option::Option::Some(value) => value,
std::option::Option::None => return std::vec::Vec::new(),
};
let mut names = std::vec::Vec::new();
for line in dependencies.lines() {
let content = match line.split('#').next() {
std::option::Option::Some(value) => value.trim(),
std::option::Option::None => continue,
};
if content.is_empty() {
continue;
}
let key = match content.split('=').next() {
std::option::Option::Some(value) => value.trim().trim_end_matches(".workspace"),
std::option::Option::None => continue,
};
if !key.is_empty() {
names.push(key);
}
}
names.sort_unstable();
return names;
}
#[test]
fn pre_009_facade_modules_and_crate_root_exports_are_exact() {
let crate_root = include_str!("../src/lib.rs");
for required in ["mod constants;", "mod error;", "mod health;", "mod settings;", "mod store;"] {
assert!(crate_root.contains(required), "missing Store facade module: {required}");
}
assert!(!crate_root.contains("pub mod "));
let actual = public_reexport_names(crate_root);
let mut expected = [
"ERROR_CODE_BACKEND_CLOSED",
"ERROR_CODE_BACKEND_NOT_COMPILED",
"ERROR_CODE_BACKEND_OPEN_FAILED",
"ERROR_CODE_POSTGRES_CONFIG_INVALID",
"ERROR_CODE_POSTGRES_CONNECT_FAILED",
"ERROR_CODE_POSTGRES_HEALTH_FAILED",
"ERROR_CODE_POSTGRES_MIGRATION_FAILED",
"ERROR_CODE_POSTGRES_MIGRATION_MISMATCH",
"ERROR_CODE_POSTGRES_POOL_TIMEOUT",
"ERROR_CODE_POSTGRES_SCHEMA_NEWER",
"ERROR_CODE_POSTGRES_TLS_FAILED",
"ERROR_CODE_RAW_CONFLICT",
"ERROR_CODE_RAW_MODEL_INVALID",
"ERROR_CODE_RAW_PAYLOAD_INVALID",
"ERROR_CODE_RAW_PROVENANCE_INVALID",
"ERROR_CODE_RAW_QUERY_INVALID",
"ERROR_CODE_RAW_RETENTION_INVALID",
"ERROR_CODE_SETTINGS_INVALID",
"ERROR_CODE_SHUTDOWN_TIMEOUT",
"Error",
"ErrorCode",
"ErrorContext",
"MAX_RAW_ACCOUNT_DATA_BYTES",
"MAX_RAW_CODE_BYTES",
"MAX_RAW_PAGE_CURSOR_BYTES",
"MAX_RAW_PAYLOAD_BYTES",
"MAX_RAW_SOURCE_PAYLOAD_BYTES",
"MAX_RAW_UNIX_MILLIS",
"PostgresBootstrapSettings",
"PostgresPoolSettings",
"PostgresStoreSettings",
"PostgresTlsMode",
"Pubkey",
"RawAccountObservation",
"RawAccountObservationRead",
"RawAccountObservationWrite",
"RawAccountState",
"RawAccountStateQuery",
"RawAccountStateRead",
"RawAccountStateReference",
"RawAccountStateWrite",
"RawAcquisitionOrigin",
"RawAcquisitionProvenance",
"RawAcquisitionWriteOutcome",
"RawContentHash",
"RawEntityWriteOutcome",
"RawFormatId",
"RawNetworkId",
"RawObservationKey",
"RawObservationWriteOutcome",
"RawPage",
"RawPageCursor",
"RawPageLimit",
"RawPageRequest",
"RawPayload",
"RawProvenanceCode",
"RawRetentionState",
"RawRetentionWriteOutcome",
"RawSlotRange",
"RawSortDirection",
"RawTimestamp",
"RawTransaction",
"RawTransactionAcquisitionMode",
"RawTransactionObservation",
"RawTransactionObservationRead",
"RawTransactionObservationWrite",
"RawTransactionQuery",
"RawTransactionRead",
"RawTransactionReference",
"RawTransactionRetentionRead",
"RawTransactionRetentionTransition",
"RawTransactionRetentionWrite",
"RawTransactionSignature",
"RawTransactionTombstone",
"RawTransactionWrite",
"Result",
"Store",
"StoreApiFuture",
"StoreBackendKind",
"StoreBackendSettings",
"StoreHealthSnapshot",
"StoreHealthState",
"StoreRuntimeSnapshot",
"StoreSettings",
];
expected.sort_unstable();
assert_eq!(actual.as_slice(), expected.as_slice());
assert_eq!(actual.len(), 84);
return;
}
#[test]
fn pre_009_facade_manifest_and_feature_contract_are_exact() {
let manifest = include_str!("../Cargo.toml");
assert!(manifest.contains("default = [\"postgres\"]"));
assert!(manifest.contains("postgres = [\"dep:ksp-store-postgres-lib\"]"));
let actual = manifest_dependency_names(manifest);
let expected = ["ksp-logging-lib", "ksp-store-api", "ksp-store-postgres-lib"];
assert_eq!(actual.as_slice(), expected.as_slice());
for forbidden in ["tokio-postgres", "deadpool-postgres", "rustls", "sqlx", "ksp-config-lib", "ksp-onchain-transport-lib", "ksp-offchain-transport-lib"] {
assert!(!manifest.contains(forbidden), "forbidden Store facade dependency detected: {forbidden}");
}
return;
}
#[test]
fn pre_009_secret_canary_never_crosses_settings_or_pre_io_error_debug() {
let malformed = std::format!("not-a-postgresql-uri-{SECRET_CANARY}");
let settings = hostile_settings(malformed.as_str());
let rendered = std::format!("{settings:?}");
assert!(!rendered.contains(SECRET_CANARY));
assert!(rendered.contains("<redacted>"));
let result = poll_ready(ksp_store_lib::Store::open(settings));
let error = match result {
std::result::Result::Err(value) => value,
std::result::Result::Ok(_) => panic!("hostile Store settings unexpectedly opened"),
};
#[cfg(feature = "postgres")]
assert_eq!(error.code(), ksp_store_lib::ERROR_CODE_POSTGRES_CONFIG_INVALID);
#[cfg(not(feature = "postgres"))]
assert_eq!(error.code(), ksp_store_lib::ERROR_CODE_BACKEND_NOT_COMPILED);
assert!(!std::format!("{error}").contains(SECRET_CANARY));
assert!(!std::format!("{error:?}").contains(SECRET_CANARY));
return;
}
#[test]
fn pre_009_facade_production_sources_keep_config_env_physical_sql_and_backend_handles_out() {
let production = std::format!(
"{}
{}
{}
{}",
include_str!("../src/error.rs"),
include_str!("../src/health.rs"),
include_str!("../src/settings.rs"),
include_str!("../src/store.rs")
);
for forbidden in [
"ksp_config_lib",
"std::env",
"dotenv",
"PGHOST",
"PGPORT",
"PGUSER",
"PGPASSWORD",
".pgpass",
"tokio_postgres::Client",
"tokio_postgres::Row",
"tokio_postgres::Statement",
"deadpool_postgres::Pool",
"sqlx::",
"CREATE TABLE",
"INSERT INTO",
"UPDATE ",
"DELETE FROM",
] {
assert!(!production.contains(forbidden), "forbidden facade ownership/runtime material detected: {forbidden}");
}
return;
}

View File

@@ -0,0 +1,220 @@
// file: crates/ksp-store-postgres-lib/tests/hardening_completeness.rs
// version: 1
#![warn(missing_docs)]
#![deny(unreachable_pub)]
#![forbid(unsafe_code)]
//! Hardening, exact-surface and scope canaries for the physical PostgreSQL Store backend.
const SECRET_CANARY: &str = "KSP-POSTGRES-SECRET-CANARY-PRE009";
fn poll_ready<T>(future: impl std::future::Future<Output = T>) -> T {
let mut future = std::boxed::Box::pin(future);
let waker = std::task::Waker::noop();
let mut context = std::task::Context::from_waker(waker);
return match std::future::Future::poll(future.as_mut(), &mut context) {
std::task::Poll::Ready(value) => value,
std::task::Poll::Pending => panic!("PostgreSQL hardening pre-I/O rejection unexpectedly became pending"),
};
}
fn network() -> ksp_store_api::RawNetworkId {
return match ksp_store_api::RawNetworkId::new("devnet") {
std::result::Result::Ok(value) => value,
std::result::Result::Err(error) => panic!("valid backend hardening network rejected: {error:?}"),
};
}
fn settings(connection_uri: &str, tls_mode: ksp_store_postgres_lib::PostgresBackendTlsMode) -> ksp_store_postgres_lib::PostgresBackendSettings {
return ksp_store_postgres_lib::PostgresBackendSettings::new(
network(),
connection_uri,
8,
std::time::Duration::from_secs(10),
std::time::Duration::from_secs(5),
std::time::Duration::from_secs(10),
std::time::Duration::from_secs(5),
tls_mode,
true,
std::time::Duration::from_secs(30),
std::time::Duration::from_secs(10),
);
}
fn public_reexport_names(source: &str) -> std::vec::Vec<&str> {
let mut names = std::vec::Vec::new();
for line in source.lines() {
let trimmed = line.trim();
if !trimmed.starts_with("pub use ") || !trimmed.ends_with(';') {
continue;
}
let without_semicolon = trimmed.trim_end_matches(';');
let name = match without_semicolon.rsplit("::").next() {
std::option::Option::Some(value) => value,
std::option::Option::None => continue,
};
names.push(name);
}
names.sort_unstable();
return names;
}
fn manifest_dependency_names(source: &str) -> std::vec::Vec<&str> {
let dependencies_tail = match source.split("[dependencies]").nth(1) {
std::option::Option::Some(value) => value,
std::option::Option::None => return std::vec::Vec::new(),
};
let dependencies = match dependencies_tail.split("[lints]").next() {
std::option::Option::Some(value) => value,
std::option::Option::None => return std::vec::Vec::new(),
};
let mut names = std::vec::Vec::new();
for line in dependencies.lines() {
let content = match line.split('#').next() {
std::option::Option::Some(value) => value.trim(),
std::option::Option::None => continue,
};
if content.is_empty() {
continue;
}
let key = match content.split('=').next() {
std::option::Option::Some(value) => value.trim().trim_end_matches(".workspace"),
std::option::Option::None => continue,
};
if !key.is_empty() {
names.push(key);
}
}
names.sort_unstable();
return names;
}
fn assert_pre_io_rejection(connection_uri: &str, tls_mode: ksp_store_postgres_lib::PostgresBackendTlsMode, expected_phase: &str) {
let settings = settings(connection_uri, tls_mode);
let rendered_settings = std::format!("{settings:?}");
assert!(!rendered_settings.contains(SECRET_CANARY));
assert!(rendered_settings.contains("<redacted>"));
let result = poll_ready(ksp_store_postgres_lib::PostgresBackend::open(settings));
let error = match result {
std::result::Result::Err(value) => value,
std::result::Result::Ok(_) => panic!("hostile PostgreSQL settings unexpectedly opened"),
};
assert_eq!(error.kind(), ksp_store_postgres_lib::PostgresBackendErrorKind::ConfigInvalid);
assert_eq!(error.phase(), expected_phase);
assert!(!std::format!("{error:?}").contains(SECRET_CANARY));
return;
}
#[test]
fn pre_009_backend_modules_exports_and_manifest_dependencies_are_exact() {
let crate_root = include_str!("../src/lib.rs");
for required in ["mod constants;", "mod error;", "mod health;", "mod migration;", "mod runtime;"] {
assert!(crate_root.contains(required), "missing PostgreSQL backend module: {required}");
}
assert!(!crate_root.contains("pub mod "));
let actual_exports = public_reexport_names(crate_root);
let mut expected_exports = [
"PostgresBackend",
"PostgresBackendError",
"PostgresBackendErrorKind",
"PostgresBackendHealthSnapshot",
"PostgresBackendRuntimeSnapshot",
"PostgresBackendSettings",
"PostgresBackendTlsMode",
];
expected_exports.sort_unstable();
assert_eq!(actual_exports.as_slice(), expected_exports.as_slice());
assert_eq!(actual_exports.len(), 7);
let manifest = include_str!("../Cargo.toml");
let actual_dependencies = manifest_dependency_names(manifest);
let expected_dependencies = [
"deadpool-postgres",
"ksp-logging-lib",
"ksp-store-api",
"rustls",
"rustls-native-certs",
"sha2",
"tokio",
"tokio-postgres",
"tokio-postgres-rustls",
];
assert_eq!(actual_dependencies.as_slice(), expected_dependencies.as_slice());
return;
}
#[test]
fn pre_009_hostile_uri_matrix_is_rejected_before_io_without_secret_echo() {
let malformed = std::format!("not-a-postgresql-uri-{SECRET_CANARY}");
assert_pre_io_rejection(malformed.as_str(), ksp_store_postgres_lib::PostgresBackendTlsMode::Disabled, "connection_uri");
let oversized = std::format!("{}{SECRET_CANARY}", "x".repeat(4_097));
assert_pre_io_rejection(oversized.as_str(), ksp_store_postgres_lib::PostgresBackendTlsMode::Disabled, "connection_uri");
let hostaddr_only = std::format!("hostaddr=127.0.0.1 user=operator password={SECRET_CANARY} dbname=ksp");
assert_pre_io_rejection(hostaddr_only.as_str(), ksp_store_postgres_lib::PostgresBackendTlsMode::VerifyFull, "tls_server_identity");
let server_options = std::format!("host=localhost user=operator password={SECRET_CANARY} dbname=ksp options='-c application_name={SECRET_CANARY}'");
assert_pre_io_rejection(server_options.as_str(), ksp_store_postgres_lib::PostgresBackendTlsMode::Disabled, "server_options");
return;
}
#[test]
fn pre_009_backend_error_bridge_cannot_retain_external_error_or_secret_text() {
let error_source = include_str!("../src/error.rs");
assert!(error_source.contains("kind: PostgresBackendErrorKind"));
assert!(error_source.contains("phase: &'static str"));
for forbidden in ["String", "source:", "message:", "tokio_postgres::Error", "deadpool_postgres::PoolError"] {
assert!(!error_source.contains(forbidden), "backend error type can retain forbidden external material: {forbidden}");
}
let runtime = include_str!("../src/runtime.rs");
assert!(runtime.contains("deadpool_postgres::PoolError::Backend(_)"));
assert!(!runtime.contains("deadpool_postgres::PoolError::Backend(error)"));
for source in [runtime, include_str!("../src/migration.rs"), include_str!("../src/health.rs")] {
for forbidden in ["format!(\"{error", "format!(\"{error:?", "error = ?", "error = %"] {
assert!(!source.contains(forbidden), "backend source renders external error material: {forbidden}");
}
}
return;
}
#[test]
fn pre_009_backend_has_no_env_bypass_or_business_persistence_capability() {
let production = std::format!(
"{}
{}
{}
{}
{}",
include_str!("../src/error.rs"),
include_str!("../src/health.rs"),
include_str!("../src/lib.rs"),
include_str!("../src/migration.rs"),
include_str!("../src/runtime.rs")
);
for forbidden in [
"std::env",
"dotenv",
"KSP_SECRET_",
"KSPB_",
"PGHOST",
"PGPORT",
"PGUSER",
"PGPASSWORD",
".pgpass",
".postgresql/",
"sslrootcert",
"sslcert",
"sslkey",
"ksp_store_lib",
"ksp_config_lib",
"sqlx::",
"impl ksp_store_api::RawTransaction",
"impl ksp_store_api::RawAccount",
] {
assert!(!production.contains(forbidden), "forbidden backend ownership/capability material detected: {forbidden}");
}
let bootstrap_sql = include_str!("../migrations/V000__bootstrap.sql");
assert!(bootstrap_sql.contains("ksp_store_schema_migrations"));
for forbidden in ["RawTransaction", "RawAccountState", "raw_transaction", "raw_account", "CORE", "DECODE", "SPECIALIZED"] {
assert!(!bootstrap_sql.contains(forbidden), "business schema leaked into foundation migration: {forbidden}");
}
return;
}

157
deltas/0.3.2/pre.009.md Normal file
View File

@@ -0,0 +1,157 @@
<!-- file: deltas/0.3.2/pre.009.md -->
<!-- version: 1 -->
# Delta `0.3.2-pre.009` — hardening, completeness et dependency matrix
## Base exacte
Cette tranche s'applique sur `0.3.2-pre.008`.
Le 29 août 2026, le gate `pre.008` est entièrement vert : audits Rust/Markdown, workspace check/Clippy, tests `ksp-store-postgres-lib`, façade avec et sans feature PostgreSQL et compilation `--no-default-features` passent. Le gate PostgreSQL réel opt-in passe également sur **PostgreSQL 17** avec bootstrap initial/idempotent/concurrent, mismatch/recovery checksum, rollback transactionnel, health `Ready`, fermeture bornée et cleanup contrôlé.
## Objet
Durcir et figer la fondation Store/PostgreSQL avant le gate technique final sans modifier le code de production, les migrations ou les dépendances runtime.
## Canari façade
Nouveau fichier :
```text
crates/ksp-store-lib/tests/hardening_completeness.rs
```
Il vérifie :
```text
5 modules privés exacts
84 exports crate-root exacts
manifest runtime exact
feature postgres par défaut exacte
backend PostgreSQL toujours optionnel
aucune dépendance physique PostgreSQL dans la façade
canary secret redacted dans StoreSettings/Debug
URI malformed rejetée avant I/O
code ConfigInvalid avec postgres / BackendNotCompiled sans feature
aucun canary dans Display/Debug Error
aucune lecture Config/env/PG*/.pgpass
aucun Pool/Client/Row/Statement/SQL physique dans la façade
```
Le même test est conçu pour passer sous le feature set par défaut et sous `--no-default-features`.
## Canari backend PostgreSQL
Nouveau fichier :
```text
crates/ksp-store-postgres-lib/tests/hardening_completeness.rs
```
Il vérifie :
```text
5 modules privés exacts
7 exports bridge exacts
9 dépendances physiques exactes du manifest
URI malformed rejetée avant I/O
URI > 4096 octets rejetée avant I/O
hostaddr sans host rejeté sous VerifyFull
options= libpq rejeté
canary password/URI absent de Debug/error backend
PostgresBackendError limité structurellement à kind + phase statique
PoolError::Backend(_) abandonne explicitement l'erreur remote
aucun rendu d'erreur externe dans runtime/migration/health
aucune lecture env/KSP/KSPB/PG*/.pgpass
aucun sslrootcert/sslcert/sslkey/.postgresql implicite
aucune capability PostgreSQL RawTransaction/RawAccount
V000 reste strictement metadata-only
```
Les cas hostiles sont tous forcés avant le premier `.await` physique et n'exigent donc aucun PostgreSQL réel.
## Non-régression Store API
`ksp-store-api` n'est pas modifié. Le gate `pre.009` rejoue explicitement ses tests, notamment `tests/external_backend.rs`, afin de confirmer qu'un backend externe peut toujours implémenter les dix capabilities RAW sans dépendre de `ksp-store-lib` ou de PostgreSQL.
## Documentation de suivi
Le plan et la matrice validation sont réconciliés avec :
```text
gates pre.006/pre.007 verts
pre.008 live vert sur PostgreSQL 17
scope exact des canaris pre.009
statuts hardening matérialisés mais non encore exécutés
commande de dependency matrix ciblée
```
## Fichiers ajoutés
```text
crates/ksp-store-lib/tests/hardening_completeness.rs
crates/ksp-store-postgres-lib/tests/hardening_completeness.rs
deltas/0.3.2/pre.009.md
```
## Fichiers modifiés
```text
Cargo.toml
docs/plans/023-V0_3_2_STORE_POSTGRES_FOUNDATION_PLAN.md
docs/validation/019-V0_3_2_STORE_POSTGRES_FOUNDATION.md
```
## Fichiers supprimés
Aucun.
## Version
```text
workspace.package.version = 0.3.2-pre.9
```
Aucun `src/**`, migration, Config, README/USAGE, CHANGELOG ou ROADMAP n'est modifié dans cette tranche.
## Validations exécutées dans l'environnement de génération
```text
python3 scripts/audit_rust_workspace_rules.py
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas/0.3.2
contrôle statique de la version et du payload
contrôle statique des exports/manifests attendus
contrôle des chaînes interdites no-env/no-business
```
Cargo/rustc/rustfmt ne sont pas disponibles dans l'environnement de génération.
## Gate opérateur
```bash
cargo fmt --all
python3 scripts/audit_rust_workspace_rules.py
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas/0.3.2
cargo check --workspace
cargo clippy --workspace --all-targets
cargo test -p ksp-store-api
cargo test -p ksp-store-postgres-lib
cargo test -p ksp-store-lib
cargo test -p ksp-store-lib --no-default-features
cargo test -p ksp-config-lib
cargo check -p ksp-store-lib --no-default-features
cargo tree -p ksp-store-lib --edges normal
cargo tree -p ksp-store-lib -e features
cargo tree -p ksp-store-postgres-lib --edges normal
cargo tree --duplicates
```
Le smoke PostgreSQL réel n'a pas besoin d'être rejoué dans `pre.009` si aucun code runtime/migration n'a changé ; il sera rejoué au gate technique final `pre.010`.
## Questions ouvertes
Aucune question fonctionnelle nouvelle. Tout échec des nouveaux canaris doit produire un correctif `pre.009-fix.NNN` avant `pre.010`.
## Suite
Après gate vert : `0.3.2-pre.010` — gate technique final sans nouveau développement fonctionnel.

View File

@@ -1,5 +1,5 @@
<!-- file: docs/plans/023-V0_3_2_STORE_POSTGRES_FOUNDATION_PLAN.md --> <!-- file: docs/plans/023-V0_3_2_STORE_POSTGRES_FOUNDATION_PLAN.md -->
<!-- version: 10 --> <!-- version: 11 -->
# Plan `0.3.2` — Store/PostgreSQL runtime foundation # Plan `0.3.2` — Store/PostgreSQL runtime foundation
@@ -1063,7 +1063,57 @@ Aucun hook test-only n'est ajouté au runtime de production et aucune table RAW
### `pre.009` — Hardening/completeness/dependency matrix ### `pre.009` — Hardening/completeness/dependency matrix
Inputs hostiles, redaction, no-env, exact exports/modules, external backend compatibility, `--no-default-features`, graphes/features/duplicates, non-régression API. Base requise : `0.3.2-pre.008` avec gate déterministe vert et preuve PostgreSQL réelle verte. Le 29 août 2026, le smoke opt-in a validé la fondation sur PostgreSQL 17 : bootstrap initial, réouverture idempotente, concurrence sous advisory lock, corruption/restauration checksum, rollback transactionnel, health `Ready` et fermeture bornée.
Cette tranche reste un hardening de preuve : aucun `src/**`, aucune migration SQL et aucune dépendance runtime ne doivent être modifiés sauf découverte d'un défaut réel par les canaris.
Canaris ajoutés :
```text
ksp-store-lib
modules privés exacts
84 exports crate-root exacts
manifest exact : logging + Store API + backend optionnel
feature postgres default exacte
URI hostile/secret canary redacted dans Settings et Error
même preuve avec et sans default features
aucune lecture Config/env/PG*/.pgpass
aucun type PostgreSQL physique ou SQL dans la façade
ksp-store-postgres-lib
modules privés exacts
7 exports bridge exacts
manifest physique exact
URI malformed/surdimensionnée/hostaddr-only/options rejetées avant I/O
canary secret absent des Debug/errors publics
PostgresBackendError ne peut retenir que kind + phase statique
PoolError::Backend perd explicitement le texte remote
aucune lecture env/PG*/.pgpass ni fichiers TLS libpq implicites
aucune capability RawTransaction/RawAccount implémentée
V000 reste metadata-only
```
La compatibilité backend externe reste prouvée par `ksp-store-api/tests/external_backend.rs`; `pre.009` la rejoue sans modifier `ksp-store-api`.
Gate ciblé attendu :
```bash
cargo fmt --all
python3 scripts/audit_rust_workspace_rules.py
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas/0.3.2
cargo check --workspace
cargo clippy --workspace --all-targets
cargo test -p ksp-store-api
cargo test -p ksp-store-postgres-lib
cargo test -p ksp-store-lib
cargo test -p ksp-store-lib --no-default-features
cargo test -p ksp-config-lib
cargo check -p ksp-store-lib --no-default-features
cargo tree -p ksp-store-lib --edges normal
cargo tree -p ksp-store-lib -e features
cargo tree -p ksp-store-postgres-lib --edges normal
cargo tree --duplicates
```
### `pre.010` — Gate technique final ### `pre.010` — Gate technique final

View File

@@ -1,5 +1,5 @@
<!-- file: docs/validation/019-V0_3_2_STORE_POSTGRES_FOUNDATION.md --> <!-- file: docs/validation/019-V0_3_2_STORE_POSTGRES_FOUNDATION.md -->
<!-- version: 10 --> <!-- version: 11 -->
# Validation `0.3.2` — Store/PostgreSQL runtime foundation # Validation `0.3.2` — Store/PostgreSQL runtime foundation
@@ -78,6 +78,12 @@ Le gate opérateur de `pre.004-fix.001`, après correction manuelle des commenta
Le gate opérateur de `pre.005-fix.001`, fourni le 29 août 2026, est entièrement vert : audits Rust/Markdown, workspace check/Clippy, 5 tests runtime backend, canaris de dépendances/API, façade avec et sans feature PostgreSQL et compilation `--no-default-features` passent. Cette base est l'entrée effective de `pre.006`. Le gate opérateur de `pre.005-fix.001`, fourni le 29 août 2026, est entièrement vert : audits Rust/Markdown, workspace check/Clippy, 5 tests runtime backend, canaris de dépendances/API, façade avec et sans feature PostgreSQL et compilation `--no-default-features` passent. Cette base est l'entrée effective de `pre.006`.
Le gate opérateur de `pre.006`, fourni le 29 août 2026, est entièrement vert : audits Rust/Markdown, workspace check/Clippy, 9 tests backend incluant la migration V000, façade avec et sans feature PostgreSQL, compilation `--no-default-features` et graphes Cargo passent.
Le gate opérateur de `pre.007`, fourni le 29 août 2026, est entièrement vert : audits Rust/Markdown, workspace check/Clippy, 12 tests backend incluant health/readiness, façade avec et sans feature PostgreSQL, compilation `--no-default-features` et graphes Cargo passent.
Le gate `pre.008` du 29 août 2026 est doublement vert : le gate déterministe passe intégralement puis `postgres_foundation_live` passe sur **PostgreSQL 17**. La preuve réelle couvre bootstrap initial/idempotent/concurrent, mismatch/recovery checksum, rollback transactionnel, health `Ready`, fermeture bornée et cleanup de la metadata possédée par le test. Cette base est l'entrée effective de `pre.009`.
## 3. Frontières Cargo ## 3. Frontières Cargo
### V32-DEP-001 — Façade -> API ### V32-DEP-001 — Façade -> API
@@ -110,7 +116,7 @@ Preuves : manifest scanner + cargo tree.
Le canari source de `pre.002` interdit explicitement la dépendance inverse ; le `cargo tree` opérateur confirme l'absence de cycle. La frontière sera durcie à nouveau en `pre.009`. Le canari source de `pre.002` interdit explicitement la dépendance inverse ; le `cargo tree` opérateur confirme l'absence de cycle. La frontière sera durcie à nouveau en `pre.009`.
Statut : `PASS pre.002 opérateur / TODO pre.009 final`. Statut : `PASS pre.002 opérateur / MATÉRIALISÉ pre.009 / TODO gate opérateur`.
### V32-DEP-004 — Feature PostgreSQL ### V32-DEP-004 — Feature PostgreSQL
@@ -140,7 +146,7 @@ consumer ordinaire -> ksp-store-postgres-lib
Matérialisé par `pre.005` : les types physiques nécessaires sont publics uniquement dans la crate backend pour la frontière inter-crates et ne sont jamais réexportés par `ksp-store-lib`; `Pool/Client/Row/Statement` restent absents de sa crate-root. Matérialisé par `pre.005` : les types physiques nécessaires sont publics uniquement dans la crate backend pour la frontière inter-crates et ne sont jamais réexportés par `ksp-store-lib`; `Pool/Client/Row/Statement` restent absents de sa crate-root.
Statut : `PASS pre.005-fix.001 opérateur / TODO pre.009`. Statut : `PASS pre.005-fix.001 opérateur / MATÉRIALISÉ pre.009 / TODO gate opérateur`.
### V32-DEP-006 — `ksp-store-api` non régressé ### V32-DEP-006 — `ksp-store-api` non régressé
@@ -152,7 +158,7 @@ exports/capabilities existants conservés
aucun type backend ajouté pour PostgreSQL aucun type backend ajouté pour PostgreSQL
``` ```
Statut : `TODO gate final`, baseline `v0.3.1` déjà verte. Statut : `MATÉRIALISÉ pre.009 / TODO gate opérateur puis gate final` — le canari backend externe de `ksp-store-api` est rejoué sans modification de la crate.
## 4. Public API et settings ## 4. Public API et settings
@@ -170,7 +176,7 @@ Critère : `Postgres` reste un backend connu sans feature et `Store::open` écho
Matérialisé par `pre.003` : le test `feature_mismatch` appelle réellement `Store::open` sous `--no-default-features` et exige `store.backend_not_compiled`. Matérialisé par `pre.003` : le test `feature_mismatch` appelle réellement `Store::open` sous `--no-default-features` et exige `store.backend_not_compiled`.
Statut : `PASS pre.003-fix.001 opérateur / TODO pre.009 final`. Statut : `PASS pre.003-fix.001 opérateur / MATÉRIALISÉ pre.009 / TODO gate opérateur`.
### V32-API-003 — Aucun type backend physique public ### V32-API-003 — Aucun type backend physique public
@@ -183,7 +189,7 @@ rustls::*
PostgresBackend / Pool / Client / Row / Statement PostgresBackend / Pool / Client / Row / Statement
``` ```
Statut : `TODO pre.009`. Statut : `MATÉRIALISÉ pre.009 / TODO gate opérateur`.
### V32-API-004 — Réexports Store API ### V32-API-004 — Réexports Store API
@@ -191,7 +197,7 @@ Critère : un consumer de `ksp-store-lib` accède aux contrats Store API utiles
`pre.003` réexporte explicitement les 60 symboles crate-root acquis de `ksp-store-api` depuis `ksp-store-lib`, sans glob et sans réexport backend. `pre.003` réexporte explicitement les 60 symboles crate-root acquis de `ksp-store-api` depuis `ksp-store-lib`, sans glob et sans réexport backend.
Statut : `PASS pre.003-fix.001 opérateur / TODO pre.009 exact exports`. Statut : `PASS pre.003-fix.001 opérateur / MATÉRIALISÉ pre.009 exact exports / TODO gate opérateur`.
### V32-API-005 — Lifecycle ### V32-API-005 — Lifecycle
@@ -275,7 +281,7 @@ PG*
`pre.004` renforce aussi le canari d'ownership avec les nouveaux filenames Store ; `pre.004-fix.001` conserve cette frontière tout en ajoutant le réseau au contrat `StoreSettings`. Store/backend restent sans dépendance Config et sans lecture KSP/KSPB. `pre.004` renforce aussi le canari d'ownership avec les nouveaux filenames Store ; `pre.004-fix.001` conserve cette frontière tout en ajoutant le réseau au contrat `StoreSettings`. Store/backend restent sans dépendance Config et sans lecture KSP/KSPB.
Statut : `PASS pre.004-fix.001 opérateur / TODO pre.009`. Statut : `PASS pre.004-fix.001 opérateur / MATÉRIALISÉ pre.009 / TODO gate opérateur`.
### V32-CONFIG-004 — Adapter Config -> Store ### V32-CONFIG-004 — Adapter Config -> Store
@@ -308,7 +314,7 @@ Critère : chaque connection future tokio-postgres est pilotée par le manager r
`pre.005` délègue la création/recycle des connexions au `Manager` Deadpool retenu ; aucun `tokio::spawn` KSP n'est introduit dans Store. La preuve de non-régression détaillée reste au hardening. `pre.005` délègue la création/recycle des connexions au `Manager` Deadpool retenu ; aucun `tokio::spawn` KSP n'est introduit dans Store. La preuve de non-régression détaillée reste au hardening.
Statut : `PASS pre.005-fix.001 opérateur / TODO pre.009`. Statut : `PASS pre.005-fix.001 opérateur / MATÉRIALISÉ pre.009 / TODO gate opérateur`.
### V32-POOL-003 — Open failure safe ### V32-POOL-003 — Open failure safe
@@ -331,7 +337,7 @@ aucune tâche volontairement laissée orpheline
`pre.005` matérialise déjà `Pool::close()` et un drain borné par `shutdown_timeout`; `Drop` ne fait qu'un `close()` best-effort. La preuve end-to-end avec backend réel reste réservée à `pre.007/pre.008`. `pre.005` matérialise déjà `Pool::close()` et un drain borné par `shutdown_timeout`; `Drop` ne fait qu'un `close()` best-effort. La preuve end-to-end avec backend réel reste réservée à `pre.007/pre.008`.
Statut : `PASS pre.005-fix.001 opérateur / PASS pre.007 opérateur / TODO exécution live pre.008`. Statut : `PASS pre.005-fix.001 opérateur / PASS pre.007 opérateur / PASS live pre.008 PostgreSQL 17`.
## 7. TLS ## 7. TLS
@@ -368,7 +374,7 @@ Statut : `PASS pre.005-fix.001 opérateur`.
Critère : backend ne lit pas `sslrootcert`, `sslcert`, `sslkey`, `.postgresql/*` ou autre fichier implicite hors settings KSP. Critère : backend ne lit pas `sslrootcert`, `sslcert`, `sslkey`, `.postgresql/*` ou autre fichier implicite hors settings KSP.
Statut : `TODO pre.009`. Statut : `MATÉRIALISÉ pre.009 / TODO gate opérateur`.
## 8. Migration/bootstrap ## 8. Migration/bootstrap
@@ -384,7 +390,7 @@ et aucune table métier RAW/CORE/DECODE/SPECIALIZED.
`pre.006` embarque exactement `migrations/V000__bootstrap.sql`, dont le seul DDL de production crée `ksp_store_schema_migrations`. Le canari source interdit les identifiants métier RAW/CORE/DECODE/SPECIALIZED dans le moteur et la ressource SQL. `pre.006` embarque exactement `migrations/V000__bootstrap.sql`, dont le seul DDL de production crée `ksp_store_schema_migrations`. Le canari source interdit les identifiants métier RAW/CORE/DECODE/SPECIALIZED dans le moteur et la ressource SQL.
Statut : `PASS pre.006 opérateur / MATÉRIALISÉ pre.008 / TODO exécution live opérateur`. Statut : `PASS pre.006 opérateur / PASS live pre.008 PostgreSQL 17`.
### V32-MIG-002 — Version/checksum ### V32-MIG-002 — Version/checksum
@@ -400,7 +406,7 @@ mismatch historique terminal
`pre.006` calcule explicitement SHA-256 sur les octets exacts du SQL embarqué puis encode les 32 octets en 64 caractères hex minuscules. Le sentinel `(0, bootstrap, checksum)` est inséré dans la même transaction que la création metadata. Les tests unitaires figent le checksum du SQL committed et couvrent sentinel valide, missing, nom/checksum divergents et historique plus récent. `pre.006` calcule explicitement SHA-256 sur les octets exacts du SQL embarqué puis encode les 32 octets en 64 caractères hex minuscules. Le sentinel `(0, bootstrap, checksum)` est inséré dans la même transaction que la création metadata. Les tests unitaires figent le checksum du SQL committed et couvrent sentinel valide, missing, nom/checksum divergents et historique plus récent.
Statut : `PASS pre.006 opérateur / MATÉRIALISÉ pre.008 / TODO exécution live opérateur`. Statut : `PASS pre.006 opérateur / PASS live pre.008 PostgreSQL 17`.
### V32-MIG-003 — Concurrence ### V32-MIG-003 — Concurrence
@@ -408,7 +414,7 @@ Critère : deux runners concurrents sont sérialisés par advisory transaction l
`pre.006` utilise une clé KSP fixe et `pg_try_advisory_xact_lock($1)` dans une boucle bornée par `migration_lock_timeout`, avec polling de 25 ms maximum. Aucun lock bloquant illimité n'est utilisé. `pre.006` utilise une clé KSP fixe et `pg_try_advisory_xact_lock($1)` dans une boucle bornée par `migration_lock_timeout`, avec polling de 25 ms maximum. Aucun lock bloquant illimité n'est utilisé.
Statut : `PASS pre.006 opérateur / MATÉRIALISÉ pre.008 / TODO concurrence live opérateur`. Statut : `PASS pre.006 opérateur / PASS concurrence live pre.008 PostgreSQL 17`.
### V32-MIG-004 — Atomicité/recovery ### V32-MIG-004 — Atomicité/recovery
@@ -416,7 +422,7 @@ Critère : échec d'une migration du run courant rollback DDL + history de ce ru
`pre.006` place lock, metadata DDL, sentinel et validation dans une transaction unique ; toute sortie d'erreur avant `commit()` droppe la transaction et PostgreSQL rollback le run courant. Un timeout externe borne également l'ensemble du bootstrap. L'injection d'échec et la preuve physique du rollback restent au smoke réel. `pre.006` place lock, metadata DDL, sentinel et validation dans une transaction unique ; toute sortie d'erreur avant `commit()` droppe la transaction et PostgreSQL rollback le run courant. Un timeout externe borne également l'ensemble du bootstrap. L'injection d'échec et la preuve physique du rollback restent au smoke réel.
Statut : `PASS pre.006 opérateur / MATÉRIALISÉ pre.008 / TODO rollback live opérateur`. Statut : `PASS pre.006 opérateur / PASS rollback live pre.008 PostgreSQL 17`.
### V32-MIG-005 — Newer runtime guard ### V32-MIG-005 — Newer runtime guard
@@ -438,7 +444,7 @@ aucun identifier physique user-configurable en 0.3.2
`pre.006` garde le DDL versionné sous `include_str!` et toutes les values variables des requêtes de contrôle (`lock key`, `statement_timeout`, history values) passent par paramètres. Les noms physiques sont des constantes KSP, jamais des settings. `pre.006` garde le DDL versionné sous `include_str!` et toutes les values variables des requêtes de contrôle (`lock key`, `statement_timeout`, history values) passent par paramètres. Les noms physiques sont des constantes KSP, jamais des settings.
Statut : `PASS pre.006 opérateur / TODO pre.009 hardening`. Statut : `PASS pre.006 opérateur / MATÉRIALISÉ pre.009 hardening / TODO gate opérateur`.
### V32-MIG-007 — No business capability ### V32-MIG-007 — No business capability
@@ -450,7 +456,7 @@ Critères :
0 repository RAW métier 0 repository RAW métier
``` ```
Statut : `TODO pre.009/gate final`. Statut : `MATÉRIALISÉ pre.009 / TODO gate opérateur puis gate final`.
## 9. Health/readiness ## 9. Health/readiness
@@ -468,7 +474,7 @@ Critère : `Store::open` ne retourne Ready qu'après connect + bootstrap/verify
`pre.007` conserve l'ouverture stricte acquise en `pre.006` et ajoute un probe borné par le `wait_timeout` du pool : acquisition, `SELECT 1`, lecture de version migration. Le smoke réel reste nécessaire pour prouver ce chemin contre PostgreSQL. `pre.007` conserve l'ouverture stricte acquise en `pre.006` et ajoute un probe borné par le `wait_timeout` du pool : acquisition, `SELECT 1`, lecture de version migration. Le smoke réel reste nécessaire pour prouver ce chemin contre PostgreSQL.
Statut : `PASS pre.007 opérateur / TODO exécution live pre.008`. Statut : `PASS pre.007 opérateur / PASS live pre.008 PostgreSQL 17`.
### V32-HEALTH-003 — Error redaction ### V32-HEALTH-003 — Error redaction
@@ -476,7 +482,7 @@ Critère : un health failure n'expose pas server error string, query text ou cre
Le backend ne conserve que `PostgresBackendErrorKind`; la façade mappe vers un `ErrorCode` KSP optionnel. Le snapshot public ne contient aucune string serveur, URI, SQL, host, user, database ou handle. Le backend ne conserve que `PostgresBackendErrorKind`; la façade mappe vers un `ErrorCode` KSP optionnel. Le snapshot public ne contient aucune string serveur, URI, SQL, host, user, database ou handle.
Statut : `PASS pre.007 opérateur / TODO pre.009 hardening`. Statut : `PASS pre.007 opérateur / MATÉRIALISÉ pre.009 hardening / TODO gate opérateur`.
## 10. PostgreSQL integration réelle ## 10. PostgreSQL integration réelle
@@ -493,7 +499,7 @@ URI jamais imprimée par le test
`pre.008` ajoute `tests/postgres_foundation_live.rs`. Le test borne l'entrée à 4096 octets, ne conserve dans ses erreurs que des phases statiques et ne rend jamais l'erreur PostgreSQL brute. L'opérateur peut masquer la saisie côté shell puis piper une seule ligne sur stdin ; aucune variable d'environnement n'est lue par le test. `pre.008` ajoute `tests/postgres_foundation_live.rs`. Le test borne l'entrée à 4096 octets, ne conserve dans ses erreurs que des phases statiques et ne rend jamais l'erreur PostgreSQL brute. L'opérateur peut masquer la saisie côté shell puis piper une seule ligne sur stdin ; aucune variable d'environnement n'est lue par le test.
Statut : `MATÉRIALISÉ pre.008 / TODO exécution live opérateur`. Statut : `PASS live pre.008 PostgreSQL 17`.
### V32-LIVE-002 — Non destructif ### V32-LIVE-002 — Non destructif
@@ -508,7 +514,7 @@ cleanup seulement de metadata créée par le test
Le test interroge `information_schema.tables` avant toute mutation. Il ne prend ownership du cleanup qu'après avoir prouvé que le bootstrap initial a créé la metadata alors que la précondition était absente. Les seuls `DROP` exécutés ciblent `ksp_store_schema_migrations` pendant les resets contrôlés et le cleanup final. Le test interroge `information_schema.tables` avant toute mutation. Il ne prend ownership du cleanup qu'après avoir prouvé que le bootstrap initial a créé la metadata alors que la précondition était absente. Les seuls `DROP` exécutés ciblent `ksp_store_schema_migrations` pendant les resets contrôlés et le cleanup final.
Statut : `MATÉRIALISÉ pre.008 / TODO exécution live opérateur`. Statut : `PASS live pre.008 PostgreSQL 17`.
### V32-LIVE-003 — Scénario foundation ### V32-LIVE-003 — Scénario foundation
@@ -528,15 +534,15 @@ cleanup metadata
La preuve rollback évite tout hook public de test : une transaction `tokio-postgres` de test exécute le V000 exact par `include_str!`, insère un sentinel transitoire, provoque ensuite une erreur SQL puis est droppée. Le test vérifie que la metadata n'existe pas après le rollback implicite et que le bootstrap KSP normal peut repartir proprement. La preuve rollback évite tout hook public de test : une transaction `tokio-postgres` de test exécute le V000 exact par `include_str!`, insère un sentinel transitoire, provoque ensuite une erreur SQL puis est droppée. Le test vérifie que la metadata n'existe pas après le rollback implicite et que le bootstrap KSP normal peut repartir proprement.
Statut : `MATÉRIALISÉ pre.008 / TODO exécution live opérateur puis revalidation pre.010`. Statut : `PASS live pre.008 PostgreSQL 17 / TODO revalidation pre.010`.
### V32-LIVE-004 — PostgreSQL support ### V32-LIVE-004 — PostgreSQL support
Critère : test refuse major < 15 et enregistre seulement le major safe réellement testé. Critère : test refuse major < 15 et enregistre seulement le major safe réellement testé.
Cible release : PostgreSQL 18.6. `pre.008` interroge uniquement `SHOW server_version_num`, dérive le major et n'imprime aucune identité de serveur. Cible release : PostgreSQL 18.6. `pre.008` interroge uniquement `SHOW server_version_num`, dérive le major et n'imprime aucune identité de serveur. Le gate opérateur réel a été exécuté avec succès sur PostgreSQL 17.
Statut : `MATÉRIALISÉ pre.008 / TODO exécution live opérateur puis revalidation pre.010`. Statut : `PASS live pre.008 PostgreSQL 17 / TODO revalidation pre.010`.
## 11. Security/adversarial ## 11. Security/adversarial
@@ -546,7 +552,9 @@ Cas : vide, surdimensionnée, malformed, paramètres conflictuels, password cont
Attendu : rejet borné sans echo. Attendu : rejet borné sans echo.
Statut : `TODO pre.009`. `pre.009` ajoute un test d'intégration backend qui force avant I/O : URI malformed, URI > 4096 octets, `hostaddr` sans identité sous `VerifyFull` et `options=` libpq. Chaque cas contient un canary secret et exige une erreur `ConfigInvalid` avec phase statique seulement.
Statut : `MATÉRIALISÉ pre.009 / TODO gate opérateur`.
### V32-SEC-002 — Secret canary ### V32-SEC-002 — Secret canary
@@ -560,7 +568,9 @@ tracing snapshots
health snapshots health snapshots
``` ```
Statut : `TODO pre.009`. `pre.009` injecte des canaris distincts dans la façade et le backend. Les settings Debug doivent afficher `<redacted>`; les erreurs pré-I/O publiques/backend ne doivent contenir aucun canary. Les snapshots health restent couverts par les canaris déterministes de `pre.007`.
Statut : `MATÉRIALISÉ pre.009 / TODO gate opérateur`.
### V32-SEC-003 — Timeouts hostiles ### V32-SEC-003 — Timeouts hostiles
@@ -568,21 +578,46 @@ Cas : zéro, inversion, dépassement bornes pour pool/connect/migration/close.
Les bornes backend-neutral décidées en `pre.001` sont matérialisées et couvertes par tests unitaires en `pre.003`. `pre.005` applique physiquement `connect/wait/create/recycle/shutdown`; `pre.006` applique physiquement `migration_timeout` et `migration_lock_timeout`. Les bornes backend-neutral décidées en `pre.001` sont matérialisées et couvertes par tests unitaires en `pre.003`. `pre.005` applique physiquement `connect/wait/create/recycle/shutdown`; `pre.006` applique physiquement `migration_timeout` et `migration_lock_timeout`.
Statut : `PASS pre.003-fix.001 opérateur` pour les bornes backend-neutral / `PASS pre.005-fix.001 opérateur` pour pool/connect/shutdown / `PASS pre.006 opérateur` pour migration / `TODO pre.009` hardening. Statut : `PASS pre.003-fix.001 opérateur` pour les bornes backend-neutral / `PASS pre.005-fix.001 opérateur` pour pool/connect/shutdown / `PASS pre.006 opérateur` pour migration / `MATÉRIALISÉ pre.009` hardening / `TODO gate opérateur`.
### V32-SEC-004 — Feature mismatch avant I/O ### V32-SEC-004 — Feature mismatch avant I/O
`pre.003` matérialise un canari d'intégration compilé avec et sans `postgres`. Sans feature, `Store::open` retourne le code stable `store.backend_not_compiled` avant tout chemin backend physique. `pre.003` matérialise un canari d'intégration compilé avec et sans `postgres`. Sans feature, `Store::open` retourne le code stable `store.backend_not_compiled` avant tout chemin backend physique.
Statut : `PASS pre.003-fix.001 opérateur / TODO pre.009`. Statut : `PASS pre.003-fix.001 opérateur / MATÉRIALISÉ pre.009 / TODO gate opérateur`.
### V32-SEC-005 — Server error sanitization ### V32-SEC-005 — Server error sanitization
Un serveur/test double qui renvoie un message contenant un canary ne doit pas le faire traverser l'erreur publique. Un serveur/test double qui renvoie un message contenant un canary ne doit pas le faire traverser l'erreur publique.
Statut : bridge de sanitization matérialisé en `pre.005`; `TODO pre.009` pour le canary serveur hostile. Statut : bridge de sanitization matérialisé en `pre.005`; `pre.009` ajoute un canari structurel prouvant que `PostgresBackendError` ne peut retenir que `kind + phase` statiques et que `PoolError::Backend(_)` abandonne le texte remote. `TODO gate opérateur`.
## 12. Gates Rust/workspace ## 12. Hardening/completeness `pre.009`
Deux nouveaux canaris d'intégration figent sans modifier la production :
```text
crates/ksp-store-lib/tests/hardening_completeness.rs
crates/ksp-store-postgres-lib/tests/hardening_completeness.rs
```
Ils couvrent :
```text
84 exports façade exacts / 5 modules privés exacts
7 exports backend exacts / 5 modules privés exacts
manifests et feature postgres exacts
URI hostiles et canaris secrets avant I/O
sanitization structurelle des erreurs serveur/pool
no-env / no-.pgpass / no-libpq-TLS-files
aucune capability PostgreSQL RawTransaction/RawAccount
V000 metadata-only
compatibilité backend externe ksp-store-api rejouée au gate
```
Statut global `pre.009` : `MATÉRIALISÉ / TODO gate opérateur`.
## 13. Gates Rust/workspace
À chaque tranche applicable : À chaque tranche applicable :
@@ -629,7 +664,7 @@ Les builds Tauri ne sont requis que si `pre.004` modifie réellement les resourc
Statut global : `TODO` jusqu'aux preuves de chaque tranche. Statut global : `TODO` jusqu'aux preuves de chaque tranche.
## 13. Critères de fermeture ## 14. Critères de fermeture
La matrice ne peut passer en finale que si tous les critères applicables sont `PASS` et que : La matrice ne peut passer en finale que si tous les critères applicables sont `PASS` et que :