v0.3.3-pre.003
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
# file: Cargo.toml
|
||||
# version: 349
|
||||
# version: 350
|
||||
|
||||
[workspace]
|
||||
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"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.3.3-pre.2"
|
||||
version = "0.3.3-pre.3"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
repository = "https://git.sasedev.com/Sasedev/khadhroony-solana-project"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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.
|
||||
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");
|
||||
/// 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");
|
||||
/// 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.
|
||||
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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-lib/src/lib.rs
|
||||
// version: 6
|
||||
// version: 7
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![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;
|
||||
/// Error code used when a bounded PostgreSQL pool operation reaches its deadline.
|
||||
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.
|
||||
pub use self::error::ERROR_CODE_POSTGRES_SCHEMA_NEWER;
|
||||
/// Error code used when PostgreSQL verified TLS setup or negotiation fails.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-lib/tests/hardening_completeness.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![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_MISMATCH",
|
||||
"ERROR_CODE_POSTGRES_POOL_TIMEOUT",
|
||||
"ERROR_CODE_POSTGRES_RETENTION_COMPACTION_UNSUPPORTED",
|
||||
"ERROR_CODE_POSTGRES_SCHEMA_NEWER",
|
||||
"ERROR_CODE_POSTGRES_TLS_FAILED",
|
||||
"ERROR_CODE_RAW_CONFLICT",
|
||||
@@ -180,7 +181,7 @@ fn pre_009_facade_modules_and_crate_root_exports_are_exact() {
|
||||
];
|
||||
expected.sort_unstable();
|
||||
assert_eq!(actual.as_slice(), expected.as_slice());
|
||||
assert_eq!(actual.len(), 84);
|
||||
assert_eq!(actual.len(), 85);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-lib/tests/public_api.rs
|
||||
// version: 5
|
||||
// version: 6
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![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_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_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_MISMATCH.code(), "postgres_migration_mismatch");
|
||||
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
|
||||
// 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.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-postgres-lib/src/lib.rs
|
||||
// version: 5
|
||||
// version: 6
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
@@ -7,10 +7,11 @@
|
||||
|
||||
//! Official PostgreSQL backend implementation for KSP Store.
|
||||
//!
|
||||
//! `0.3.2-pre.007` owns the physical `tokio-postgres` connection, bounded
|
||||
//! Deadpool pool, explicit Rustls TLS policy, private KSP migration/bootstrap
|
||||
//! engine and safe lightweight health/readiness probe. Business persistence
|
||||
//! remains absent from this foundation release.
|
||||
//! The backend owns the physical `tokio-postgres` connection, bounded Deadpool
|
||||
//! pool, explicit Rustls TLS policy, private KSP migration/bootstrap engine and
|
||||
//! safe lightweight health/readiness probe. `0.3.3-pre.003` adds the immutable
|
||||
//! 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
|
||||
//! common facade consumes only this crate's narrow backend bridge and never
|
||||
@@ -22,6 +23,8 @@ mod health;
|
||||
mod migration;
|
||||
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.
|
||||
pub use self::error::PostgresBackendError;
|
||||
/// Safe backend-local error classification used by the common Store facade.
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
// file: crates/ksp-store-postgres-lib/src/migration.rs
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
use sha2::Digest; // rust-rules: trait-import
|
||||
|
||||
const ADVISORY_LOCK_KEY: i64 = 0x4b53_5053_544f_5245;
|
||||
const EMBEDDED_MIGRATIONS: &[EmbeddedMigration] = &[EmbeddedMigration {
|
||||
hook: MigrationHook::None,
|
||||
name: "bootstrap",
|
||||
sql: include_str!("../migrations/V000__bootstrap.sql"),
|
||||
version: 0,
|
||||
}];
|
||||
const EMBEDDED_MIGRATIONS: &[EmbeddedMigration] = &[
|
||||
EmbeddedMigration { hook: MigrationHook::None, name: "bootstrap", sql: include_str!("../migrations/V000__bootstrap.sql"), version: 0 },
|
||||
EmbeddedMigration {
|
||||
hook: MigrationHook::StoreIdentity,
|
||||
name: "raw_transaction",
|
||||
sql: include_str!("../migrations/V001__raw_transaction.sql"),
|
||||
version: 1,
|
||||
},
|
||||
];
|
||||
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_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 METADATA_EXISTS_SQL: &str = r#"SELECT EXISTS (
|
||||
SELECT 1 FROM information_schema.tables
|
||||
@@ -54,6 +59,7 @@ struct EmbeddedMigration {
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
enum MigrationHook {
|
||||
None,
|
||||
StoreIdentity,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
@@ -80,7 +86,11 @@ pub(crate) async fn bootstrap(
|
||||
if let std::result::Result::Err(error) = registry_result {
|
||||
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 {
|
||||
std::result::Result::Ok(result) => result,
|
||||
std::result::Result::Err(_) => {
|
||||
@@ -310,16 +320,86 @@ async fn run_applied_migration_hooks(
|
||||
}
|
||||
|
||||
async fn run_migration_hook(
|
||||
_transaction: &deadpool_postgres::Transaction<'_>,
|
||||
_network: &ksp_store_api::RawNetworkId,
|
||||
transaction: &deadpool_postgres::Transaction<'_>,
|
||||
network: &ksp_store_api::RawNetworkId,
|
||||
hook: MigrationHook,
|
||||
_context: MigrationHookContext,
|
||||
context: MigrationHookContext,
|
||||
) -> std::result::Result<(), crate::PostgresBackendError> {
|
||||
return match hook {
|
||||
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(
|
||||
transaction: &deadpool_postgres::Transaction<'_>,
|
||||
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"));
|
||||
},
|
||||
};
|
||||
const REQUIRED: [(&str, &str, &str); 4] =
|
||||
[("version", "bigint", "NO"), ("name", "text", "NO"), ("checksum", "text", "NO"), ("applied_at", "timestamp with time zone", "NO")];
|
||||
const REQUIRED: [(&str, &str, &str); 4] = [
|
||||
("version", "bigint", "NO"),
|
||||
("name", "text", "NO"),
|
||||
("checksum", "text", "NO"),
|
||||
("applied_at", "timestamp with time zone", "NO"),
|
||||
];
|
||||
let mut found = [false; REQUIRED.len()];
|
||||
for row in rows {
|
||||
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
|
||||
// version: 6
|
||||
// version: 7
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![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 bootstrap_sql = 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"));
|
||||
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 foundation SQL: {forbidden}");
|
||||
assert!(!bootstrap_sql.contains(forbidden), "business schema leaked into immutable V000 SQL: {forbidden}");
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -84,17 +84,28 @@ fn pre_007_health_probe_remains_foundation_only_and_private_sql() {
|
||||
}
|
||||
|
||||
#[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 v001 = include_str!("../migrations/V001__raw_transaction.sql");
|
||||
assert!(migration.contains("const EMBEDDED_MIGRATIONS: &[EmbeddedMigration]"));
|
||||
assert!(migration.contains("MigrationHook::None"));
|
||||
assert!(migration.contains("run_migration_hook(transaction, network, migration.hook, MigrationHookContext::AppliedNow).await"));
|
||||
assert!(migration.contains("network: &ksp_store_api::RawNetworkId"));
|
||||
assert!(migration.contains("MigrationHook::StoreIdentity"));
|
||||
assert!(migration.contains("MigrationHookContext::AppliedNow"));
|
||||
assert!(migration.contains("MigrationHookContext::Existing"));
|
||||
assert!(migration.contains("run_applied_migration_hooks(&transaction, network, next_index).await"));
|
||||
assert!(migration.contains("validate_history(history.as_slice(), EMBEDDED_MIGRATIONS)"));
|
||||
assert!(migration.contains("apply_pending_migrations(&transaction, network, next_index).await"));
|
||||
assert!(!migration.contains("V001__raw_transaction.sql"));
|
||||
assert!(!migration.contains("ksp_store_identity"));
|
||||
assert!(migration.contains("INSERT INTO ksp_store_identity (singleton, network) VALUES (1, $1)"));
|
||||
assert!(migration.contains("SELECT singleton, network FROM ksp_store_identity ORDER BY singleton LIMIT 2"));
|
||||
assert!(migration.contains("ksp_store_api::RawNetworkId::new(stored_network)"));
|
||||
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",
|
||||
] {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-postgres-lib/tests/hardening_completeness.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
@@ -115,6 +115,7 @@ fn pre_009_backend_modules_exports_and_manifest_dependencies_are_exact() {
|
||||
assert!(!crate_root.contains("pub mod "));
|
||||
let actual_exports = public_reexport_names(crate_root);
|
||||
let mut expected_exports = [
|
||||
"ERROR_CODE_POSTGRES_RETENTION_COMPACTION_UNSUPPORTED",
|
||||
"PostgresBackend",
|
||||
"PostgresBackendError",
|
||||
"PostgresBackendErrorKind",
|
||||
@@ -125,7 +126,7 @@ fn pre_009_backend_modules_exports_and_manifest_dependencies_are_exact() {
|
||||
];
|
||||
expected_exports.sort_unstable();
|
||||
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 actual_dependencies = manifest_dependency_names(manifest);
|
||||
let expected_dependencies = [
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-store-postgres-lib/tests/postgres_foundation_live.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![deny(unreachable_pub)]
|
||||
@@ -8,12 +8,30 @@
|
||||
//! Opt-in real PostgreSQL proof for the Store foundation runtime.
|
||||
//!
|
||||
//! The test reads one dedicated PostgreSQL URI from stdin, refuses to start
|
||||
//! when the KSP migration metadata table already exists, never prints the URI,
|
||||
//! creates no business table and cleans up only metadata it proved it created.
|
||||
//! when any KSP Store table managed by V000/V001 already exists, never prints
|
||||
//! 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_BROKEN_CHECKSUM_A: &str = "0000000000000000000000000000000000000000000000000000000000000000";
|
||||
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_METADATA_EXISTS_SQL: &str = r#"SELECT EXISTS (
|
||||
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_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_INSERT_SQL: &str =
|
||||
"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::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 {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
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 = 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"));
|
||||
}
|
||||
eprintln!("KSP Store PostgreSQL live proof: server major {major}");
|
||||
let mut owns_metadata = false;
|
||||
let scenario = run_foundation_scenario(&mut admin, uri, &mut owns_metadata).await;
|
||||
let cleanup = if owns_metadata { drop_metadata(&admin).await } else { std::result::Result::Ok(()) };
|
||||
let mut owns_schema = false;
|
||||
let scenario = run_foundation_scenario(&mut admin, uri, &mut owns_schema).await;
|
||||
let cleanup = if owns_schema { drop_managed_schema(&admin).await } else { std::result::Result::Ok(()) };
|
||||
if let std::result::Result::Err(error) = cleanup {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
if let std::result::Result::Err(error) = scenario {
|
||||
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 {
|
||||
std::result::Result::Ok(value) => value,
|
||||
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(());
|
||||
}
|
||||
|
||||
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 = match initial_result {
|
||||
std::result::Result::Ok(value) => value,
|
||||
@@ -134,9 +151,9 @@ async fn run_foundation_scenario(admin: &mut tokio_postgres::Client, uri: &str,
|
||||
if !created {
|
||||
return std::result::Result::Err(LiveFailure::new("initial_bootstrap_metadata"));
|
||||
}
|
||||
*owns_metadata = true;
|
||||
*owns_schema = true;
|
||||
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"));
|
||||
}
|
||||
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;
|
||||
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
|
||||
{
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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),
|
||||
};
|
||||
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 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);
|
||||
}
|
||||
|
||||
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> {
|
||||
let row_result = client.query_one(LIVE_METADATA_EXISTS_SQL, &[]).await;
|
||||
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> {
|
||||
return match client.batch_execute(LIVE_METADATA_DROP_SQL).await {
|
||||
async fn drop_managed_schema(client: &tokio_postgres::Client) -> std::result::Result<(), LiveFailure> {
|
||||
return match client.batch_execute(LIVE_MANAGED_SCHEMA_DROP_SQL).await {
|
||||
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
|
||||
// version: 3
|
||||
// version: 4
|
||||
|
||||
#![warn(missing_docs)]
|
||||
#![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;
|
||||
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
|
||||
// version: 2
|
||||
// version: 3
|
||||
|
||||
fn applied(version: i64, name: &str, checksum: &str) -> super::AppliedMigration {
|
||||
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]
|
||||
fn pre_002_embedded_registry_keeps_v000_immutable_and_current_version_registry_driven() {
|
||||
assert_eq!(super::EMBEDDED_MIGRATIONS.len(), 1);
|
||||
let migration = &super::EMBEDDED_MIGRATIONS[0];
|
||||
assert_eq!(migration.version, 0);
|
||||
assert_eq!(migration.name, "bootstrap");
|
||||
assert_eq!(migration.hook, super::MigrationHook::None);
|
||||
assert!(migration.sql.contains("CREATE TABLE ksp_store_schema_migrations"));
|
||||
fn pre_003_embedded_registry_keeps_v000_immutable_and_adds_exact_v001() {
|
||||
assert_eq!(super::EMBEDDED_MIGRATIONS.len(), 2);
|
||||
let v000 = &super::EMBEDDED_MIGRATIONS[0];
|
||||
assert_eq!(v000.version, 0);
|
||||
assert_eq!(v000.name, "bootstrap");
|
||||
assert_eq!(v000.hook, super::MigrationHook::None);
|
||||
assert!(v000.sql.contains("CREATE TABLE ksp_store_schema_migrations"));
|
||||
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_eq!(crate::current_migration_version(), 0);
|
||||
let checksum = super::migration_checksum(migration.sql);
|
||||
assert_eq!(checksum.len(), 64);
|
||||
assert_eq!(checksum, "d29068b8c13b9dc0cc9ef6aaadd0fa12d41e0fe4c56541a1118c4bfc846a1450");
|
||||
assert_eq!(crate::current_migration_version(), 1);
|
||||
return;
|
||||
}
|
||||
|
||||
#[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 v001 = embedded(1, "synthetic", "SELECT 1;");
|
||||
let registry = [v000, v001];
|
||||
assert!(super::validate_embedded_registry(®istry).is_ok());
|
||||
let v001 = super::EMBEDDED_MIGRATIONS[1];
|
||||
let v000_checksum = super::migration_checksum(v000.sql);
|
||||
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 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;
|
||||
}
|
||||
|
||||
@@ -58,10 +91,9 @@ fn pre_002_registry_rejects_empty_nonzero_gap_and_empty_metadata_entries() {
|
||||
}
|
||||
|
||||
#[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 v001 = embedded(1, "synthetic", "SELECT 1;");
|
||||
let registry = [v000, v001];
|
||||
let v001 = super::EMBEDDED_MIGRATIONS[1];
|
||||
let v000_checksum = super::migration_checksum(v000.sql);
|
||||
let v001_checksum = super::migration_checksum(v001.sql);
|
||||
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_v000 = [applied(1, v001.name, v001_checksum.as_str())];
|
||||
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));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
#[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 v001 = embedded(1, "synthetic", "SELECT 1;");
|
||||
let registry = [v000, v001];
|
||||
let v001 = super::EMBEDDED_MIGRATIONS[1];
|
||||
let v000_checksum = super::migration_checksum(v000.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 result = super::validate_history(&history, ®istry);
|
||||
let history = [
|
||||
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));
|
||||
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 -->
|
||||
<!-- version: 2 -->
|
||||
<!-- version: 3 -->
|
||||
|
||||
# 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
|
||||
```
|
||||
|
||||
Tranche technique courante :
|
||||
Tranches techniques validées :
|
||||
|
||||
```text
|
||||
0.3.3-pre.001 — audit, threat model, physical design, sizing et planning
|
||||
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é
|
||||
|
||||
@@ -1033,6 +1040,17 @@ Statut matérialisé en `pre.002` :
|
||||
- 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.
|
||||
|
||||
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
|
||||
|
||||
- codecs SQL privés ;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!-- 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
|
||||
|
||||
@@ -11,7 +11,7 @@ Cette validation accompagne :
|
||||
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
|
||||
|
||||
@@ -176,7 +176,7 @@ ksp_raw_transaction_observations
|
||||
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
|
||||
|
||||
@@ -355,7 +355,7 @@ Archived
|
||||
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
|
||||
|
||||
@@ -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 ;
|
||||
- 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]` ;
|
||||
- V000 seule + auto migrate -> V001 appliquée ;
|
||||
- V001 checksum divergent -> mismatch ;
|
||||
- binding réseau réel dans le hook ;
|
||||
- inventaire/checksum exact de V001.
|
||||
- registre réel `[V000, V001]` et version courante dérivée `1` ;
|
||||
- historique V000 seul reconnu comme préfixe exact avec V001 pending ;
|
||||
- historique V000+V001 exact reconnu comme complet ;
|
||||
- checksum V001 figé à `6fe57ed0313d2ed295280dd6e49f6d86695d4e4effee2724a25e36db1ea17761` ;
|
||||
- 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
|
||||
|
||||
`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
|
||||
|
||||
@@ -520,9 +522,11 @@ cap 500/1000 dans Store pagination
|
||||
|
||||
### `pre.003`
|
||||
|
||||
- V001 + tables/constraints/indexes ;
|
||||
- binding réseau ;
|
||||
- checksum/inventory tests.
|
||||
- V001 + tables/constraints/indexes : PASS statique ;
|
||||
- binding réseau hook `AppliedNow`/`Existing` : PASS statique ;
|
||||
- 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`
|
||||
|
||||
|
||||
Reference in New Issue
Block a user