v0.3.3-pre.003
This commit is contained in:
@@ -1,12 +1,12 @@
|
|||||||
# file: Cargo.toml
|
# file: Cargo.toml
|
||||||
# version: 349
|
# version: 350
|
||||||
|
|
||||||
[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.2"
|
version = "0.3.3-pre.3"
|
||||||
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-lib/src/error.rs
|
// file: crates/ksp-store-lib/src/error.rs
|
||||||
// version: 4
|
// version: 5
|
||||||
|
|
||||||
/// Error code reserved for operations attempted after a Store backend has entered its closed state.
|
/// Error code reserved for operations attempted after a Store backend has entered its closed state.
|
||||||
pub const ERROR_CODE_BACKEND_CLOSED: ksp_store_api::ErrorCode = ksp_store_api::ErrorCode::new("store", "backend_closed");
|
pub const ERROR_CODE_BACKEND_CLOSED: ksp_store_api::ErrorCode = ksp_store_api::ErrorCode::new("store", "backend_closed");
|
||||||
@@ -19,6 +19,9 @@ pub const ERROR_CODE_POSTGRES_MIGRATION_FAILED: ksp_store_api::ErrorCode = ksp_s
|
|||||||
pub const ERROR_CODE_POSTGRES_MIGRATION_MISMATCH: ksp_store_api::ErrorCode = ksp_store_api::ErrorCode::new("store", "postgres_migration_mismatch");
|
pub const ERROR_CODE_POSTGRES_MIGRATION_MISMATCH: ksp_store_api::ErrorCode = ksp_store_api::ErrorCode::new("store", "postgres_migration_mismatch");
|
||||||
/// Error code used when a bounded PostgreSQL pool wait, create or recycle operation reaches its deadline.
|
/// Error code used when a bounded PostgreSQL pool wait, create or recycle operation reaches its deadline.
|
||||||
pub const ERROR_CODE_POSTGRES_POOL_TIMEOUT: ksp_store_api::ErrorCode = ksp_store_api::ErrorCode::new("store", "postgres_pool_timeout");
|
pub const ERROR_CODE_POSTGRES_POOL_TIMEOUT: ksp_store_api::ErrorCode = ksp_store_api::ErrorCode::new("store", "postgres_pool_timeout");
|
||||||
|
/// Error code used when PostgreSQL cannot represent a requested RAW retention compaction state.
|
||||||
|
pub const ERROR_CODE_POSTGRES_RETENTION_COMPACTION_UNSUPPORTED: ksp_store_api::ErrorCode =
|
||||||
|
ksp_store_api::ErrorCode::new("store", "postgres_retention_compaction_unsupported");
|
||||||
/// Error code used when PostgreSQL history contains a migration newer than this Store runtime understands.
|
/// Error code used when PostgreSQL history contains a migration newer than this Store runtime understands.
|
||||||
pub const ERROR_CODE_POSTGRES_SCHEMA_NEWER: ksp_store_api::ErrorCode = ksp_store_api::ErrorCode::new("store", "postgres_schema_newer");
|
pub const ERROR_CODE_POSTGRES_SCHEMA_NEWER: ksp_store_api::ErrorCode = ksp_store_api::ErrorCode::new("store", "postgres_schema_newer");
|
||||||
/// Error code used when verified PostgreSQL TLS setup or negotiation cannot be completed safely.
|
/// Error code used when verified PostgreSQL TLS setup or negotiation cannot be completed safely.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-store-lib/src/lib.rs
|
// file: crates/ksp-store-lib/src/lib.rs
|
||||||
// version: 6
|
// version: 7
|
||||||
|
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
#![deny(unreachable_pub)]
|
#![deny(unreachable_pub)]
|
||||||
@@ -40,6 +40,8 @@ pub use self::error::ERROR_CODE_POSTGRES_MIGRATION_FAILED;
|
|||||||
pub use self::error::ERROR_CODE_POSTGRES_MIGRATION_MISMATCH;
|
pub use self::error::ERROR_CODE_POSTGRES_MIGRATION_MISMATCH;
|
||||||
/// Error code used when a bounded PostgreSQL pool operation reaches its deadline.
|
/// Error code used when a bounded PostgreSQL pool operation reaches its deadline.
|
||||||
pub use self::error::ERROR_CODE_POSTGRES_POOL_TIMEOUT;
|
pub use self::error::ERROR_CODE_POSTGRES_POOL_TIMEOUT;
|
||||||
|
/// Error code used when PostgreSQL cannot represent a requested RAW retention compaction state.
|
||||||
|
pub use self::error::ERROR_CODE_POSTGRES_RETENTION_COMPACTION_UNSUPPORTED;
|
||||||
/// Error code used when PostgreSQL schema history is newer than this Store runtime.
|
/// Error code used when PostgreSQL schema history is newer than this Store runtime.
|
||||||
pub use self::error::ERROR_CODE_POSTGRES_SCHEMA_NEWER;
|
pub use self::error::ERROR_CODE_POSTGRES_SCHEMA_NEWER;
|
||||||
/// Error code used when PostgreSQL verified TLS setup or negotiation fails.
|
/// Error code used when PostgreSQL verified TLS setup or negotiation fails.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-store-lib/tests/hardening_completeness.rs
|
// file: crates/ksp-store-lib/tests/hardening_completeness.rs
|
||||||
// version: 1
|
// version: 2
|
||||||
|
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
#![deny(unreachable_pub)]
|
#![deny(unreachable_pub)]
|
||||||
@@ -102,6 +102,7 @@ fn pre_009_facade_modules_and_crate_root_exports_are_exact() {
|
|||||||
"ERROR_CODE_POSTGRES_MIGRATION_FAILED",
|
"ERROR_CODE_POSTGRES_MIGRATION_FAILED",
|
||||||
"ERROR_CODE_POSTGRES_MIGRATION_MISMATCH",
|
"ERROR_CODE_POSTGRES_MIGRATION_MISMATCH",
|
||||||
"ERROR_CODE_POSTGRES_POOL_TIMEOUT",
|
"ERROR_CODE_POSTGRES_POOL_TIMEOUT",
|
||||||
|
"ERROR_CODE_POSTGRES_RETENTION_COMPACTION_UNSUPPORTED",
|
||||||
"ERROR_CODE_POSTGRES_SCHEMA_NEWER",
|
"ERROR_CODE_POSTGRES_SCHEMA_NEWER",
|
||||||
"ERROR_CODE_POSTGRES_TLS_FAILED",
|
"ERROR_CODE_POSTGRES_TLS_FAILED",
|
||||||
"ERROR_CODE_RAW_CONFLICT",
|
"ERROR_CODE_RAW_CONFLICT",
|
||||||
@@ -180,7 +181,7 @@ fn pre_009_facade_modules_and_crate_root_exports_are_exact() {
|
|||||||
];
|
];
|
||||||
expected.sort_unstable();
|
expected.sort_unstable();
|
||||||
assert_eq!(actual.as_slice(), expected.as_slice());
|
assert_eq!(actual.as_slice(), expected.as_slice());
|
||||||
assert_eq!(actual.len(), 84);
|
assert_eq!(actual.len(), 85);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-store-lib/tests/public_api.rs
|
// file: crates/ksp-store-lib/tests/public_api.rs
|
||||||
// version: 5
|
// version: 6
|
||||||
|
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
#![deny(unreachable_pub)]
|
#![deny(unreachable_pub)]
|
||||||
@@ -40,6 +40,7 @@ fn pre_005_common_and_postgres_error_codes_are_stable_and_store_owned() {
|
|||||||
assert_eq!(ksp_store_lib::ERROR_CODE_POSTGRES_CONNECT_FAILED.code(), "postgres_connect_failed");
|
assert_eq!(ksp_store_lib::ERROR_CODE_POSTGRES_CONNECT_FAILED.code(), "postgres_connect_failed");
|
||||||
assert_eq!(ksp_store_lib::ERROR_CODE_POSTGRES_HEALTH_FAILED.code(), "postgres_health_failed");
|
assert_eq!(ksp_store_lib::ERROR_CODE_POSTGRES_HEALTH_FAILED.code(), "postgres_health_failed");
|
||||||
assert_eq!(ksp_store_lib::ERROR_CODE_POSTGRES_POOL_TIMEOUT.code(), "postgres_pool_timeout");
|
assert_eq!(ksp_store_lib::ERROR_CODE_POSTGRES_POOL_TIMEOUT.code(), "postgres_pool_timeout");
|
||||||
|
assert_eq!(ksp_store_lib::ERROR_CODE_POSTGRES_RETENTION_COMPACTION_UNSUPPORTED.code(), "postgres_retention_compaction_unsupported");
|
||||||
assert_eq!(ksp_store_lib::ERROR_CODE_POSTGRES_MIGRATION_FAILED.code(), "postgres_migration_failed");
|
assert_eq!(ksp_store_lib::ERROR_CODE_POSTGRES_MIGRATION_FAILED.code(), "postgres_migration_failed");
|
||||||
assert_eq!(ksp_store_lib::ERROR_CODE_POSTGRES_MIGRATION_MISMATCH.code(), "postgres_migration_mismatch");
|
assert_eq!(ksp_store_lib::ERROR_CODE_POSTGRES_MIGRATION_MISMATCH.code(), "postgres_migration_mismatch");
|
||||||
assert_eq!(ksp_store_lib::ERROR_CODE_POSTGRES_SCHEMA_NEWER.code(), "postgres_schema_newer");
|
assert_eq!(ksp_store_lib::ERROR_CODE_POSTGRES_SCHEMA_NEWER.code(), "postgres_schema_newer");
|
||||||
|
|||||||
@@ -0,0 +1,133 @@
|
|||||||
|
CREATE TABLE ksp_store_identity (
|
||||||
|
singleton SMALLINT PRIMARY KEY,
|
||||||
|
network TEXT NOT NULL,
|
||||||
|
CONSTRAINT ck_ksp_store_identity_singleton CHECK (singleton = 1),
|
||||||
|
CONSTRAINT ck_ksp_store_identity_network CHECK (
|
||||||
|
octet_length(network) BETWEEN 1 AND 128
|
||||||
|
AND network ~ '^[A-Za-z0-9_.:-]+$'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE ksp_raw_transactions (
|
||||||
|
signature BYTEA PRIMARY KEY,
|
||||||
|
slot NUMERIC(20, 0) NOT NULL,
|
||||||
|
block_time_unix_millis BIGINT NULL,
|
||||||
|
format_id TEXT NOT NULL,
|
||||||
|
format_version BIGINT NOT NULL,
|
||||||
|
content_hash BYTEA NOT NULL,
|
||||||
|
payload BYTEA NULL,
|
||||||
|
retention_state TEXT NOT NULL,
|
||||||
|
CONSTRAINT ck_ksp_raw_transactions_signature CHECK (octet_length(signature) = 64),
|
||||||
|
CONSTRAINT ck_ksp_raw_transactions_slot CHECK (slot BETWEEN 0 AND 18446744073709551615),
|
||||||
|
CONSTRAINT ck_ksp_raw_transactions_block_time CHECK (
|
||||||
|
block_time_unix_millis IS NULL
|
||||||
|
OR block_time_unix_millis BETWEEN 0 AND 253402300799999
|
||||||
|
),
|
||||||
|
CONSTRAINT ck_ksp_raw_transactions_format_id CHECK (
|
||||||
|
octet_length(format_id) BETWEEN 1 AND 128
|
||||||
|
AND format_id ~ '^[A-Za-z0-9_.:-]+$'
|
||||||
|
),
|
||||||
|
CONSTRAINT ck_ksp_raw_transactions_format_version CHECK (format_version BETWEEN 1 AND 4294967295),
|
||||||
|
CONSTRAINT ck_ksp_raw_transactions_content_hash CHECK (octet_length(content_hash) = 32),
|
||||||
|
CONSTRAINT ck_ksp_raw_transactions_payload CHECK (
|
||||||
|
payload IS NULL
|
||||||
|
OR octet_length(payload) BETWEEN 1 AND 16777216
|
||||||
|
),
|
||||||
|
CONSTRAINT ck_ksp_raw_transactions_retention_state CHECK (retention_state IN ('full', 'archived', 'purged')),
|
||||||
|
CONSTRAINT ck_ksp_raw_transactions_payload_state CHECK (
|
||||||
|
(retention_state = 'full' AND payload IS NOT NULL)
|
||||||
|
OR (retention_state IN ('archived', 'purged') AND payload IS NULL)
|
||||||
|
),
|
||||||
|
CONSTRAINT ck_ksp_raw_transactions_purged_block_time CHECK (
|
||||||
|
retention_state <> 'purged'
|
||||||
|
OR block_time_unix_millis IS NULL
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE ksp_raw_transaction_observations (
|
||||||
|
observation_key BYTEA PRIMARY KEY,
|
||||||
|
transaction_signature BYTEA NOT NULL REFERENCES ksp_raw_transactions(signature) ON DELETE RESTRICT,
|
||||||
|
provider TEXT NOT NULL,
|
||||||
|
protocol TEXT NOT NULL,
|
||||||
|
acquisition_method TEXT NOT NULL,
|
||||||
|
origin TEXT NOT NULL,
|
||||||
|
received_at_unix_millis BIGINT NOT NULL,
|
||||||
|
capture_session_id TEXT NULL,
|
||||||
|
commitment TEXT NULL,
|
||||||
|
endpoint_id TEXT NULL,
|
||||||
|
filter_id TEXT NULL,
|
||||||
|
observed_at_unix_millis BIGINT NULL,
|
||||||
|
source_payload_hash BYTEA NULL,
|
||||||
|
source_payload_size_bytes BIGINT NULL,
|
||||||
|
CONSTRAINT ck_ksp_raw_transaction_observations_key CHECK (octet_length(observation_key) = 32),
|
||||||
|
CONSTRAINT ck_ksp_raw_transaction_observations_signature CHECK (octet_length(transaction_signature) = 64),
|
||||||
|
CONSTRAINT ck_ksp_raw_transaction_observations_provider CHECK (
|
||||||
|
octet_length(provider) BETWEEN 1 AND 128
|
||||||
|
AND provider ~ '^[A-Za-z0-9_.:-]+$'
|
||||||
|
),
|
||||||
|
CONSTRAINT ck_ksp_raw_transaction_observations_protocol CHECK (
|
||||||
|
octet_length(protocol) BETWEEN 1 AND 128
|
||||||
|
AND protocol ~ '^[A-Za-z0-9_.:-]+$'
|
||||||
|
),
|
||||||
|
CONSTRAINT ck_ksp_raw_transaction_observations_method CHECK (
|
||||||
|
octet_length(acquisition_method) BETWEEN 1 AND 128
|
||||||
|
AND acquisition_method ~ '^[A-Za-z0-9_.:-]+$'
|
||||||
|
),
|
||||||
|
CONSTRAINT ck_ksp_raw_transaction_observations_origin CHECK (origin IN ('backfill', 'import', 'live', 'repair', 'replay')),
|
||||||
|
CONSTRAINT ck_ksp_raw_transaction_observations_received_at CHECK (received_at_unix_millis BETWEEN 0 AND 253402300799999),
|
||||||
|
CONSTRAINT ck_ksp_raw_transaction_observations_capture_session CHECK (
|
||||||
|
capture_session_id IS NULL
|
||||||
|
OR (
|
||||||
|
octet_length(capture_session_id) BETWEEN 1 AND 128
|
||||||
|
AND capture_session_id ~ '^[A-Za-z0-9_.:-]+$'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
CONSTRAINT ck_ksp_raw_transaction_observations_commitment CHECK (
|
||||||
|
commitment IS NULL
|
||||||
|
OR (
|
||||||
|
octet_length(commitment) BETWEEN 1 AND 128
|
||||||
|
AND commitment ~ '^[A-Za-z0-9_.:-]+$'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
CONSTRAINT ck_ksp_raw_transaction_observations_endpoint CHECK (
|
||||||
|
endpoint_id IS NULL
|
||||||
|
OR (
|
||||||
|
octet_length(endpoint_id) BETWEEN 1 AND 128
|
||||||
|
AND endpoint_id ~ '^[A-Za-z0-9_.:-]+$'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
CONSTRAINT ck_ksp_raw_transaction_observations_filter CHECK (
|
||||||
|
filter_id IS NULL
|
||||||
|
OR (
|
||||||
|
octet_length(filter_id) BETWEEN 1 AND 128
|
||||||
|
AND filter_id ~ '^[A-Za-z0-9_.:-]+$'
|
||||||
|
)
|
||||||
|
),
|
||||||
|
CONSTRAINT ck_ksp_raw_transaction_observations_observed_at CHECK (
|
||||||
|
observed_at_unix_millis IS NULL
|
||||||
|
OR observed_at_unix_millis BETWEEN 0 AND 253402300799999
|
||||||
|
),
|
||||||
|
CONSTRAINT ck_ksp_raw_transaction_observations_time_order CHECK (
|
||||||
|
observed_at_unix_millis IS NULL
|
||||||
|
OR observed_at_unix_millis <= received_at_unix_millis
|
||||||
|
),
|
||||||
|
CONSTRAINT ck_ksp_raw_transaction_observations_source_hash CHECK (
|
||||||
|
source_payload_hash IS NULL
|
||||||
|
OR octet_length(source_payload_hash) = 32
|
||||||
|
),
|
||||||
|
CONSTRAINT ck_ksp_raw_transaction_observations_source_size CHECK (
|
||||||
|
source_payload_size_bytes IS NULL
|
||||||
|
OR source_payload_size_bytes BETWEEN 0 AND 67108864
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE ksp_raw_transaction_archive_payloads (
|
||||||
|
signature BYTEA PRIMARY KEY REFERENCES ksp_raw_transactions(signature) ON DELETE RESTRICT,
|
||||||
|
payload BYTEA NOT NULL,
|
||||||
|
CONSTRAINT ck_ksp_raw_transaction_archive_payloads_signature CHECK (octet_length(signature) = 64),
|
||||||
|
CONSTRAINT ck_ksp_raw_transaction_archive_payloads_payload CHECK (octet_length(payload) BETWEEN 1 AND 16777216)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX ix_ksp_raw_transactions_slot_signature
|
||||||
|
ON ksp_raw_transactions (slot, signature)
|
||||||
|
WHERE retention_state <> 'purged';
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
// file: crates/ksp-store-postgres-lib/src/error.rs
|
// file: crates/ksp-store-postgres-lib/src/error.rs
|
||||||
// version: 3
|
// version: 4
|
||||||
|
|
||||||
|
/// Stable KSP error code reserved for PostgreSQL retention transitions that require unsupported physical compaction.
|
||||||
|
pub const ERROR_CODE_POSTGRES_RETENTION_COMPACTION_UNSUPPORTED: ksp_store_api::ErrorCode =
|
||||||
|
ksp_store_api::ErrorCode::new("store", "postgres_retention_compaction_unsupported");
|
||||||
|
|
||||||
/// Safe backend-local classification used by the Store facade for stable error mapping.
|
/// Safe backend-local classification used by the Store facade for stable error mapping.
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-store-postgres-lib/src/lib.rs
|
// file: crates/ksp-store-postgres-lib/src/lib.rs
|
||||||
// version: 5
|
// version: 6
|
||||||
|
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
#![deny(unreachable_pub)]
|
#![deny(unreachable_pub)]
|
||||||
@@ -7,10 +7,11 @@
|
|||||||
|
|
||||||
//! Official PostgreSQL backend implementation for KSP Store.
|
//! Official PostgreSQL backend implementation for KSP Store.
|
||||||
//!
|
//!
|
||||||
//! `0.3.2-pre.007` owns the physical `tokio-postgres` connection, bounded
|
//! The backend owns the physical `tokio-postgres` connection, bounded Deadpool
|
||||||
//! Deadpool pool, explicit Rustls TLS policy, private KSP migration/bootstrap
|
//! pool, explicit Rustls TLS policy, private KSP migration/bootstrap engine and
|
||||||
//! engine and safe lightweight health/readiness probe. Business persistence
|
//! safe lightweight health/readiness probe. `0.3.3-pre.003` adds the immutable
|
||||||
//! remains absent from this foundation release.
|
//! V001 RawTransaction physical schema and mono-network database binding; the
|
||||||
|
//! business capability implementations remain deferred to later prereleases.
|
||||||
//!
|
//!
|
||||||
//! This crate depends on `ksp-store-api` and never on `ksp-store-lib`. The
|
//! This crate depends on `ksp-store-api` and never on `ksp-store-lib`. The
|
||||||
//! common facade consumes only this crate's narrow backend bridge and never
|
//! common facade consumes only this crate's narrow backend bridge and never
|
||||||
@@ -22,6 +23,8 @@ mod health;
|
|||||||
mod migration;
|
mod migration;
|
||||||
mod runtime;
|
mod runtime;
|
||||||
|
|
||||||
|
/// Stable KSP error code for unsupported PostgreSQL retention compaction.
|
||||||
|
pub use self::error::ERROR_CODE_POSTGRES_RETENTION_COMPACTION_UNSUPPORTED;
|
||||||
/// Safe backend-local error returned to the common Store facade.
|
/// Safe backend-local error returned to the common Store facade.
|
||||||
pub use self::error::PostgresBackendError;
|
pub use self::error::PostgresBackendError;
|
||||||
/// Safe backend-local error classification used by the common Store facade.
|
/// Safe backend-local error classification used by the common Store facade.
|
||||||
|
|||||||
@@ -1,18 +1,23 @@
|
|||||||
// file: crates/ksp-store-postgres-lib/src/migration.rs
|
// file: crates/ksp-store-postgres-lib/src/migration.rs
|
||||||
// version: 3
|
// version: 4
|
||||||
|
|
||||||
use sha2::Digest; // rust-rules: trait-import
|
use sha2::Digest; // rust-rules: trait-import
|
||||||
|
|
||||||
const ADVISORY_LOCK_KEY: i64 = 0x4b53_5053_544f_5245;
|
const ADVISORY_LOCK_KEY: i64 = 0x4b53_5053_544f_5245;
|
||||||
const EMBEDDED_MIGRATIONS: &[EmbeddedMigration] = &[EmbeddedMigration {
|
const EMBEDDED_MIGRATIONS: &[EmbeddedMigration] = &[
|
||||||
hook: MigrationHook::None,
|
EmbeddedMigration { hook: MigrationHook::None, name: "bootstrap", sql: include_str!("../migrations/V000__bootstrap.sql"), version: 0 },
|
||||||
name: "bootstrap",
|
EmbeddedMigration {
|
||||||
sql: include_str!("../migrations/V000__bootstrap.sql"),
|
hook: MigrationHook::StoreIdentity,
|
||||||
version: 0,
|
name: "raw_transaction",
|
||||||
}];
|
sql: include_str!("../migrations/V001__raw_transaction.sql"),
|
||||||
|
version: 1,
|
||||||
|
},
|
||||||
|
];
|
||||||
const HEX_LOWER: &[u8; 16] = b"0123456789abcdef";
|
const HEX_LOWER: &[u8; 16] = b"0123456789abcdef";
|
||||||
const HISTORY_INSERT_SQL: &str = "INSERT INTO ksp_store_schema_migrations (version, name, checksum, applied_at) VALUES ($1, $2, $3, CURRENT_TIMESTAMP)";
|
const HISTORY_INSERT_SQL: &str = "INSERT INTO ksp_store_schema_migrations (version, name, checksum, applied_at) VALUES ($1, $2, $3, CURRENT_TIMESTAMP)";
|
||||||
const HISTORY_LOAD_SQL: &str = "SELECT version, name, checksum FROM ksp_store_schema_migrations ORDER BY version";
|
const HISTORY_LOAD_SQL: &str = "SELECT version, name, checksum FROM ksp_store_schema_migrations ORDER BY version";
|
||||||
|
const IDENTITY_INSERT_SQL: &str = "INSERT INTO ksp_store_identity (singleton, network) VALUES (1, $1)";
|
||||||
|
const IDENTITY_LOAD_SQL: &str = "SELECT singleton, network FROM ksp_store_identity ORDER BY singleton LIMIT 2";
|
||||||
const LOCK_POLL_INTERVAL_MS: u64 = 25;
|
const LOCK_POLL_INTERVAL_MS: u64 = 25;
|
||||||
const METADATA_EXISTS_SQL: &str = r#"SELECT EXISTS (
|
const METADATA_EXISTS_SQL: &str = r#"SELECT EXISTS (
|
||||||
SELECT 1 FROM information_schema.tables
|
SELECT 1 FROM information_schema.tables
|
||||||
@@ -54,6 +59,7 @@ struct EmbeddedMigration {
|
|||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
enum MigrationHook {
|
enum MigrationHook {
|
||||||
None,
|
None,
|
||||||
|
StoreIdentity,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
@@ -80,7 +86,11 @@ pub(crate) async fn bootstrap(
|
|||||||
if let std::result::Result::Err(error) = registry_result {
|
if let std::result::Result::Err(error) = registry_result {
|
||||||
return std::result::Result::Err(error);
|
return std::result::Result::Err(error);
|
||||||
}
|
}
|
||||||
let bounded = tokio::time::timeout(migration_timeout, bootstrap_inner(client, network, auto_migrate, migration_timeout, migration_lock_timeout)).await;
|
let bounded = tokio::time::timeout(
|
||||||
|
migration_timeout,
|
||||||
|
bootstrap_inner(client, network, auto_migrate, migration_timeout, migration_lock_timeout),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
return match bounded {
|
return match bounded {
|
||||||
std::result::Result::Ok(result) => result,
|
std::result::Result::Ok(result) => result,
|
||||||
std::result::Result::Err(_) => {
|
std::result::Result::Err(_) => {
|
||||||
@@ -310,16 +320,86 @@ async fn run_applied_migration_hooks(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn run_migration_hook(
|
async fn run_migration_hook(
|
||||||
_transaction: &deadpool_postgres::Transaction<'_>,
|
transaction: &deadpool_postgres::Transaction<'_>,
|
||||||
_network: &ksp_store_api::RawNetworkId,
|
network: &ksp_store_api::RawNetworkId,
|
||||||
hook: MigrationHook,
|
hook: MigrationHook,
|
||||||
_context: MigrationHookContext,
|
context: MigrationHookContext,
|
||||||
) -> std::result::Result<(), crate::PostgresBackendError> {
|
) -> std::result::Result<(), crate::PostgresBackendError> {
|
||||||
return match hook {
|
return match hook {
|
||||||
MigrationHook::None => std::result::Result::Ok(()),
|
MigrationHook::None => std::result::Result::Ok(()),
|
||||||
|
MigrationHook::StoreIdentity => bind_store_identity(transaction, network, context).await,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn bind_store_identity(
|
||||||
|
transaction: &deadpool_postgres::Transaction<'_>,
|
||||||
|
network: &ksp_store_api::RawNetworkId,
|
||||||
|
context: MigrationHookContext,
|
||||||
|
) -> std::result::Result<(), crate::PostgresBackendError> {
|
||||||
|
if context == MigrationHookContext::AppliedNow {
|
||||||
|
let insert_result = transaction.execute(IDENTITY_INSERT_SQL, &[&network.as_str()]).await;
|
||||||
|
match insert_result {
|
||||||
|
std::result::Result::Ok(1) => {},
|
||||||
|
std::result::Result::Ok(_) | std::result::Result::Err(_) => {
|
||||||
|
return std::result::Result::Err(crate::PostgresBackendError::new(
|
||||||
|
crate::PostgresBackendErrorKind::MigrationFailed,
|
||||||
|
"store_identity_insert",
|
||||||
|
));
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let rows_result = transaction.query(IDENTITY_LOAD_SQL, &[]).await;
|
||||||
|
let rows = match rows_result {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => {
|
||||||
|
return std::result::Result::Err(crate::PostgresBackendError::new(
|
||||||
|
crate::PostgresBackendErrorKind::MigrationMismatch,
|
||||||
|
"store_identity_read",
|
||||||
|
));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if rows.len() != 1 {
|
||||||
|
return std::result::Result::Err(crate::PostgresBackendError::new(
|
||||||
|
crate::PostgresBackendErrorKind::MigrationMismatch,
|
||||||
|
"store_identity_count",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let row = &rows[0];
|
||||||
|
let singleton_result = row.try_get::<usize, i16>(0);
|
||||||
|
let network_result = row.try_get::<usize, std::string::String>(1);
|
||||||
|
let (singleton, stored_network) = match (singleton_result, network_result) {
|
||||||
|
(std::result::Result::Ok(singleton), std::result::Result::Ok(stored_network)) => (singleton, stored_network),
|
||||||
|
_ => {
|
||||||
|
return std::result::Result::Err(crate::PostgresBackendError::new(
|
||||||
|
crate::PostgresBackendErrorKind::MigrationMismatch,
|
||||||
|
"store_identity_decode",
|
||||||
|
));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if singleton != 1 {
|
||||||
|
return std::result::Result::Err(crate::PostgresBackendError::new(
|
||||||
|
crate::PostgresBackendErrorKind::MigrationMismatch,
|
||||||
|
"store_identity_singleton",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let stored_network = match ksp_store_api::RawNetworkId::new(stored_network) {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => {
|
||||||
|
return std::result::Result::Err(crate::PostgresBackendError::new(
|
||||||
|
crate::PostgresBackendErrorKind::MigrationMismatch,
|
||||||
|
"store_identity_network",
|
||||||
|
));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if stored_network.as_str() != network.as_str() {
|
||||||
|
return std::result::Result::Err(crate::PostgresBackendError::new(
|
||||||
|
crate::PostgresBackendErrorKind::MigrationMismatch,
|
||||||
|
"store_identity_network",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
async fn set_statement_timeout(
|
async fn set_statement_timeout(
|
||||||
transaction: &deadpool_postgres::Transaction<'_>,
|
transaction: &deadpool_postgres::Transaction<'_>,
|
||||||
timeout: std::time::Duration,
|
timeout: std::time::Duration,
|
||||||
@@ -384,8 +464,12 @@ async fn verify_metadata_shape(transaction: &deadpool_postgres::Transaction<'_>)
|
|||||||
return std::result::Result::Err(crate::PostgresBackendError::new(crate::PostgresBackendErrorKind::MigrationFailed, "metadata_shape"));
|
return std::result::Result::Err(crate::PostgresBackendError::new(crate::PostgresBackendErrorKind::MigrationFailed, "metadata_shape"));
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const REQUIRED: [(&str, &str, &str); 4] =
|
const REQUIRED: [(&str, &str, &str); 4] = [
|
||||||
[("version", "bigint", "NO"), ("name", "text", "NO"), ("checksum", "text", "NO"), ("applied_at", "timestamp with time zone", "NO")];
|
("version", "bigint", "NO"),
|
||||||
|
("name", "text", "NO"),
|
||||||
|
("checksum", "text", "NO"),
|
||||||
|
("applied_at", "timestamp with time zone", "NO"),
|
||||||
|
];
|
||||||
let mut found = [false; REQUIRED.len()];
|
let mut found = [false; REQUIRED.len()];
|
||||||
for row in rows {
|
for row in rows {
|
||||||
let name_result = row.try_get::<usize, std::string::String>(0);
|
let name_result = row.try_get::<usize, std::string::String>(0);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-store-postgres-lib/tests/dependency_boundary.rs
|
// file: crates/ksp-store-postgres-lib/tests/dependency_boundary.rs
|
||||||
// version: 6
|
// version: 7
|
||||||
|
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
#![deny(unreachable_pub)]
|
#![deny(unreachable_pub)]
|
||||||
@@ -21,10 +21,10 @@ fn pre_005_backend_owns_exact_physical_runtime_dependencies_without_reverse_faca
|
|||||||
let migration = include_str!("../src/migration.rs");
|
let migration = include_str!("../src/migration.rs");
|
||||||
let bootstrap_sql = include_str!("../migrations/V000__bootstrap.sql");
|
let bootstrap_sql = include_str!("../migrations/V000__bootstrap.sql");
|
||||||
assert!(migration.contains("include_str!(\"../migrations/V000__bootstrap.sql\")"));
|
assert!(migration.contains("include_str!(\"../migrations/V000__bootstrap.sql\")"));
|
||||||
|
assert!(migration.contains("include_str!(\"../migrations/V001__raw_transaction.sql\")"));
|
||||||
assert!(bootstrap_sql.contains("ksp_store_schema_migrations"));
|
assert!(bootstrap_sql.contains("ksp_store_schema_migrations"));
|
||||||
for forbidden in ["RawTransaction", "RawAccountState", "raw_transaction", "raw_account", "CORE", "DECODE", "SPECIALIZED"] {
|
for forbidden in ["RawTransaction", "RawAccountState", "raw_transaction", "raw_account", "CORE", "DECODE", "SPECIALIZED"] {
|
||||||
assert!(!migration.contains(forbidden), "business migration implementation leaked into foundation: {forbidden}");
|
assert!(!bootstrap_sql.contains(forbidden), "business schema leaked into immutable V000 SQL: {forbidden}");
|
||||||
assert!(!bootstrap_sql.contains(forbidden), "business schema leaked into foundation SQL: {forbidden}");
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -84,17 +84,28 @@ fn pre_007_health_probe_remains_foundation_only_and_private_sql() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pre_002_migration_engine_is_registry_driven_and_network_hook_ready_without_v001_schema() {
|
fn pre_003_migration_engine_embeds_v001_and_binds_network_without_repository_scope() {
|
||||||
let migration = include_str!("../src/migration.rs");
|
let migration = include_str!("../src/migration.rs");
|
||||||
|
let v001 = include_str!("../migrations/V001__raw_transaction.sql");
|
||||||
assert!(migration.contains("const EMBEDDED_MIGRATIONS: &[EmbeddedMigration]"));
|
assert!(migration.contains("const EMBEDDED_MIGRATIONS: &[EmbeddedMigration]"));
|
||||||
assert!(migration.contains("MigrationHook::None"));
|
assert!(migration.contains("MigrationHook::StoreIdentity"));
|
||||||
assert!(migration.contains("run_migration_hook(transaction, network, migration.hook, MigrationHookContext::AppliedNow).await"));
|
assert!(migration.contains("MigrationHookContext::AppliedNow"));
|
||||||
assert!(migration.contains("network: &ksp_store_api::RawNetworkId"));
|
|
||||||
assert!(migration.contains("MigrationHookContext::Existing"));
|
assert!(migration.contains("MigrationHookContext::Existing"));
|
||||||
assert!(migration.contains("run_applied_migration_hooks(&transaction, network, next_index).await"));
|
assert!(migration.contains("INSERT INTO ksp_store_identity (singleton, network) VALUES (1, $1)"));
|
||||||
assert!(migration.contains("validate_history(history.as_slice(), EMBEDDED_MIGRATIONS)"));
|
assert!(migration.contains("SELECT singleton, network FROM ksp_store_identity ORDER BY singleton LIMIT 2"));
|
||||||
assert!(migration.contains("apply_pending_migrations(&transaction, network, next_index).await"));
|
assert!(migration.contains("ksp_store_api::RawNetworkId::new(stored_network)"));
|
||||||
assert!(!migration.contains("V001__raw_transaction.sql"));
|
for required in [
|
||||||
assert!(!migration.contains("ksp_store_identity"));
|
"CREATE TABLE ksp_store_identity",
|
||||||
|
"CREATE TABLE ksp_raw_transactions",
|
||||||
|
"CREATE TABLE ksp_raw_transaction_observations",
|
||||||
|
"CREATE TABLE ksp_raw_transaction_archive_payloads",
|
||||||
|
"CREATE INDEX ix_ksp_raw_transactions_slot_signature",
|
||||||
|
] {
|
||||||
|
assert!(v001.contains(required), "missing V001 physical object: {required}");
|
||||||
|
}
|
||||||
|
for forbidden in ["impl ksp_store_api::RawTransaction", "repository", "sqlx", "RawAccountState"] {
|
||||||
|
assert!(!migration.contains(forbidden), "repository/cross-scope implementation leaked into migration engine: {forbidden}");
|
||||||
|
assert!(!v001.contains(forbidden), "forbidden V001 scope content detected: {forbidden}");
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-store-postgres-lib/tests/hardening_completeness.rs
|
// file: crates/ksp-store-postgres-lib/tests/hardening_completeness.rs
|
||||||
// version: 1
|
// version: 2
|
||||||
|
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
#![deny(unreachable_pub)]
|
#![deny(unreachable_pub)]
|
||||||
@@ -115,6 +115,7 @@ fn pre_009_backend_modules_exports_and_manifest_dependencies_are_exact() {
|
|||||||
assert!(!crate_root.contains("pub mod "));
|
assert!(!crate_root.contains("pub mod "));
|
||||||
let actual_exports = public_reexport_names(crate_root);
|
let actual_exports = public_reexport_names(crate_root);
|
||||||
let mut expected_exports = [
|
let mut expected_exports = [
|
||||||
|
"ERROR_CODE_POSTGRES_RETENTION_COMPACTION_UNSUPPORTED",
|
||||||
"PostgresBackend",
|
"PostgresBackend",
|
||||||
"PostgresBackendError",
|
"PostgresBackendError",
|
||||||
"PostgresBackendErrorKind",
|
"PostgresBackendErrorKind",
|
||||||
@@ -125,7 +126,7 @@ fn pre_009_backend_modules_exports_and_manifest_dependencies_are_exact() {
|
|||||||
];
|
];
|
||||||
expected_exports.sort_unstable();
|
expected_exports.sort_unstable();
|
||||||
assert_eq!(actual_exports.as_slice(), expected_exports.as_slice());
|
assert_eq!(actual_exports.as_slice(), expected_exports.as_slice());
|
||||||
assert_eq!(actual_exports.len(), 7);
|
assert_eq!(actual_exports.len(), 8);
|
||||||
let manifest = include_str!("../Cargo.toml");
|
let manifest = include_str!("../Cargo.toml");
|
||||||
let actual_dependencies = manifest_dependency_names(manifest);
|
let actual_dependencies = manifest_dependency_names(manifest);
|
||||||
let expected_dependencies = [
|
let expected_dependencies = [
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-store-postgres-lib/tests/postgres_foundation_live.rs
|
// file: crates/ksp-store-postgres-lib/tests/postgres_foundation_live.rs
|
||||||
// version: 1
|
// version: 2
|
||||||
|
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
#![deny(unreachable_pub)]
|
#![deny(unreachable_pub)]
|
||||||
@@ -8,12 +8,30 @@
|
|||||||
//! Opt-in real PostgreSQL proof for the Store foundation runtime.
|
//! Opt-in real PostgreSQL proof for the Store foundation runtime.
|
||||||
//!
|
//!
|
||||||
//! The test reads one dedicated PostgreSQL URI from stdin, refuses to start
|
//! The test reads one dedicated PostgreSQL URI from stdin, refuses to start
|
||||||
//! when the KSP migration metadata table already exists, never prints the URI,
|
//! when any KSP Store table managed by V000/V001 already exists, never prints
|
||||||
//! creates no business table and cleans up only metadata it proved it created.
|
//! the URI, and cleans up only the isolated schema it proved absent first. It
|
||||||
|
//! validates migration/bootstrap behavior, not RawTransaction capabilities.
|
||||||
|
|
||||||
const LIVE_BOOTSTRAP_SQL: &str = include_str!("../migrations/V000__bootstrap.sql");
|
const LIVE_BOOTSTRAP_SQL: &str = include_str!("../migrations/V000__bootstrap.sql");
|
||||||
const LIVE_BROKEN_CHECKSUM_A: &str = "0000000000000000000000000000000000000000000000000000000000000000";
|
const LIVE_BROKEN_CHECKSUM_A: &str = "0000000000000000000000000000000000000000000000000000000000000000";
|
||||||
const LIVE_BROKEN_CHECKSUM_B: &str = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff";
|
const LIVE_BROKEN_CHECKSUM_B: &str = "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff";
|
||||||
|
const LIVE_MANAGED_SCHEMA_DROP_SQL: &str = r#"DROP TABLE IF EXISTS ksp_raw_transaction_observations;
|
||||||
|
DROP TABLE IF EXISTS ksp_raw_transaction_archive_payloads;
|
||||||
|
DROP TABLE IF EXISTS ksp_raw_transactions;
|
||||||
|
DROP TABLE IF EXISTS ksp_store_identity;
|
||||||
|
DROP TABLE IF EXISTS ksp_store_schema_migrations;"#;
|
||||||
|
const LIVE_MANAGED_SCHEMA_EXISTS_SQL: &str = r#"SELECT EXISTS (
|
||||||
|
SELECT 1 FROM information_schema.tables
|
||||||
|
WHERE table_schema = current_schema()
|
||||||
|
AND table_name IN (
|
||||||
|
'ksp_store_schema_migrations',
|
||||||
|
'ksp_store_identity',
|
||||||
|
'ksp_raw_transactions',
|
||||||
|
'ksp_raw_transaction_observations',
|
||||||
|
'ksp_raw_transaction_archive_payloads'
|
||||||
|
)
|
||||||
|
AND table_type = 'BASE TABLE'
|
||||||
|
)"#;
|
||||||
const LIVE_MAX_URI_BYTES: usize = 4_096;
|
const LIVE_MAX_URI_BYTES: usize = 4_096;
|
||||||
const LIVE_METADATA_EXISTS_SQL: &str = r#"SELECT EXISTS (
|
const LIVE_METADATA_EXISTS_SQL: &str = r#"SELECT EXISTS (
|
||||||
SELECT 1 FROM information_schema.tables
|
SELECT 1 FROM information_schema.tables
|
||||||
@@ -21,7 +39,6 @@ const LIVE_METADATA_EXISTS_SQL: &str = r#"SELECT EXISTS (
|
|||||||
AND table_name = 'ksp_store_schema_migrations'
|
AND table_name = 'ksp_store_schema_migrations'
|
||||||
AND table_type = 'BASE TABLE'
|
AND table_type = 'BASE TABLE'
|
||||||
)"#;
|
)"#;
|
||||||
const LIVE_METADATA_DROP_SQL: &str = "DROP TABLE IF EXISTS ksp_store_schema_migrations";
|
|
||||||
const LIVE_SENTINEL_CHECKSUM_SQL: &str = "SELECT checksum FROM ksp_store_schema_migrations WHERE version = 0";
|
const LIVE_SENTINEL_CHECKSUM_SQL: &str = "SELECT checksum FROM ksp_store_schema_migrations WHERE version = 0";
|
||||||
const LIVE_SENTINEL_INSERT_SQL: &str =
|
const LIVE_SENTINEL_INSERT_SQL: &str =
|
||||||
"INSERT INTO ksp_store_schema_migrations (version, name, checksum, applied_at) VALUES (0, 'bootstrap', 'pre008_rollback_injected', CURRENT_TIMESTAMP)";
|
"INSERT INTO ksp_store_schema_migrations (version, name, checksum, applied_at) VALUES (0, 'bootstrap', 'pre008_rollback_injected', CURRENT_TIMESTAMP)";
|
||||||
@@ -83,13 +100,13 @@ async fn run_live_test(uri: &str) -> std::result::Result<(), LiveFailure> {
|
|||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
};
|
};
|
||||||
let preexisting_result = metadata_exists(&admin).await;
|
let preexisting_result = managed_schema_exists(&admin).await;
|
||||||
let preexisting = match preexisting_result {
|
let preexisting = match preexisting_result {
|
||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
};
|
};
|
||||||
if preexisting {
|
if preexisting {
|
||||||
return std::result::Result::Err(LiveFailure::new("metadata_preexisting_refusal"));
|
return std::result::Result::Err(LiveFailure::new("managed_schema_preexisting_refusal"));
|
||||||
}
|
}
|
||||||
let major_result = postgres_major(&admin).await;
|
let major_result = postgres_major(&admin).await;
|
||||||
let major = match major_result {
|
let major = match major_result {
|
||||||
@@ -100,16 +117,16 @@ async fn run_live_test(uri: &str) -> std::result::Result<(), LiveFailure> {
|
|||||||
return std::result::Result::Err(LiveFailure::new("postgres_major_unsupported"));
|
return std::result::Result::Err(LiveFailure::new("postgres_major_unsupported"));
|
||||||
}
|
}
|
||||||
eprintln!("KSP Store PostgreSQL live proof: server major {major}");
|
eprintln!("KSP Store PostgreSQL live proof: server major {major}");
|
||||||
let mut owns_metadata = false;
|
let mut owns_schema = false;
|
||||||
let scenario = run_foundation_scenario(&mut admin, uri, &mut owns_metadata).await;
|
let scenario = run_foundation_scenario(&mut admin, uri, &mut owns_schema).await;
|
||||||
let cleanup = if owns_metadata { drop_metadata(&admin).await } else { std::result::Result::Ok(()) };
|
let cleanup = if owns_schema { drop_managed_schema(&admin).await } else { std::result::Result::Ok(()) };
|
||||||
if let std::result::Result::Err(error) = cleanup {
|
if let std::result::Result::Err(error) = cleanup {
|
||||||
return std::result::Result::Err(error);
|
return std::result::Result::Err(error);
|
||||||
}
|
}
|
||||||
if let std::result::Result::Err(error) = scenario {
|
if let std::result::Result::Err(error) = scenario {
|
||||||
return std::result::Result::Err(error);
|
return std::result::Result::Err(error);
|
||||||
}
|
}
|
||||||
let remains_result = metadata_exists(&admin).await;
|
let remains_result = managed_schema_exists(&admin).await;
|
||||||
let remains = match remains_result {
|
let remains = match remains_result {
|
||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
@@ -120,7 +137,7 @@ async fn run_live_test(uri: &str) -> std::result::Result<(), LiveFailure> {
|
|||||||
return std::result::Result::Ok(());
|
return std::result::Result::Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run_foundation_scenario(admin: &mut tokio_postgres::Client, uri: &str, owns_metadata: &mut bool) -> std::result::Result<(), LiveFailure> {
|
async fn run_foundation_scenario(admin: &mut tokio_postgres::Client, uri: &str, owns_schema: &mut bool) -> std::result::Result<(), LiveFailure> {
|
||||||
let initial_result = open_backend(uri).await;
|
let initial_result = open_backend(uri).await;
|
||||||
let initial = match initial_result {
|
let initial = match initial_result {
|
||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
@@ -134,9 +151,9 @@ async fn run_foundation_scenario(admin: &mut tokio_postgres::Client, uri: &str,
|
|||||||
if !created {
|
if !created {
|
||||||
return std::result::Result::Err(LiveFailure::new("initial_bootstrap_metadata"));
|
return std::result::Result::Err(LiveFailure::new("initial_bootstrap_metadata"));
|
||||||
}
|
}
|
||||||
*owns_metadata = true;
|
*owns_schema = true;
|
||||||
let initial_health = initial.health().await;
|
let initial_health = initial.health().await;
|
||||||
if !initial_health.is_ready() || initial_health.migration_version() != std::option::Option::Some(0) || initial_health.pending_migration_count() != 0 {
|
if !initial_health.is_ready() || initial_health.migration_version() != std::option::Option::Some(1) || initial_health.pending_migration_count() != 0 {
|
||||||
return std::result::Result::Err(LiveFailure::new("initial_health"));
|
return std::result::Result::Err(LiveFailure::new("initial_health"));
|
||||||
}
|
}
|
||||||
let initial_close = close_backend(initial).await;
|
let initial_close = close_backend(initial).await;
|
||||||
@@ -150,7 +167,7 @@ async fn run_foundation_scenario(admin: &mut tokio_postgres::Client, uri: &str,
|
|||||||
};
|
};
|
||||||
let idempotent_health = idempotent.health().await;
|
let idempotent_health = idempotent.health().await;
|
||||||
if !idempotent_health.is_ready()
|
if !idempotent_health.is_ready()
|
||||||
|| idempotent_health.migration_version() != std::option::Option::Some(0)
|
|| idempotent_health.migration_version() != std::option::Option::Some(1)
|
||||||
|| idempotent_health.pending_migration_count() != 0
|
|| idempotent_health.pending_migration_count() != 0
|
||||||
{
|
{
|
||||||
return std::result::Result::Err(LiveFailure::new("idempotent_health"));
|
return std::result::Result::Err(LiveFailure::new("idempotent_health"));
|
||||||
@@ -159,7 +176,7 @@ async fn run_foundation_scenario(admin: &mut tokio_postgres::Client, uri: &str,
|
|||||||
if let std::result::Result::Err(error) = idempotent_close {
|
if let std::result::Result::Err(error) = idempotent_close {
|
||||||
return std::result::Result::Err(error);
|
return std::result::Result::Err(error);
|
||||||
}
|
}
|
||||||
let reset_result = drop_metadata(admin).await;
|
let reset_result = drop_managed_schema(admin).await;
|
||||||
if let std::result::Result::Err(error) = reset_result {
|
if let std::result::Result::Err(error) = reset_result {
|
||||||
return std::result::Result::Err(error);
|
return std::result::Result::Err(error);
|
||||||
}
|
}
|
||||||
@@ -213,7 +230,7 @@ async fn run_foundation_scenario(admin: &mut tokio_postgres::Client, uri: &str,
|
|||||||
if let std::result::Result::Err(error) = recovered_close {
|
if let std::result::Result::Err(error) = recovered_close {
|
||||||
return std::result::Result::Err(error);
|
return std::result::Result::Err(error);
|
||||||
}
|
}
|
||||||
let rollback_reset = drop_metadata(admin).await;
|
let rollback_reset = drop_managed_schema(admin).await;
|
||||||
if let std::result::Result::Err(error) = rollback_reset {
|
if let std::result::Result::Err(error) = rollback_reset {
|
||||||
return std::result::Result::Err(error);
|
return std::result::Result::Err(error);
|
||||||
}
|
}
|
||||||
@@ -233,7 +250,7 @@ async fn run_foundation_scenario(admin: &mut tokio_postgres::Client, uri: &str,
|
|||||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||||
};
|
};
|
||||||
let final_health = final_backend.health().await;
|
let final_health = final_backend.health().await;
|
||||||
if !final_health.is_ready() || final_health.migration_version() != std::option::Option::Some(0) || final_health.pending_migration_count() != 0 {
|
if !final_health.is_ready() || final_health.migration_version() != std::option::Option::Some(1) || final_health.pending_migration_count() != 0 {
|
||||||
return std::result::Result::Err(LiveFailure::new("final_health"));
|
return std::result::Result::Err(LiveFailure::new("final_health"));
|
||||||
}
|
}
|
||||||
return close_backend(final_backend).await;
|
return close_backend(final_backend).await;
|
||||||
@@ -354,6 +371,18 @@ async fn postgres_major(client: &tokio_postgres::Client) -> std::result::Result<
|
|||||||
return std::result::Result::Ok(version_num / 10_000);
|
return std::result::Result::Ok(version_num / 10_000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn managed_schema_exists(client: &tokio_postgres::Client) -> std::result::Result<bool, LiveFailure> {
|
||||||
|
let row_result = client.query_one(LIVE_MANAGED_SCHEMA_EXISTS_SQL, &[]).await;
|
||||||
|
let row = match row_result {
|
||||||
|
std::result::Result::Ok(value) => value,
|
||||||
|
std::result::Result::Err(_) => return std::result::Result::Err(LiveFailure::new("managed_schema_probe")),
|
||||||
|
};
|
||||||
|
return match row.try_get::<usize, bool>(0) {
|
||||||
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
||||||
|
std::result::Result::Err(_) => std::result::Result::Err(LiveFailure::new("managed_schema_probe_decode")),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async fn metadata_exists(client: &tokio_postgres::Client) -> std::result::Result<bool, LiveFailure> {
|
async fn metadata_exists(client: &tokio_postgres::Client) -> std::result::Result<bool, LiveFailure> {
|
||||||
let row_result = client.query_one(LIVE_METADATA_EXISTS_SQL, &[]).await;
|
let row_result = client.query_one(LIVE_METADATA_EXISTS_SQL, &[]).await;
|
||||||
let row = match row_result {
|
let row = match row_result {
|
||||||
@@ -366,10 +395,10 @@ async fn metadata_exists(client: &tokio_postgres::Client) -> std::result::Result
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn drop_metadata(client: &tokio_postgres::Client) -> std::result::Result<(), LiveFailure> {
|
async fn drop_managed_schema(client: &tokio_postgres::Client) -> std::result::Result<(), LiveFailure> {
|
||||||
return match client.batch_execute(LIVE_METADATA_DROP_SQL).await {
|
return match client.batch_execute(LIVE_MANAGED_SCHEMA_DROP_SQL).await {
|
||||||
std::result::Result::Ok(()) => std::result::Result::Ok(()),
|
std::result::Result::Ok(()) => std::result::Result::Ok(()),
|
||||||
std::result::Result::Err(_) => std::result::Result::Err(LiveFailure::new("metadata_cleanup")),
|
std::result::Result::Err(_) => std::result::Result::Err(LiveFailure::new("managed_schema_cleanup")),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-store-postgres-lib/tests/public_api.rs
|
// file: crates/ksp-store-postgres-lib/tests/public_api.rs
|
||||||
// version: 3
|
// version: 4
|
||||||
|
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
#![deny(unreachable_pub)]
|
#![deny(unreachable_pub)]
|
||||||
@@ -58,3 +58,13 @@ fn pre_007_backend_health_bridge_exposes_only_safe_snapshot_types() {
|
|||||||
let _health_probe = ksp_store_postgres_lib::PostgresBackend::health;
|
let _health_probe = ksp_store_postgres_lib::PostgresBackend::health;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pre_003_retention_compaction_error_code_matches_store_contract_value() {
|
||||||
|
assert_eq!(ksp_store_postgres_lib::ERROR_CODE_POSTGRES_RETENTION_COMPACTION_UNSUPPORTED.domain(), "store");
|
||||||
|
assert_eq!(
|
||||||
|
ksp_store_postgres_lib::ERROR_CODE_POSTGRES_RETENTION_COMPACTION_UNSUPPORTED.code(),
|
||||||
|
"postgres_retention_compaction_unsupported",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-store-postgres-lib/unit_tests/migration.rs
|
// file: crates/ksp-store-postgres-lib/unit_tests/migration.rs
|
||||||
// version: 2
|
// version: 3
|
||||||
|
|
||||||
fn applied(version: i64, name: &str, checksum: &str) -> super::AppliedMigration {
|
fn applied(version: i64, name: &str, checksum: &str) -> super::AppliedMigration {
|
||||||
return super::AppliedMigration { checksum: checksum.to_owned(), name: name.to_owned(), version };
|
return super::AppliedMigration { checksum: checksum.to_owned(), name: name.to_owned(), version };
|
||||||
@@ -10,36 +10,69 @@ fn embedded(version: i64, name: &'static str, sql: &'static str) -> super::Embed
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pre_002_embedded_registry_keeps_v000_immutable_and_current_version_registry_driven() {
|
fn pre_003_embedded_registry_keeps_v000_immutable_and_adds_exact_v001() {
|
||||||
assert_eq!(super::EMBEDDED_MIGRATIONS.len(), 1);
|
assert_eq!(super::EMBEDDED_MIGRATIONS.len(), 2);
|
||||||
let migration = &super::EMBEDDED_MIGRATIONS[0];
|
let v000 = &super::EMBEDDED_MIGRATIONS[0];
|
||||||
assert_eq!(migration.version, 0);
|
assert_eq!(v000.version, 0);
|
||||||
assert_eq!(migration.name, "bootstrap");
|
assert_eq!(v000.name, "bootstrap");
|
||||||
assert_eq!(migration.hook, super::MigrationHook::None);
|
assert_eq!(v000.hook, super::MigrationHook::None);
|
||||||
assert!(migration.sql.contains("CREATE TABLE ksp_store_schema_migrations"));
|
assert!(v000.sql.contains("CREATE TABLE ksp_store_schema_migrations"));
|
||||||
for forbidden in ["RawTransaction", "RawAccountState", "raw_transaction", "raw_account", "CORE", "DECODE", "SPECIALIZED"] {
|
for forbidden in ["RawTransaction", "RawAccountState", "raw_transaction", "raw_account", "CORE", "DECODE", "SPECIALIZED"] {
|
||||||
assert!(!migration.sql.contains(forbidden), "business schema leaked into bootstrap SQL: {forbidden}");
|
assert!(!v000.sql.contains(forbidden), "business schema leaked into immutable V000 SQL: {forbidden}");
|
||||||
}
|
}
|
||||||
|
assert_eq!(super::migration_checksum(v000.sql), "d29068b8c13b9dc0cc9ef6aaadd0fa12d41e0fe4c56541a1118c4bfc846a1450");
|
||||||
|
let v001 = &super::EMBEDDED_MIGRATIONS[1];
|
||||||
|
assert_eq!(v001.version, 1);
|
||||||
|
assert_eq!(v001.name, "raw_transaction");
|
||||||
|
assert_eq!(v001.hook, super::MigrationHook::StoreIdentity);
|
||||||
|
assert_eq!(super::migration_checksum(v001.sql), "6fe57ed0313d2ed295280dd6e49f6d86695d4e4effee2724a25e36db1ea17761");
|
||||||
assert!(super::validate_embedded_registry(super::EMBEDDED_MIGRATIONS).is_ok());
|
assert!(super::validate_embedded_registry(super::EMBEDDED_MIGRATIONS).is_ok());
|
||||||
assert_eq!(crate::current_migration_version(), 0);
|
assert_eq!(crate::current_migration_version(), 1);
|
||||||
let checksum = super::migration_checksum(migration.sql);
|
|
||||||
assert_eq!(checksum.len(), 64);
|
|
||||||
assert_eq!(checksum, "d29068b8c13b9dc0cc9ef6aaadd0fa12d41e0fe4c56541a1118c4bfc846a1450");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pre_002_ordered_registry_accepts_exact_history_prefix_and_full_history() {
|
fn pre_003_v001_inventory_indexes_and_api_bounds_are_exact() {
|
||||||
|
let sql = super::EMBEDDED_MIGRATIONS[1].sql;
|
||||||
|
for required in [
|
||||||
|
"CREATE TABLE ksp_store_identity",
|
||||||
|
"CREATE TABLE ksp_raw_transactions",
|
||||||
|
"CREATE TABLE ksp_raw_transaction_observations",
|
||||||
|
"CREATE TABLE ksp_raw_transaction_archive_payloads",
|
||||||
|
"CREATE INDEX ix_ksp_raw_transactions_slot_signature",
|
||||||
|
"WHERE retention_state <> 'purged'",
|
||||||
|
"octet_length(signature) = 64",
|
||||||
|
"slot BETWEEN 0 AND 18446744073709551615",
|
||||||
|
"block_time_unix_millis BETWEEN 0 AND 253402300799999",
|
||||||
|
"octet_length(content_hash) = 32",
|
||||||
|
"octet_length(payload) BETWEEN 1 AND 16777216",
|
||||||
|
"format_version BETWEEN 1 AND 4294967295",
|
||||||
|
"received_at_unix_millis BETWEEN 0 AND 253402300799999",
|
||||||
|
"source_payload_size_bytes BETWEEN 0 AND 67108864",
|
||||||
|
"origin IN ('backfill', 'import', 'live', 'repair', 'replay')",
|
||||||
|
"retention_state IN ('full', 'archived', 'purged')",
|
||||||
|
] {
|
||||||
|
assert!(sql.contains(required), "V001 physical contract is missing: {required}");
|
||||||
|
}
|
||||||
|
assert_eq!(sql.matches("PRIMARY KEY").count(), 4);
|
||||||
|
assert_eq!(sql.matches("REFERENCES ksp_raw_transactions(signature) ON DELETE RESTRICT").count(), 2);
|
||||||
|
assert!(!sql.contains("compacted"));
|
||||||
|
assert!(!sql.contains("BIGSERIAL"));
|
||||||
|
assert!(!sql.contains("slot BIGINT"));
|
||||||
|
assert!(sql.contains("network TEXT NOT NULL"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pre_003_ordered_registry_accepts_v000_prefix_and_full_v001_history() {
|
||||||
let v000 = super::EMBEDDED_MIGRATIONS[0];
|
let v000 = super::EMBEDDED_MIGRATIONS[0];
|
||||||
let v001 = embedded(1, "synthetic", "SELECT 1;");
|
let v001 = super::EMBEDDED_MIGRATIONS[1];
|
||||||
let registry = [v000, v001];
|
|
||||||
assert!(super::validate_embedded_registry(®istry).is_ok());
|
|
||||||
let v000_checksum = super::migration_checksum(v000.sql);
|
let v000_checksum = super::migration_checksum(v000.sql);
|
||||||
let prefix = [applied(0, v000.name, v000_checksum.as_str())];
|
let prefix = [applied(0, v000.name, v000_checksum.as_str())];
|
||||||
assert_eq!(super::validate_history(&prefix, ®istry).ok(), std::option::Option::Some(1));
|
assert_eq!(super::validate_history(&prefix, super::EMBEDDED_MIGRATIONS).ok(), std::option::Option::Some(1));
|
||||||
let v001_checksum = super::migration_checksum(v001.sql);
|
let v001_checksum = super::migration_checksum(v001.sql);
|
||||||
let full = [applied(0, v000.name, v000_checksum.as_str()), applied(1, v001.name, v001_checksum.as_str())];
|
let full = [applied(0, v000.name, v000_checksum.as_str()), applied(1, v001.name, v001_checksum.as_str())];
|
||||||
assert_eq!(super::validate_history(&full, ®istry).ok(), std::option::Option::Some(2));
|
assert_eq!(super::validate_history(&full, super::EMBEDDED_MIGRATIONS).ok(), std::option::Option::Some(2));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,10 +91,9 @@ fn pre_002_registry_rejects_empty_nonzero_gap_and_empty_metadata_entries() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pre_002_divergent_missing_or_gapped_history_is_terminal_mismatch() {
|
fn pre_003_divergent_missing_or_gapped_history_is_terminal_mismatch() {
|
||||||
let v000 = super::EMBEDDED_MIGRATIONS[0];
|
let v000 = super::EMBEDDED_MIGRATIONS[0];
|
||||||
let v001 = embedded(1, "synthetic", "SELECT 1;");
|
let v001 = super::EMBEDDED_MIGRATIONS[1];
|
||||||
let registry = [v000, v001];
|
|
||||||
let v000_checksum = super::migration_checksum(v000.sql);
|
let v000_checksum = super::migration_checksum(v000.sql);
|
||||||
let v001_checksum = super::migration_checksum(v001.sql);
|
let v001_checksum = super::migration_checksum(v001.sql);
|
||||||
let wrong_name = [applied(0, "changed", v000_checksum.as_str())];
|
let wrong_name = [applied(0, "changed", v000_checksum.as_str())];
|
||||||
@@ -69,21 +101,24 @@ fn pre_002_divergent_missing_or_gapped_history_is_terminal_mismatch() {
|
|||||||
let missing: [super::AppliedMigration; 0] = [];
|
let missing: [super::AppliedMigration; 0] = [];
|
||||||
let missing_v000 = [applied(1, v001.name, v001_checksum.as_str())];
|
let missing_v000 = [applied(1, v001.name, v001_checksum.as_str())];
|
||||||
for history in [&wrong_name[..], &wrong_checksum[..], &missing[..], &missing_v000[..]] {
|
for history in [&wrong_name[..], &wrong_checksum[..], &missing[..], &missing_v000[..]] {
|
||||||
let result = super::validate_history(history, ®istry);
|
let result = super::validate_history(history, super::EMBEDDED_MIGRATIONS);
|
||||||
assert_eq!(result.err().map(|value| return value.kind()), std::option::Option::Some(crate::PostgresBackendErrorKind::MigrationMismatch));
|
assert_eq!(result.err().map(|value| return value.kind()), std::option::Option::Some(crate::PostgresBackendErrorKind::MigrationMismatch));
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pre_002_newer_history_is_rejected_without_down_migration() {
|
fn pre_003_newer_history_is_rejected_without_down_migration() {
|
||||||
let v000 = super::EMBEDDED_MIGRATIONS[0];
|
let v000 = super::EMBEDDED_MIGRATIONS[0];
|
||||||
let v001 = embedded(1, "synthetic", "SELECT 1;");
|
let v001 = super::EMBEDDED_MIGRATIONS[1];
|
||||||
let registry = [v000, v001];
|
|
||||||
let v000_checksum = super::migration_checksum(v000.sql);
|
let v000_checksum = super::migration_checksum(v000.sql);
|
||||||
let v001_checksum = super::migration_checksum(v001.sql);
|
let v001_checksum = super::migration_checksum(v001.sql);
|
||||||
let history = [applied(0, v000.name, v000_checksum.as_str()), applied(1, v001.name, v001_checksum.as_str()), applied(2, "future", "future-checksum")];
|
let history = [
|
||||||
let result = super::validate_history(&history, ®istry);
|
applied(0, v000.name, v000_checksum.as_str()),
|
||||||
|
applied(1, v001.name, v001_checksum.as_str()),
|
||||||
|
applied(2, "future", "future-checksum"),
|
||||||
|
];
|
||||||
|
let result = super::validate_history(&history, super::EMBEDDED_MIGRATIONS);
|
||||||
assert_eq!(result.err().map(|value| return value.kind()), std::option::Option::Some(crate::PostgresBackendErrorKind::SchemaNewer));
|
assert_eq!(result.err().map(|value| return value.kind()), std::option::Option::Some(crate::PostgresBackendErrorKind::SchemaNewer));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
353
deltas/0.3.3/pre.003.md
Normal file
353
deltas/0.3.3/pre.003.md
Normal file
@@ -0,0 +1,353 @@
|
|||||||
|
<!-- file: deltas/0.3.3/pre.003.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.3.3-pre.003` — V001 RawTransaction physique et binding réseau
|
||||||
|
|
||||||
|
## 1. Base requise
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.3-pre.002
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur fourni pour `pre.002` est entièrement vert :
|
||||||
|
|
||||||
|
```text
|
||||||
|
cargo fmt --all PASS
|
||||||
|
audit Rust général / exports / workspace PASS
|
||||||
|
audit Markdown PASS — 214 tables / 132 files
|
||||||
|
cargo check --workspace PASS
|
||||||
|
cargo clippy --workspace --all-targets PASS
|
||||||
|
cargo test -p ksp-store-api PASS
|
||||||
|
cargo test -p ksp-store-lib PASS
|
||||||
|
cargo test -p ksp-store-postgres-lib PASS
|
||||||
|
cargo test -p ksp-config-lib PASS
|
||||||
|
cargo check -p ksp-store-lib --no-default-features PASS
|
||||||
|
```
|
||||||
|
|
||||||
|
Le test PostgreSQL foundation reste `#[ignore]` dans ce gate ordinaire. La preuve live complète de la vertical slice `0.3.3` reste réservée à `pre.009`.
|
||||||
|
|
||||||
|
## 2. Objectif
|
||||||
|
|
||||||
|
Matérialiser le design physique décidé en `pre.001` sur le moteur multi-version acquis en `pre.002`, sans commencer encore les repositories/capabilities métier.
|
||||||
|
|
||||||
|
La tranche livre :
|
||||||
|
|
||||||
|
```text
|
||||||
|
V001__raw_transaction.sql
|
||||||
|
registre embedded réel V000 + V001
|
||||||
|
binding mono-réseau atomique
|
||||||
|
contraintes/indexes RAW exacts
|
||||||
|
checksum/inventory/bounds statiques
|
||||||
|
code stable PostgreSQL Compacted unsupported
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Version
|
||||||
|
|
||||||
|
Le workspace passe à :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.3-pre.3
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Migration V001
|
||||||
|
|
||||||
|
La migration ajoutée est :
|
||||||
|
|
||||||
|
```text
|
||||||
|
crates/ksp-store-postgres-lib/migrations/V001__raw_transaction.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
Le registre embedded contient désormais exactement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
V000 bootstrap
|
||||||
|
V001 raw_transaction
|
||||||
|
```
|
||||||
|
|
||||||
|
La version courante dérivée du registre vaut donc :
|
||||||
|
|
||||||
|
```text
|
||||||
|
1
|
||||||
|
```
|
||||||
|
|
||||||
|
V000 reste byte-identique avec son checksum historique :
|
||||||
|
|
||||||
|
```text
|
||||||
|
d29068b8c13b9dc0cc9ef6aaadd0fa12d41e0fe4c56541a1118c4bfc846a1450
|
||||||
|
```
|
||||||
|
|
||||||
|
Checksum SHA-256 V001 :
|
||||||
|
|
||||||
|
```text
|
||||||
|
6fe57ed0313d2ed295280dd6e49f6d86695d4e4effee2724a25e36db1ea17761
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Inventaire physique V001
|
||||||
|
|
||||||
|
V001 crée exactement les tables métier/identité suivantes :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp_store_identity
|
||||||
|
ksp_raw_transactions
|
||||||
|
ksp_raw_transaction_observations
|
||||||
|
ksp_raw_transaction_archive_payloads
|
||||||
|
```
|
||||||
|
|
||||||
|
et l'index secondaire suivant :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ix_ksp_raw_transactions_slot_signature
|
||||||
|
ON ksp_raw_transactions(slot, signature)
|
||||||
|
WHERE retention_state <> 'purged'
|
||||||
|
```
|
||||||
|
|
||||||
|
Les clés physiques restent :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp_store_identity(singleton)
|
||||||
|
ksp_raw_transactions(signature)
|
||||||
|
ksp_raw_transaction_observations(observation_key)
|
||||||
|
ksp_raw_transaction_archive_payloads(signature)
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun ID SQL auxiliaire n'est ajouté.
|
||||||
|
|
||||||
|
## 6. Bornes et contraintes
|
||||||
|
|
||||||
|
Les contraintes SQL matérialisent les invariants backend-agnostiques déjà acquis :
|
||||||
|
|
||||||
|
```text
|
||||||
|
network/code UTF-8 ASCII sûr 1..=128 octets
|
||||||
|
signature transaction 64 octets
|
||||||
|
observation key 32 octets
|
||||||
|
content/source hash 32 octets
|
||||||
|
slot NUMERIC(20,0), 0..=u64::MAX
|
||||||
|
format_version BIGINT, 1..=u32::MAX
|
||||||
|
RawTimestamp BIGINT, 0..=253402300799999
|
||||||
|
payload canonical/archive 1..=16 MiB lorsqu'il existe
|
||||||
|
source payload size 0..=64 MiB
|
||||||
|
origin backfill/import/live/repair/replay
|
||||||
|
retention physique full/archived/purged
|
||||||
|
```
|
||||||
|
|
||||||
|
`slot` n'est jamais réduit en `BIGINT` signé.
|
||||||
|
|
||||||
|
La cohérence de rétention physique impose :
|
||||||
|
|
||||||
|
```text
|
||||||
|
full -> payload hot présent
|
||||||
|
archived -> payload hot absent
|
||||||
|
purged -> payload hot absent + block_time absent
|
||||||
|
```
|
||||||
|
|
||||||
|
La relation archive reste séparée du hot path. V001 ne prétend jamais représenter `Compacted`.
|
||||||
|
|
||||||
|
## 7. Binding mono-réseau atomique
|
||||||
|
|
||||||
|
V001 déclare le hook privé :
|
||||||
|
|
||||||
|
```text
|
||||||
|
MigrationHook::StoreIdentity
|
||||||
|
```
|
||||||
|
|
||||||
|
Le comportement est distinct selon le contexte déjà acquis en `pre.002`.
|
||||||
|
|
||||||
|
### `AppliedNow`
|
||||||
|
|
||||||
|
Sous la même transaction et le même advisory lock :
|
||||||
|
|
||||||
|
```text
|
||||||
|
batch_execute(V001)
|
||||||
|
-> INSERT singleton=1 + network runtime
|
||||||
|
-> relire exactement l'identité
|
||||||
|
-> valider singleton + RawNetworkId + égalité réseau
|
||||||
|
-> INSERT history V001
|
||||||
|
-> commit final
|
||||||
|
```
|
||||||
|
|
||||||
|
L'insertion n'utilise aucun upsert : une divergence physique ne peut pas être absorbée silencieusement.
|
||||||
|
|
||||||
|
### `Existing`
|
||||||
|
|
||||||
|
Une réouverture V001 effectue uniquement :
|
||||||
|
|
||||||
|
```text
|
||||||
|
SELECT singleton, network
|
||||||
|
-> exactement une ligne
|
||||||
|
-> singleton = 1
|
||||||
|
-> network valide RawNetworkId
|
||||||
|
-> network == backend.network
|
||||||
|
```
|
||||||
|
|
||||||
|
Une identité absente, multiple, illisible, malformée ou différente devient `MigrationMismatch` avec une phase statique. La valeur réseau persistée et les erreurs PostgreSQL ne sont jamais retenues/rendues.
|
||||||
|
|
||||||
|
Une base V001 dont l'identité a disparu n'est donc jamais réclamée/rebindée automatiquement.
|
||||||
|
|
||||||
|
## 8. `Compacted` PostgreSQL non supporté
|
||||||
|
|
||||||
|
Le code stable suivant est désormais matérialisé par valeur dans le backend et la façade :
|
||||||
|
|
||||||
|
```text
|
||||||
|
store.postgres_retention_compaction_unsupported
|
||||||
|
```
|
||||||
|
|
||||||
|
Exports :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp_store_postgres_lib::ERROR_CODE_POSTGRES_RETENTION_COMPACTION_UNSUPPORTED
|
||||||
|
ksp_store_lib::ERROR_CODE_POSTGRES_RETENTION_COMPACTION_UNSUPPORTED
|
||||||
|
```
|
||||||
|
|
||||||
|
Les deux crates construisent le même `ErrorCode` KSP sans créer de dépendance inverse `ksp-store-postgres-lib -> ksp-store-lib`.
|
||||||
|
|
||||||
|
L'utilisation opérationnelle de ce code par `RawTransactionRetentionWrite` reste réservée à `pre.007`.
|
||||||
|
|
||||||
|
## 9. Tests statiques ajoutés/actualisés
|
||||||
|
|
||||||
|
Les tests de migration couvrent désormais :
|
||||||
|
|
||||||
|
```text
|
||||||
|
registre réel [V000,V001]
|
||||||
|
V000 checksum immuable
|
||||||
|
V001 checksum exact
|
||||||
|
version courante = 1
|
||||||
|
V000 seul = préfixe valide / V001 pending
|
||||||
|
historique complet V000+V001 valide
|
||||||
|
mismatch/newer toujours terminaux
|
||||||
|
inventaire exact des quatre tables
|
||||||
|
index partiel exact
|
||||||
|
slot u64 max sans narrowing
|
||||||
|
bornes u32/timestamp/payload/source payload
|
||||||
|
retention physique sans compacted
|
||||||
|
hook StoreIdentity réel
|
||||||
|
contextes AppliedNow / Existing
|
||||||
|
absence de repository/capability dans la tranche
|
||||||
|
```
|
||||||
|
|
||||||
|
Les canaris de surface publique sont actualisés :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-store-postgres-lib root pub use : 7 -> 8
|
||||||
|
ksp-store-lib root pub use : 84 -> 85
|
||||||
|
```
|
||||||
|
|
||||||
|
La seule nouvelle surface est le code d'erreur PostgreSQL `Compacted` explicitement planifié.
|
||||||
|
|
||||||
|
Le test live foundation hérité de `0.3.2` est maintenu cohérent avec V001 sans devenir la preuve métier de cette release :
|
||||||
|
|
||||||
|
```text
|
||||||
|
version health attendue 0 -> 1
|
||||||
|
refus de départ toute table Store V000/V001 déjà présente
|
||||||
|
cleanup ensemble des tables Store V000/V001 possédées
|
||||||
|
preuve de rollback historique V000 inchangée
|
||||||
|
statut dans le gate ordinaire toujours #[ignore]
|
||||||
|
```
|
||||||
|
|
||||||
|
Il n'est pas exécuté dans cette tranche ; les scénarios V001 spécifiques restent réservés à `pre.009`.
|
||||||
|
|
||||||
|
## 10. Preuves volontairement différées
|
||||||
|
|
||||||
|
Cette tranche ne prétend pas avoir exécuté PostgreSQL réel pour V001.
|
||||||
|
|
||||||
|
Restent au gate live `pre.009` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
upgrade réel V000 -> V001
|
||||||
|
rollback réel si hook échoue
|
||||||
|
réouverture même réseau
|
||||||
|
réouverture réseau différent
|
||||||
|
identity supprimée/malformée sur DB réelle
|
||||||
|
checksum V001 divergent sur DB réelle
|
||||||
|
concurrence réelle du bootstrap V001
|
||||||
|
```
|
||||||
|
|
||||||
|
Ces points ne sont donc pas marqués PASS dans la validation `pre.003`.
|
||||||
|
|
||||||
|
## 11. Fichiers modifiés
|
||||||
|
|
||||||
|
```text
|
||||||
|
Cargo.toml
|
||||||
|
crates/ksp-store-lib/src/error.rs
|
||||||
|
crates/ksp-store-lib/src/lib.rs
|
||||||
|
crates/ksp-store-lib/tests/hardening_completeness.rs
|
||||||
|
crates/ksp-store-lib/tests/public_api.rs
|
||||||
|
crates/ksp-store-postgres-lib/src/error.rs
|
||||||
|
crates/ksp-store-postgres-lib/src/lib.rs
|
||||||
|
crates/ksp-store-postgres-lib/src/migration.rs
|
||||||
|
crates/ksp-store-postgres-lib/tests/dependency_boundary.rs
|
||||||
|
crates/ksp-store-postgres-lib/tests/hardening_completeness.rs
|
||||||
|
crates/ksp-store-postgres-lib/tests/postgres_foundation_live.rs
|
||||||
|
crates/ksp-store-postgres-lib/tests/public_api.rs
|
||||||
|
crates/ksp-store-postgres-lib/unit_tests/migration.rs
|
||||||
|
docs/plans/024-V0_3_3_STORE_POSTGRES_RAW_TRANSACTION_PLAN.md
|
||||||
|
docs/validation/020-V0_3_3_STORE_POSTGRES_RAW_TRANSACTION.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 12. Fichiers ajoutés
|
||||||
|
|
||||||
|
```text
|
||||||
|
crates/ksp-store-postgres-lib/migrations/V001__raw_transaction.sql
|
||||||
|
deltas/0.3.3/pre.003.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 13. Fichiers supprimés
|
||||||
|
|
||||||
|
Aucun.
|
||||||
|
|
||||||
|
## 14. Hors scope confirmé
|
||||||
|
|
||||||
|
Aucun changement n'implémente encore :
|
||||||
|
|
||||||
|
```text
|
||||||
|
RawTransactionRead
|
||||||
|
RawTransactionWrite
|
||||||
|
RawTransactionObservationRead
|
||||||
|
RawTransactionObservationWrite
|
||||||
|
RawTransactionRetentionRead
|
||||||
|
RawTransactionRetentionWrite
|
||||||
|
row codecs SQL métier
|
||||||
|
get/list/cursor
|
||||||
|
canonical+observation writes
|
||||||
|
archive/purge/rehydrate behavior
|
||||||
|
ksp-store-lib capability dispatch
|
||||||
|
RawAccountState PostgreSQL
|
||||||
|
Config std.store
|
||||||
|
workers/jobs/apps
|
||||||
|
```
|
||||||
|
|
||||||
|
## 15. Validations exécutées dans l'environnement de génération
|
||||||
|
|
||||||
|
```text
|
||||||
|
python3 scripts/audit_rust_workspace_rules.py
|
||||||
|
General Rust rule audit: clean
|
||||||
|
Rust export completeness audit: 0 candidate(s)
|
||||||
|
KSP workspace Rust rule audit: clean
|
||||||
|
|
||||||
|
python3 scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts crates deltas/0.3.3
|
||||||
|
Markdown table audit: clean (214 table(s), 133 file(s))
|
||||||
|
```
|
||||||
|
|
||||||
|
Les commandes Cargo ne sont pas disponibles dans l'environnement de génération. Elles restent **NON EXÉCUTÉES** ici et doivent être exécutées côté opérateur.
|
||||||
|
|
||||||
|
## 16. Gate opérateur demandé
|
||||||
|
|
||||||
|
```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
|
||||||
|
```
|
||||||
|
|
||||||
|
Le test PostgreSQL live demeure hors gate ordinaire jusqu'à la tranche dédiée `pre.009`.
|
||||||
|
|
||||||
|
## 17. Suite prévue après gate vert
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.3-pre.004 — mapping SQL privé + lectures RawTransaction/observation/rétention/tombstone
|
||||||
|
```
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<!-- file: docs/plans/024-V0_3_3_STORE_POSTGRES_RAW_TRANSACTION_PLAN.md -->
|
<!-- file: docs/plans/024-V0_3_3_STORE_POSTGRES_RAW_TRANSACTION_PLAN.md -->
|
||||||
<!-- version: 2 -->
|
<!-- version: 3 -->
|
||||||
|
|
||||||
# Plan `0.3.3` — Store/PostgreSQL RawTransaction vertical slice
|
# Plan `0.3.3` — Store/PostgreSQL RawTransaction vertical slice
|
||||||
|
|
||||||
@@ -23,13 +23,20 @@ Première tranche validée :
|
|||||||
0.3.3-pre.001 — audit, threat model, physical design, sizing et planning
|
0.3.3-pre.001 — audit, threat model, physical design, sizing et planning
|
||||||
```
|
```
|
||||||
|
|
||||||
Tranche technique courante :
|
Tranches techniques validées :
|
||||||
|
|
||||||
```text
|
```text
|
||||||
|
0.3.3-pre.001 — audit, threat model, physical design, sizing et planning
|
||||||
0.3.3-pre.002 — moteur de migrations multi-version
|
0.3.3-pre.002 — moteur de migrations multi-version
|
||||||
```
|
```
|
||||||
|
|
||||||
`pre.001` est restée volontairement une tranche de conception. Son gate opérateur est vert. `pre.002` généralise uniquement le moteur de migrations PostgreSQL ; elle ne crée toujours ni migration métier `V001`, ni repository PostgreSQL métier, ni dispatch RAW dans `ksp-store-lib`.
|
Tranche technique courante :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.3-pre.003 — V001 physique + binding réseau
|
||||||
|
```
|
||||||
|
|
||||||
|
Les gates opérateur de `pre.001` et `pre.002` sont verts. `pre.003` matérialise le schéma physique V001 et le binding mono-réseau atomique, mais n'ouvre encore ni repository PostgreSQL métier, ni capability RAW, ni dispatch dans `ksp-store-lib`.
|
||||||
|
|
||||||
## 2. Sources et autorité
|
## 2. Sources et autorité
|
||||||
|
|
||||||
@@ -1033,6 +1040,17 @@ Statut matérialisé en `pre.002` :
|
|||||||
- tests de checksum/inventory/bounds ;
|
- tests de checksum/inventory/bounds ;
|
||||||
- matérialiser `store.postgres_retention_compaction_unsupported`, sans modifier les contrats stables sauf preuve nouvelle d'un blocage réel.
|
- matérialiser `store.postgres_retention_compaction_unsupported`, sans modifier les contrats stables sauf preuve nouvelle d'un blocage réel.
|
||||||
|
|
||||||
|
Statut matérialisé en `pre.003` :
|
||||||
|
|
||||||
|
- registre réel `[V000, V001]`, version courante `1` ;
|
||||||
|
- V000 reste byte-identique avec son checksum historique ;
|
||||||
|
- V001 est embarquée sous `migrations/V001__raw_transaction.sql` avec checksum SHA-256 `6fe57ed0313d2ed295280dd6e49f6d86695d4e4effee2724a25e36db1ea17761` ;
|
||||||
|
- V001 crée exactement `ksp_store_identity`, `ksp_raw_transactions`, `ksp_raw_transaction_observations`, `ksp_raw_transaction_archive_payloads` et l'index partiel `(slot, signature)` hors `purged` ;
|
||||||
|
- `slot` reste `NUMERIC(20,0)` et toutes les bornes physiques suivent les invariants `ksp-store-api` sans narrowing ;
|
||||||
|
- le hook V001 crée puis valide l'identité en `AppliedNow`, et valide seulement en `Existing` ; absence, forme invalide ou réseau différent deviennent un `MigrationMismatch` sûr sans rendre la valeur persistée ;
|
||||||
|
- `store.postgres_retention_compaction_unsupported` est réservé par valeur dans le backend et la façade, sans reverse dependency ;
|
||||||
|
- aucune capability/repository `RawTransaction*` n'est encore implémentée.
|
||||||
|
|
||||||
### `0.3.3-pre.004` — mapping et lectures
|
### `0.3.3-pre.004` — mapping et lectures
|
||||||
|
|
||||||
- codecs SQL privés ;
|
- codecs SQL privés ;
|
||||||
|
|||||||
@@ -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: 2 -->
|
<!-- version: 3 -->
|
||||||
|
|
||||||
# Validation `0.3.3` — Store/PostgreSQL RawTransaction vertical slice
|
# Validation `0.3.3` — Store/PostgreSQL RawTransaction vertical slice
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ Cette validation accompagne :
|
|||||||
0.3.3 — Store/PostgreSQL RawTransaction vertical slice
|
0.3.3 — Store/PostgreSQL RawTransaction vertical slice
|
||||||
```
|
```
|
||||||
|
|
||||||
Elle démarre en `0.3.3-pre.001` comme matrice de preuve. Le gate opérateur de `pre.001` est vert. Les lignes non encore implémentées restent explicitement `À FAIRE`; elles ne sont pas présentées comme acquises.
|
Elle démarre en `0.3.3-pre.001` comme matrice de preuve. Les gates opérateur de `pre.001` et `pre.002` sont verts. Les lignes non encore implémentées restent explicitement `À FAIRE`; elles ne sont pas présentées comme acquises.
|
||||||
|
|
||||||
## 2. Baseline stable
|
## 2. Baseline stable
|
||||||
|
|
||||||
@@ -176,7 +176,7 @@ ksp_raw_transaction_observations
|
|||||||
ksp_raw_transaction_archive_payloads
|
ksp_raw_transaction_archive_payloads
|
||||||
```
|
```
|
||||||
|
|
||||||
Statut `pre.001` : **design seulement**. Aucun de ces objets V001 n'est encore créé.
|
Statut `pre.003` : **matérialisé dans V001**. Les quatre tables ci-dessus sont créées par la migration embedded `V001__raw_transaction.sql`; aucune capability/repository ne les consomme encore.
|
||||||
|
|
||||||
### 5.2 Indexes
|
### 5.2 Indexes
|
||||||
|
|
||||||
@@ -355,7 +355,7 @@ Archived
|
|||||||
Purged
|
Purged
|
||||||
```
|
```
|
||||||
|
|
||||||
`Compacted` reste un état logique API connu, explicitement optionnel depuis le plan `0.3.1`, mais non représenté mensongèrement par PostgreSQL `0.3.3`.
|
`Compacted` reste un état logique API connu, explicitement optionnel depuis le plan `0.3.1`, mais non représenté mensongèrement par PostgreSQL `0.3.3`. Le code `store.postgres_retention_compaction_unsupported` est matérialisé dès `pre.003` dans `ksp-store-postgres-lib` et `ksp-store-lib` avec la même valeur KSP ; son usage opérationnel par les transitions sera branché en `pre.007`.
|
||||||
|
|
||||||
### 14.2 Matrice
|
### 14.2 Matrice
|
||||||
|
|
||||||
@@ -446,21 +446,23 @@ Preuves acquises en `pre.002` avant création réelle de V001 :
|
|||||||
- les migrations pending sont appliquées dans l'ordre sous la transaction et l'advisory lock déjà acquis ;
|
- les migrations pending sont appliquées dans l'ordre sous la transaction et l'advisory lock déjà acquis ;
|
||||||
- chaque migration vérifie encore la forme de la metadata avant d'insérer sa ligne d'historique.
|
- chaque migration vérifie encore la forme de la metadata avant d'insérer sa ligne d'historique.
|
||||||
|
|
||||||
Preuves différées à `pre.003` après création de la vraie V001 :
|
Preuves matérialisées en `pre.003` avec la vraie V001 :
|
||||||
|
|
||||||
- registre réel `[V000, V001]` ;
|
- registre réel `[V000, V001]` et version courante dérivée `1` ;
|
||||||
- V000 seule + auto migrate -> V001 appliquée ;
|
- historique V000 seul reconnu comme préfixe exact avec V001 pending ;
|
||||||
- V001 checksum divergent -> mismatch ;
|
- historique V000+V001 exact reconnu comme complet ;
|
||||||
- binding réseau réel dans le hook ;
|
- checksum V001 figé à `6fe57ed0313d2ed295280dd6e49f6d86695d4e4effee2724a25e36db1ea17761` ;
|
||||||
- inventaire/checksum exact de V001.
|
- binding réseau réel branché dans le hook `StoreIdentity` ;
|
||||||
|
- inventaire physique exact : identity, canonical, observations, archive, index partiel ;
|
||||||
|
- bornes SQL statiquement alignées sur `u64`, `u32`, `RawTimestamp`, payload 16 MiB et source payload 64 MiB.
|
||||||
|
|
||||||
La concurrence PostgreSQL réelle du moteur reste couverte par la preuve foundation acquise en `0.3.2`; la vertical slice métier recevra sa propre preuve live en `pre.009`.
|
L'application V000 -> V001, le rollback réel du hook, la réouverture même/autre réseau et la divergence de checksum sur PostgreSQL réel restent à prouver dans le gate live dédié `pre.009`; ils ne sont pas déclarés PASS dans cette tranche.
|
||||||
|
|
||||||
### 17.2 Binding réseau atomique
|
### 17.2 Binding réseau atomique
|
||||||
|
|
||||||
`pre.002` prépare la frontière sans créer l'identité : `RawNetworkId` est transmis au moteur et chaque migration possède un hook privé exécuté sous la même transaction/advisory lock. Deux contextes sont distingués : `AppliedNow` pour une migration qui vient d'être exécutée et `Existing` pour une migration déjà présente dans l'historique lors d'une réouverture. V000 déclare explicitement le hook neutre.
|
`pre.002` prépare la frontière sans créer l'identité : `RawNetworkId` est transmis au moteur et chaque migration possède un hook privé exécuté sous la même transaction/advisory lock. Deux contextes sont distingués : `AppliedNow` pour une migration qui vient d'être exécutée et `Existing` pour une migration déjà présente dans l'historique lors d'une réouverture. V000 déclare explicitement le hook neutre.
|
||||||
|
|
||||||
`pre.003` ajoutera le hook V001 réel : en contexte `AppliedNow`, il créera puis validera `ksp_store_identity` avant insertion de l'historique V001 ; en contexte `Existing`, il validera strictement l'identité existante sans la recréer si elle a disparu. Un échec du binding fera donc rollback du SQL V001 et de son historique lors d'une première application, et une réouverture d'une base V001 incohérente échouera avant exposition du backend prêt.
|
`pre.003` branche le hook V001 réel : en contexte `AppliedNow`, il insère la singleton puis relit/valide `ksp_store_identity` avant insertion de l'historique V001 ; en contexte `Existing`, il relit/valide strictement l'identité sans jamais la recréer. Une absence, un nombre de lignes inattendu, une forme invalide ou un réseau différent sont classés `MigrationMismatch` avec phase statique et sans rendre le réseau stocké. L'atomicité PostgreSQL réelle de ce chemin sera prouvée dans `pre.009`.
|
||||||
|
|
||||||
## 18. Boundaries
|
## 18. Boundaries
|
||||||
|
|
||||||
@@ -520,9 +522,11 @@ cap 500/1000 dans Store pagination
|
|||||||
|
|
||||||
### `pre.003`
|
### `pre.003`
|
||||||
|
|
||||||
- V001 + tables/constraints/indexes ;
|
- V001 + tables/constraints/indexes : PASS statique ;
|
||||||
- binding réseau ;
|
- binding réseau hook `AppliedNow`/`Existing` : PASS statique ;
|
||||||
- checksum/inventory tests.
|
- checksum/inventory/bounds tests : PASS statique ;
|
||||||
|
- code `postgres_retention_compaction_unsupported` backend + façade : PASS ;
|
||||||
|
- preuve PostgreSQL réelle V001 : différée à `pre.009`.
|
||||||
|
|
||||||
### `pre.004`
|
### `pre.004`
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user