v0.3.3-pre.009-fix.004
This commit is contained in:
@@ -1,12 +1,12 @@
|
|||||||
# file: Cargo.toml
|
# file: Cargo.toml
|
||||||
# version: 357
|
# version: 358
|
||||||
|
|
||||||
[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.3-pre.9.fix.3"
|
version = "0.3.3-pre.9.fix.4"
|
||||||
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"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-store-postgres-lib/src/schema.rs
|
// file: crates/ksp-store-postgres-lib/src/schema.rs
|
||||||
// version: 4
|
// version: 5
|
||||||
|
|
||||||
/// Immutable V000 physical schema resource inventory.
|
/// Immutable V000 physical schema resource inventory.
|
||||||
pub(crate) const V000_RESOURCES: &[SchemaResource] = &[SchemaResource {
|
pub(crate) const V000_RESOURCES: &[SchemaResource] = &[SchemaResource {
|
||||||
@@ -1138,6 +1138,7 @@ fn matches_expected_constraint_definition(table: &str, kind: &str, definition: &
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn normalize_catalog_sql(value: &str) -> std::string::String {
|
fn normalize_catalog_sql(value: &str) -> std::string::String {
|
||||||
|
let value = normalize_quoted_numeric_cast_literals(value);
|
||||||
return value
|
return value
|
||||||
.chars()
|
.chars()
|
||||||
.filter(|character| return !character.is_whitespace() && *character != '"' && *character != '(' && *character != ')')
|
.filter(|character| return !character.is_whitespace() && *character != '"' && *character != '(' && *character != ')')
|
||||||
@@ -1149,6 +1150,47 @@ fn normalize_catalog_sql(value: &str) -> std::string::String {
|
|||||||
.to_ascii_lowercase();
|
.to_ascii_lowercase();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn normalize_quoted_numeric_cast_literals(value: &str) -> std::string::String {
|
||||||
|
const NUMERIC_CAST: &str = "::numeric";
|
||||||
|
let mut normalized = std::string::String::with_capacity(value.len());
|
||||||
|
let mut remaining = value;
|
||||||
|
loop {
|
||||||
|
let (before_quote, after_quote) = match remaining.split_once('\'') {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => {
|
||||||
|
normalized.push_str(remaining);
|
||||||
|
break;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
normalized.push_str(before_quote);
|
||||||
|
let (literal, after_literal) = match after_quote.split_once('\'') {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => {
|
||||||
|
normalized.push('\'');
|
||||||
|
normalized.push_str(after_quote);
|
||||||
|
break;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let has_digit = literal.chars().any(|character| return character.is_ascii_digit());
|
||||||
|
let numeric_literal = !literal.is_empty()
|
||||||
|
&& has_digit
|
||||||
|
&& literal
|
||||||
|
.chars()
|
||||||
|
.enumerate()
|
||||||
|
.all(|(offset, character)| return character.is_ascii_digit() || (offset == 0 && (character == '+' || character == '-')));
|
||||||
|
if numeric_literal && let std::option::Option::Some(after_numeric_cast) = after_literal.strip_prefix(NUMERIC_CAST) {
|
||||||
|
normalized.push_str(literal);
|
||||||
|
remaining = after_numeric_cast;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
normalized.push('\'');
|
||||||
|
normalized.push_str(literal);
|
||||||
|
normalized.push('\'');
|
||||||
|
remaining = after_literal;
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
fn schema_incompatible<T>(phase: &'static str) -> std::result::Result<T, crate::PostgresBackendError> {
|
fn schema_incompatible<T>(phase: &'static str) -> std::result::Result<T, crate::PostgresBackendError> {
|
||||||
return std::result::Result::Err(crate::PostgresBackendError::new(crate::PostgresBackendErrorKind::MigrationMismatch, phase));
|
return std::result::Result::Err(crate::PostgresBackendError::new(crate::PostgresBackendErrorKind::MigrationMismatch, phase));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-store-postgres-lib/unit_tests/schema.rs
|
// file: crates/ksp-store-postgres-lib/unit_tests/schema.rs
|
||||||
// version: 2
|
// version: 3
|
||||||
|
|
||||||
fn actual_column(name: &str, udt_name: &str, nullable: bool) -> super::ActualColumn {
|
fn actual_column(name: &str, udt_name: &str, nullable: bool) -> super::ActualColumn {
|
||||||
return super::ActualColumn {
|
return super::ActualColumn {
|
||||||
@@ -99,6 +99,10 @@ fn pre_003_fix_001_required_column_matching_ignores_integer_precision_but_requir
|
|||||||
fn pre_003_fix_001_catalog_normalization_and_resource_owned_constraint_definition_are_deterministic() {
|
fn pre_003_fix_001_catalog_normalization_and_resource_owned_constraint_definition_are_deterministic() {
|
||||||
let normalized = super::normalize_catalog_sql("CHECK ((slot >= (0)::numeric) AND (slot <= (18446744073709551615)::numeric))");
|
let normalized = super::normalize_catalog_sql("CHECK ((slot >= (0)::numeric) AND (slot <= (18446744073709551615)::numeric))");
|
||||||
assert_eq!(normalized, "checkslot>=0andslot<=18446744073709551615");
|
assert_eq!(normalized, "checkslot>=0andslot<=18446744073709551615");
|
||||||
|
let postgres_17_normalized = super::normalize_catalog_sql("CHECK ((slot >= '0'::numeric) AND (slot <= '18446744073709551615'::numeric))");
|
||||||
|
assert_eq!(postgres_17_normalized, normalized);
|
||||||
|
let text_literal = super::normalize_catalog_sql("CHECK (retention_state = 'full'::text)");
|
||||||
|
assert_eq!(text_literal, "checkretention_state='full'");
|
||||||
let resource = crate::V001_RESOURCES.iter().find(|resource| return resource.id == "constraints/006_ck_ksp_raw_transactions_slot.sql");
|
let resource = crate::V001_RESOURCES.iter().find(|resource| return resource.id == "constraints/006_ck_ksp_raw_transactions_slot.sql");
|
||||||
assert!(resource.is_some(), "V001 slot constraint resource must remain embedded");
|
assert!(resource.is_some(), "V001 slot constraint resource must remain embedded");
|
||||||
let resource = match resource {
|
let resource = match resource {
|
||||||
|
|||||||
117
deltas/0.3.3/pre.009-fix.004.md
Normal file
117
deltas/0.3.3/pre.009-fix.004.md
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
<!-- file: deltas/0.3.3/pre.009-fix.004.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.3.3-pre.009-fix.004` — normalisation du CHECK NUMERIC déparsée par PostgreSQL 17
|
||||||
|
|
||||||
|
## 1. Base et diagnostic réel
|
||||||
|
|
||||||
|
Base opérateur :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.3-pre.9.fix.3
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate standard fourni le 2026-08-30 est entièrement propre. Le live PostgreSQL réel atteint PostgreSQL 17 puis identifie précisément la ressource V001 rejetée après application :
|
||||||
|
|
||||||
|
```text
|
||||||
|
constraints/006_ck_ksp_raw_transactions_slot.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
La ressource crée :
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CHECK (slot >= 0 AND slot <= 18446744073709551615)
|
||||||
|
```
|
||||||
|
|
||||||
|
La colonne `slot` est `NUMERIC(20,0)`. PostgreSQL peut déparser un littéral `NUMERIC` hors plage bigint sous forme quotée avec cast explicite, notamment :
|
||||||
|
|
||||||
|
```text
|
||||||
|
'18446744073709551615'::numeric
|
||||||
|
```
|
||||||
|
|
||||||
|
L'ancien `normalize_catalog_sql` supprimait `::numeric` mais conservait les quotes simples. Le contrat embarqué devenait donc `18446744073709551615` tandis que le catalogue pouvait devenir `'18446744073709551615'`, ce qui produisait un faux `Incompatible` immédiatement après création.
|
||||||
|
|
||||||
|
## 2. Version
|
||||||
|
|
||||||
|
```text
|
||||||
|
workspace.package.version = 0.3.3-pre.9.fix.4
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Correction d'introspection
|
||||||
|
|
||||||
|
`schema.rs` ajoute une normalisation ciblée avant la normalisation générale :
|
||||||
|
|
||||||
|
- seules les chaînes composées d'un signe ASCII optionnel et de chiffres ASCII ;
|
||||||
|
- uniquement lorsqu'elles sont immédiatement suivies de `::numeric` ;
|
||||||
|
- voient leurs quotes simples et leur cast `::numeric` retirés dans cette étape ciblée ;
|
||||||
|
- les autres casts continuent d’être traités par la normalisation existante.
|
||||||
|
|
||||||
|
Cette règle ne retire donc pas les quotes des littéraux métier/textuels tels que :
|
||||||
|
|
||||||
|
```text
|
||||||
|
'full'::text
|
||||||
|
'archived'::text
|
||||||
|
'purged'::text
|
||||||
|
```
|
||||||
|
|
||||||
|
Le canari unitaire existant du CHECK `slot` couvre désormais explicitement la forme PostgreSQL 17 quotée et vérifie également qu'un littéral texte conserve ses quotes.
|
||||||
|
|
||||||
|
## 4. Pourquoi la migration reste inchangée
|
||||||
|
|
||||||
|
Le SQL V001 est sémantiquement correct et PostgreSQL l'applique avant que KSP ne le rejette. Le défaut est donc dans l'équivalence du catalogue, pas dans la contrainte elle-même. Modifier la ressource SQL ferait inutilement varier le checksum V001 et réécrirait le contrat de migration pour un problème de déparsing.
|
||||||
|
|
||||||
|
`fix.004` ne modifie donc aucune ressource V000/V001.
|
||||||
|
|
||||||
|
## 5. Invariants
|
||||||
|
|
||||||
|
Aucun changement de :
|
||||||
|
|
||||||
|
- tables, contraintes ou indexes V001 ;
|
||||||
|
- ordre ou nombre des 40 ressources V001 ;
|
||||||
|
- Store API / façade ;
|
||||||
|
- RawTransaction read/write/pagination/rétention ;
|
||||||
|
- cursor ;
|
||||||
|
- Config ;
|
||||||
|
- scope `RawAccountState`.
|
||||||
|
|
||||||
|
Checksums attendus inchangés :
|
||||||
|
|
||||||
|
```text
|
||||||
|
V000 d29068b8c13b9dc0cc9ef6aaadd0fa12d41e0fe4c56541a1118c4bfc846a1450
|
||||||
|
V001 31488cda2f08f3f46c4cdbdbb6c18c243662fada02eac4487040c8735d72cc51
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
crates/ksp-store-postgres-lib/src/schema.rs
|
||||||
|
crates/ksp-store-postgres-lib/unit_tests/schema.rs
|
||||||
|
docs/validation/020-V0_3_3_STORE_POSTGRES_RAW_TRANSACTION.md
|
||||||
|
deltas/0.3.3/pre.009-fix.004.md
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucune suppression.
|
||||||
|
|
||||||
|
## 7. Gate opérateur 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.3
|
||||||
|
cargo check --workspace
|
||||||
|
cargo clippy --workspace --all-targets
|
||||||
|
cargo test -p ksp-store-api
|
||||||
|
cargo test -p ksp-store-lib
|
||||||
|
cargo test -p ksp-store-postgres-lib
|
||||||
|
cargo test -p ksp-config-lib
|
||||||
|
cargo check -p ksp-store-lib --no-default-features
|
||||||
|
```
|
||||||
|
|
||||||
|
Puis :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
read -rsp "Dedicated PostgreSQL URI: " KSP_PG_TEST_URI; echo
|
||||||
|
printf '%s\n' "$KSP_PG_TEST_URI" | cargo test -p ksp-store-postgres-lib --test postgres_raw_transaction_live -- --ignored --nocapture --test-threads=1
|
||||||
|
unset KSP_PG_TEST_URI
|
||||||
|
```
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: docs/validation/020-V0_3_3_STORE_POSTGRES_RAW_TRANSACTION.md -->
|
<!-- file: docs/validation/020-V0_3_3_STORE_POSTGRES_RAW_TRANSACTION.md -->
|
||||||
<!-- version: 17 -->
|
<!-- version: 18 -->
|
||||||
|
|
||||||
# Validation `0.3.3` — Store/PostgreSQL RawTransaction vertical slice
|
# Validation `0.3.3` — Store/PostgreSQL RawTransaction vertical slice
|
||||||
|
|
||||||
@@ -645,8 +645,11 @@ cap 500/1000 dans Store pagination
|
|||||||
- `pre.009-fix.002` corrige uniquement les cinq diagnostics Clippy du test et propage `PostgresBackendError::phase()` vers `LiveFailure` lors de `open_backend`, sans rendre le texte PostgreSQL, l'URI, SQLSTATE ou bind ;
|
- `pre.009-fix.002` corrige uniquement les cinq diagnostics Clippy du test et propage `PostgresBackendError::phase()` vers `LiveFailure` lors de `open_backend`, sans rendre le texte PostgreSQL, l'URI, SQLSTATE ou bind ;
|
||||||
- gate Cargo standard de `pre.009-fix.002` du 2026-08-30 : PASS complet, audits/check/Clippy/tests/no-default-features propres ;
|
- gate Cargo standard de `pre.009-fix.002` du 2026-08-30 : PASS complet, audits/check/Clippy/tests/no-default-features propres ;
|
||||||
- live PostgreSQL réel `pre.009-fix.002` : PostgreSQL 17 joint puis FAIL à `schema_resource_post_apply`, ce qui localise le défaut dans la relecture d'une ressource V001 juste après son application ;
|
- live PostgreSQL réel `pre.009-fix.002` : PostgreSQL 17 joint puis FAIL à `schema_resource_post_apply`, ce qui localise le défaut dans la relecture d'une ressource V001 juste après son application ;
|
||||||
- `pre.009-fix.003` conserve la redaction mais remplace, uniquement pour ce cas post-apply, la phase agrégée par `resource.id`, identifiant embarqué statique et non sensible ; le prochain live doit donc nommer exactement `tables/...`, `constraints/...` ou `indexes/...` ;
|
- `pre.009-fix.003` conserve la redaction mais remplace, uniquement pour ce cas post-apply, la phase agrégée par `resource.id`, identifiant embarqué statique et non sensible ;
|
||||||
- aucune logique SQL/migration n'est modifiée par `fix.003` : il s'agit d'une instrumentation diagnostique sûre avant correction de la ressource réellement fautive.
|
- gate Cargo standard de `pre.009-fix.003` du 2026-08-30 : PASS complet ;
|
||||||
|
- live PostgreSQL réel `pre.009-fix.003` : PostgreSQL 17 joint puis FAIL précisément sur `constraints/006_ck_ksp_raw_transactions_slot.sql` ;
|
||||||
|
- diagnostic : le CHECK est appliqué, mais la forme catalogue PostgreSQL peut rendre la borne `NUMERIC` hors bigint comme littéral quoté `'18446744073709551615'::numeric`, tandis que l'ancien normaliseur conservait les quotes ;
|
||||||
|
- `pre.009-fix.004` corrige uniquement cette équivalence catalogue ciblée et ajoute un canari qui préserve les quotes des littéraux texte ; aucune ressource SQL/migration n'est modifiée.
|
||||||
|
|
||||||
### `pre.010`
|
### `pre.010`
|
||||||
|
|
||||||
@@ -770,5 +773,13 @@ cargo check -p ksp-store-lib --no-default-features
|
|||||||
- [PASS statique] les implémentations directes `RawTransactionWrite` / `RawTransactionObservationWrite` restent interdites dans le backend à ce stade ;
|
- [PASS statique] les implémentations directes `RawTransactionWrite` / `RawTransactionObservationWrite` restent interdites dans le backend à ce stade ;
|
||||||
- [PASS statique] le canari `pre.007` continue d'exiger `DELETE_ARCHIVE_PAYLOAD_SQL` et `DELETE FROM ksp_raw_transaction_archive_payloads` ;
|
- [PASS statique] le canari `pre.007` continue d'exiger `DELETE_ARCHIVE_PAYLOAD_SQL` et `DELETE FROM ksp_raw_transaction_archive_payloads` ;
|
||||||
- [PASS statique] aucune logique runtime, migration ou checksum V000/V001 modifié ;
|
- [PASS statique] aucune logique runtime, migration ou checksum V000/V001 modifié ;
|
||||||
- [À FAIRE] gate Cargo opérateur complet de `pre.007-fix.001`.
|
- [PASS] gate Cargo opérateur complet de `pre.007-fix.001` fourni le 2026-08-30.
|
||||||
|
|
||||||
|
### `pre.009-fix.004` — normalisation PostgreSQL 17 du CHECK `slot`
|
||||||
|
|
||||||
|
- [PASS opérateur] gate standard `pre.009-fix.003` intégralement propre le 2026-08-30 ;
|
||||||
|
- [DIAGNOSTIC LIVE] PostgreSQL 17 identifie exactement `constraints/006_ck_ksp_raw_transactions_slot.sql` comme ressource incompatible immédiatement après application ;
|
||||||
|
- [FIX statique] l'introspection normalise désormais uniquement les littéraux numériques quotés suivis de `::numeric`, forme que le déparseur PostgreSQL peut produire pour `NUMERIC` hors plage bigint ;
|
||||||
|
- [PASS statique] le canari couvre `'18446744073709551615'::numeric` et vérifie que les littéraux texte comme `'full'::text` conservent leurs quotes ;
|
||||||
|
- [PASS statique] aucun fichier SQL V000/V001, ordre de ressource ou checksum de migration n'est modifié ;
|
||||||
|
- [À FAIRE] gate Cargo opérateur complet puis live PostgreSQL `postgres_raw_transaction_live`.
|
||||||
|
|||||||
Reference in New Issue
Block a user