v0.3.4-pre.003
This commit is contained in:
@@ -1,12 +1,12 @@
|
|||||||
# file: Cargo.toml
|
# file: Cargo.toml
|
||||||
# version: 366
|
# version: 367
|
||||||
|
|
||||||
[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.4-pre.2.fix.1"
|
version = "0.3.4-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"
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
DO $ksp$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_constraint
|
||||||
|
WHERE conname = 'ck_ksp_raw_account_states_pubkey'
|
||||||
|
AND conrelid = to_regclass('ksp_raw_account_states')
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE ksp_raw_account_states ADD CONSTRAINT ck_ksp_raw_account_states_pubkey CHECK (octet_length(pubkey) = 32);
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$ksp$;
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
DO $ksp$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_constraint
|
||||||
|
WHERE conname = 'ck_ksp_raw_account_states_slot'
|
||||||
|
AND conrelid = to_regclass('ksp_raw_account_states')
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE ksp_raw_account_states ADD CONSTRAINT ck_ksp_raw_account_states_slot CHECK (slot >= 0 AND slot <= 18446744073709551615);
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$ksp$;
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
DO $ksp$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_constraint
|
||||||
|
WHERE conname = 'ck_ksp_raw_account_states_state_hash'
|
||||||
|
AND conrelid = to_regclass('ksp_raw_account_states')
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE ksp_raw_account_states ADD CONSTRAINT ck_ksp_raw_account_states_state_hash CHECK (octet_length(state_hash) = 32);
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$ksp$;
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
DO $ksp$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_constraint
|
||||||
|
WHERE conname = 'ck_ksp_raw_account_states_lamports'
|
||||||
|
AND conrelid = to_regclass('ksp_raw_account_states')
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE ksp_raw_account_states ADD CONSTRAINT ck_ksp_raw_account_states_lamports CHECK (lamports >= 0 AND lamports <= 18446744073709551615);
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$ksp$;
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
DO $ksp$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_constraint
|
||||||
|
WHERE conname = 'ck_ksp_raw_account_states_owner'
|
||||||
|
AND conrelid = to_regclass('ksp_raw_account_states')
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE ksp_raw_account_states ADD CONSTRAINT ck_ksp_raw_account_states_owner CHECK (octet_length(owner) = 32);
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$ksp$;
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
DO $ksp$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_constraint
|
||||||
|
WHERE conname = 'ck_ksp_raw_account_states_rent_epoch'
|
||||||
|
AND conrelid = to_regclass('ksp_raw_account_states')
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE ksp_raw_account_states ADD CONSTRAINT ck_ksp_raw_account_states_rent_epoch CHECK (rent_epoch >= 0 AND rent_epoch <= 18446744073709551615);
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$ksp$;
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
DO $ksp$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_constraint
|
||||||
|
WHERE conname = 'ck_ksp_raw_account_states_data'
|
||||||
|
AND conrelid = to_regclass('ksp_raw_account_states')
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE ksp_raw_account_states ADD CONSTRAINT ck_ksp_raw_account_states_data CHECK (octet_length(data) <= 16777216);
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$ksp$;
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
DO $ksp$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_constraint
|
||||||
|
WHERE conname = 'ck_ksp_raw_account_observations_key'
|
||||||
|
AND conrelid = to_regclass('ksp_raw_account_observations')
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE ksp_raw_account_observations ADD CONSTRAINT ck_ksp_raw_account_observations_key CHECK (octet_length(observation_key) = 32);
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$ksp$;
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
DO $ksp$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_constraint
|
||||||
|
WHERE conname = 'ck_ksp_raw_account_observations_account_pubkey'
|
||||||
|
AND conrelid = to_regclass('ksp_raw_account_observations')
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE ksp_raw_account_observations ADD CONSTRAINT ck_ksp_raw_account_observations_account_pubkey CHECK (octet_length(account_pubkey) = 32);
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$ksp$;
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
DO $ksp$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_constraint
|
||||||
|
WHERE conname = 'ck_ksp_raw_account_observations_account_slot'
|
||||||
|
AND conrelid = to_regclass('ksp_raw_account_observations')
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE ksp_raw_account_observations ADD CONSTRAINT ck_ksp_raw_account_observations_account_slot CHECK (account_slot >= 0 AND account_slot <= 18446744073709551615);
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$ksp$;
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
DO $ksp$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_constraint
|
||||||
|
WHERE conname = 'ck_ksp_raw_account_observations_account_state_hash'
|
||||||
|
AND conrelid = to_regclass('ksp_raw_account_observations')
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE ksp_raw_account_observations ADD CONSTRAINT ck_ksp_raw_account_observations_account_state_hash CHECK (octet_length(account_state_hash) = 32);
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$ksp$;
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
DO $ksp$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_constraint
|
||||||
|
WHERE conname = 'ck_ksp_raw_account_observations_provider'
|
||||||
|
AND conrelid = to_regclass('ksp_raw_account_observations')
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE ksp_raw_account_observations ADD CONSTRAINT ck_ksp_raw_account_observations_provider CHECK (
|
||||||
|
octet_length(provider) >= 1 AND octet_length(provider) <= 128
|
||||||
|
AND provider ~ '^[A-Za-z0-9_.:-]+$'
|
||||||
|
);
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$ksp$;
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
DO $ksp$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_constraint
|
||||||
|
WHERE conname = 'ck_ksp_raw_account_observations_protocol'
|
||||||
|
AND conrelid = to_regclass('ksp_raw_account_observations')
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE ksp_raw_account_observations ADD CONSTRAINT ck_ksp_raw_account_observations_protocol CHECK (
|
||||||
|
octet_length(protocol) >= 1 AND octet_length(protocol) <= 128
|
||||||
|
AND protocol ~ '^[A-Za-z0-9_.:-]+$'
|
||||||
|
);
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$ksp$;
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
DO $ksp$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_constraint
|
||||||
|
WHERE conname = 'ck_ksp_raw_account_observations_method'
|
||||||
|
AND conrelid = to_regclass('ksp_raw_account_observations')
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE ksp_raw_account_observations ADD CONSTRAINT ck_ksp_raw_account_observations_method CHECK (
|
||||||
|
octet_length(acquisition_method) >= 1 AND octet_length(acquisition_method) <= 128
|
||||||
|
AND acquisition_method ~ '^[A-Za-z0-9_.:-]+$'
|
||||||
|
);
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$ksp$;
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
DO $ksp$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_constraint
|
||||||
|
WHERE conname = 'ck_ksp_raw_account_observations_origin'
|
||||||
|
AND conrelid = to_regclass('ksp_raw_account_observations')
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE ksp_raw_account_observations ADD CONSTRAINT ck_ksp_raw_account_observations_origin CHECK (origin = 'backfill' OR origin = 'import' OR origin = 'live' OR origin = 'repair' OR origin = 'replay');
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$ksp$;
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
DO $ksp$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_constraint
|
||||||
|
WHERE conname = 'ck_ksp_raw_account_observations_received_at'
|
||||||
|
AND conrelid = to_regclass('ksp_raw_account_observations')
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE ksp_raw_account_observations ADD CONSTRAINT ck_ksp_raw_account_observations_received_at CHECK (received_at_unix_millis >= 0 AND received_at_unix_millis <= 253402300799999);
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$ksp$;
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
DO $ksp$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_constraint
|
||||||
|
WHERE conname = 'ck_ksp_raw_account_observations_capture_session'
|
||||||
|
AND conrelid = to_regclass('ksp_raw_account_observations')
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE ksp_raw_account_observations ADD CONSTRAINT ck_ksp_raw_account_observations_capture_session CHECK (
|
||||||
|
capture_session_id IS NULL
|
||||||
|
OR (
|
||||||
|
octet_length(capture_session_id) >= 1 AND octet_length(capture_session_id) <= 128
|
||||||
|
AND capture_session_id ~ '^[A-Za-z0-9_.:-]+$'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$ksp$;
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
DO $ksp$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_constraint
|
||||||
|
WHERE conname = 'ck_ksp_raw_account_observations_commitment'
|
||||||
|
AND conrelid = to_regclass('ksp_raw_account_observations')
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE ksp_raw_account_observations ADD CONSTRAINT ck_ksp_raw_account_observations_commitment CHECK (
|
||||||
|
commitment IS NULL
|
||||||
|
OR (
|
||||||
|
octet_length(commitment) >= 1 AND octet_length(commitment) <= 128
|
||||||
|
AND commitment ~ '^[A-Za-z0-9_.:-]+$'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$ksp$;
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
DO $ksp$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_constraint
|
||||||
|
WHERE conname = 'ck_ksp_raw_account_observations_endpoint'
|
||||||
|
AND conrelid = to_regclass('ksp_raw_account_observations')
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE ksp_raw_account_observations ADD CONSTRAINT ck_ksp_raw_account_observations_endpoint CHECK (
|
||||||
|
endpoint_id IS NULL
|
||||||
|
OR (
|
||||||
|
octet_length(endpoint_id) >= 1 AND octet_length(endpoint_id) <= 128
|
||||||
|
AND endpoint_id ~ '^[A-Za-z0-9_.:-]+$'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$ksp$;
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
DO $ksp$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_constraint
|
||||||
|
WHERE conname = 'ck_ksp_raw_account_observations_filter'
|
||||||
|
AND conrelid = to_regclass('ksp_raw_account_observations')
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE ksp_raw_account_observations ADD CONSTRAINT ck_ksp_raw_account_observations_filter CHECK (
|
||||||
|
filter_id IS NULL
|
||||||
|
OR (
|
||||||
|
octet_length(filter_id) >= 1 AND octet_length(filter_id) <= 128
|
||||||
|
AND filter_id ~ '^[A-Za-z0-9_.:-]+$'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$ksp$;
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
DO $ksp$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_constraint
|
||||||
|
WHERE conname = 'ck_ksp_raw_account_observations_observed_at'
|
||||||
|
AND conrelid = to_regclass('ksp_raw_account_observations')
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE ksp_raw_account_observations ADD CONSTRAINT ck_ksp_raw_account_observations_observed_at CHECK (
|
||||||
|
observed_at_unix_millis IS NULL
|
||||||
|
OR observed_at_unix_millis >= 0 AND observed_at_unix_millis <= 253402300799999
|
||||||
|
);
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$ksp$;
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
DO $ksp$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_constraint
|
||||||
|
WHERE conname = 'ck_ksp_raw_account_observations_time_order'
|
||||||
|
AND conrelid = to_regclass('ksp_raw_account_observations')
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE ksp_raw_account_observations ADD CONSTRAINT ck_ksp_raw_account_observations_time_order CHECK (observed_at_unix_millis IS NULL OR observed_at_unix_millis <= received_at_unix_millis);
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$ksp$;
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
DO $ksp$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_constraint
|
||||||
|
WHERE conname = 'ck_ksp_raw_account_observations_source_hash'
|
||||||
|
AND conrelid = to_regclass('ksp_raw_account_observations')
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE ksp_raw_account_observations ADD CONSTRAINT ck_ksp_raw_account_observations_source_hash CHECK (
|
||||||
|
source_payload_hash IS NULL
|
||||||
|
OR octet_length(source_payload_hash) = 32
|
||||||
|
);
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$ksp$;
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
DO $ksp$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_constraint
|
||||||
|
WHERE conname = 'ck_ksp_raw_account_observations_source_size'
|
||||||
|
AND conrelid = to_regclass('ksp_raw_account_observations')
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE ksp_raw_account_observations ADD CONSTRAINT ck_ksp_raw_account_observations_source_size CHECK (
|
||||||
|
source_payload_size_bytes IS NULL
|
||||||
|
OR source_payload_size_bytes >= 0 AND source_payload_size_bytes <= 67108864
|
||||||
|
);
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$ksp$;
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
DO $ksp$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_constraint
|
||||||
|
WHERE conname = 'ck_ksp_raw_account_observations_transaction_signature'
|
||||||
|
AND conrelid = to_regclass('ksp_raw_account_observations')
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE ksp_raw_account_observations ADD CONSTRAINT ck_ksp_raw_account_observations_transaction_signature CHECK (
|
||||||
|
transaction_signature IS NULL
|
||||||
|
OR octet_length(transaction_signature) = 64
|
||||||
|
);
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$ksp$;
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
DO $ksp$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_constraint
|
||||||
|
WHERE conname = 'ck_ksp_raw_account_observations_write_version'
|
||||||
|
AND conrelid = to_regclass('ksp_raw_account_observations')
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE ksp_raw_account_observations ADD CONSTRAINT ck_ksp_raw_account_observations_write_version CHECK (
|
||||||
|
write_version IS NULL
|
||||||
|
OR write_version >= 0 AND write_version <= 18446744073709551615
|
||||||
|
);
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$ksp$;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
CREATE INDEX IF NOT EXISTS ix_ksp_raw_account_states_slot_pubkey_state_hash
|
||||||
|
ON ksp_raw_account_states (slot, pubkey, state_hash);
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-store-postgres-lib/src/lib.rs
|
// file: crates/ksp-store-postgres-lib/src/lib.rs
|
||||||
// version: 15
|
// version: 16
|
||||||
|
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
#![deny(unreachable_pub)]
|
#![deny(unreachable_pub)]
|
||||||
@@ -23,6 +23,9 @@
|
|||||||
//! `PostgresBackend` while preserving the existing narrow backend bridge.
|
//! `PostgresBackend` while preserving the existing narrow backend bridge.
|
||||||
//! `0.3.4-pre.002` registers additive V002 and its two minimal RAW account
|
//! `0.3.4-pre.002` registers additive V002 and its two minimal RAW account
|
||||||
//! tables with canonical state/observation PKs and the observation-state FK.
|
//! tables with canonical state/observation PKs and the observation-state FK.
|
||||||
|
//! `0.3.4-pre.003` completes V002 with exact physical bounds, one unfiltered
|
||||||
|
//! navigation index, external-schema compatibility and the bounded prerelease
|
||||||
|
//! checksum transition from the provisional `pre.002` schema.
|
||||||
//!
|
//!
|
||||||
//! 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
|
||||||
@@ -99,9 +102,11 @@ pub(crate) use self::schema::V001_RESOURCES;
|
|||||||
pub(crate) use self::schema::V002_RESOURCES;
|
pub(crate) use self::schema::V002_RESOURCES;
|
||||||
/// Private physical schema resource inspector consumed by the migration engine.
|
/// Private physical schema resource inspector consumed by the migration engine.
|
||||||
pub(crate) use self::schema::inspect_resource;
|
pub(crate) use self::schema::inspect_resource;
|
||||||
/// Private V001 adoption probe consumed by the migration engine.
|
/// Private managed-schema adoption probe consumed by the migration engine.
|
||||||
pub(crate) use self::schema::managed_v001_objects_exist;
|
pub(crate) use self::schema::managed_schema_objects_exist;
|
||||||
/// Private external-schema compatibility gate consumed by the migration engine.
|
/// Private V001 external-schema compatibility gate consumed by the migration engine.
|
||||||
pub(crate) use self::schema::verify_v001_external_compatibility;
|
pub(crate) use self::schema::verify_v001_external_compatibility;
|
||||||
|
/// Private V002 external-schema compatibility gate consumed by the migration engine.
|
||||||
|
pub(crate) use self::schema::verify_v002_external_compatibility;
|
||||||
|
|
||||||
const _: &str = crate::TRACING_TARGET;
|
const _: &str = crate::TRACING_TARGET;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-store-postgres-lib/src/migration.rs
|
// file: crates/ksp-store-postgres-lib/src/migration.rs
|
||||||
// version: 8
|
// version: 9
|
||||||
|
|
||||||
use sha2::Digest; // rust-rules: trait-import
|
use sha2::Digest; // rust-rules: trait-import
|
||||||
|
|
||||||
@@ -9,6 +9,7 @@ const EMBEDDED_MIGRATIONS: &[EmbeddedMigration] = &[
|
|||||||
checksum: MigrationChecksum::LegacySql(include_str!("../migrations/v000_bootstrap/tables/001_ksp_store_schema_migrations.sql")),
|
checksum: MigrationChecksum::LegacySql(include_str!("../migrations/v000_bootstrap/tables/001_ksp_store_schema_migrations.sql")),
|
||||||
hook: MigrationHook::None,
|
hook: MigrationHook::None,
|
||||||
name: "bootstrap",
|
name: "bootstrap",
|
||||||
|
previous_checksums: &[],
|
||||||
resources: crate::V000_RESOURCES,
|
resources: crate::V000_RESOURCES,
|
||||||
version: 0,
|
version: 0,
|
||||||
},
|
},
|
||||||
@@ -16,6 +17,7 @@ const EMBEDDED_MIGRATIONS: &[EmbeddedMigration] = &[
|
|||||||
checksum: MigrationChecksum::Resources,
|
checksum: MigrationChecksum::Resources,
|
||||||
hook: MigrationHook::StoreIdentity,
|
hook: MigrationHook::StoreIdentity,
|
||||||
name: "raw_transaction",
|
name: "raw_transaction",
|
||||||
|
previous_checksums: &[],
|
||||||
resources: crate::V001_RESOURCES,
|
resources: crate::V001_RESOURCES,
|
||||||
version: 1,
|
version: 1,
|
||||||
},
|
},
|
||||||
@@ -23,12 +25,14 @@ const EMBEDDED_MIGRATIONS: &[EmbeddedMigration] = &[
|
|||||||
checksum: MigrationChecksum::Resources,
|
checksum: MigrationChecksum::Resources,
|
||||||
hook: MigrationHook::None,
|
hook: MigrationHook::None,
|
||||||
name: "raw_account_state",
|
name: "raw_account_state",
|
||||||
|
previous_checksums: &[V002_PROVISIONAL_CHECKSUM_PRE_002],
|
||||||
resources: crate::V002_RESOURCES,
|
resources: crate::V002_RESOURCES,
|
||||||
version: 2,
|
version: 2,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
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_UPDATE_CHECKSUM_SQL: &str = "UPDATE ksp_store_schema_migrations SET checksum = $1 WHERE version = $2 AND name = $3 AND checksum = $4";
|
||||||
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_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 IDENTITY_LOAD_SQL: &str = "SELECT singleton, network FROM ksp_store_identity ORDER BY singleton LIMIT 2";
|
||||||
@@ -40,6 +44,7 @@ const METADATA_EXISTS_SQL: &str = r#"SELECT EXISTS (
|
|||||||
AND table_type = 'BASE TABLE'
|
AND table_type = 'BASE TABLE'
|
||||||
)"#;
|
)"#;
|
||||||
const SET_STATEMENT_TIMEOUT_SQL: &str = "SELECT set_config('statement_timeout', $1, true)";
|
const SET_STATEMENT_TIMEOUT_SQL: &str = "SELECT set_config('statement_timeout', $1, true)";
|
||||||
|
const V002_PROVISIONAL_CHECKSUM_PRE_002: &str = "30ac87496f1bb3805d816660891d7eab2127c599a636eb40c17ade5926311f55";
|
||||||
|
|
||||||
struct AppliedMigration {
|
struct AppliedMigration {
|
||||||
checksum: std::string::String,
|
checksum: std::string::String,
|
||||||
@@ -58,6 +63,7 @@ struct EmbeddedMigration {
|
|||||||
checksum: MigrationChecksum,
|
checksum: MigrationChecksum,
|
||||||
hook: MigrationHook,
|
hook: MigrationHook,
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
|
previous_checksums: &'static [&'static str],
|
||||||
resources: &'static [crate::SchemaResource],
|
resources: &'static [crate::SchemaResource],
|
||||||
version: i64,
|
version: i64,
|
||||||
}
|
}
|
||||||
@@ -140,7 +146,7 @@ async fn bootstrap_inner(
|
|||||||
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 (next_index, mutation_mode) = if metadata_exists {
|
let (next_index, mutation_mode, applied_history) = if metadata_exists {
|
||||||
let metadata_result = crate::inspect_resource(&transaction, &crate::V000_RESOURCES[0]).await;
|
let metadata_result = crate::inspect_resource(&transaction, &crate::V000_RESOURCES[0]).await;
|
||||||
match metadata_result {
|
match metadata_result {
|
||||||
std::result::Result::Ok(crate::SchemaResourceState::Compatible) => {},
|
std::result::Result::Ok(crate::SchemaResourceState::Compatible) => {},
|
||||||
@@ -155,18 +161,18 @@ async fn bootstrap_inner(
|
|||||||
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 validation_result = validate_history(history.as_slice(), EMBEDDED_MIGRATIONS);
|
let validation_result = validate_history(history.as_slice(), EMBEDDED_MIGRATIONS, schema_autoupdate);
|
||||||
let index = match validation_result {
|
let index = match validation_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),
|
||||||
};
|
};
|
||||||
(index, SchemaMutationMode::Update)
|
(index, SchemaMutationMode::Update, std::option::Option::Some(history))
|
||||||
} else {
|
} else {
|
||||||
if !schema_autocreate {
|
if !schema_autocreate {
|
||||||
log_schema_block("schema_autocreate_disabled");
|
log_schema_block("schema_autocreate_disabled");
|
||||||
return std::result::Result::Err(crate::PostgresBackendError::new(crate::PostgresBackendErrorKind::MigrationFailed, "schema_autocreate_disabled"));
|
return std::result::Result::Err(crate::PostgresBackendError::new(crate::PostgresBackendErrorKind::MigrationFailed, "schema_autocreate_disabled"));
|
||||||
}
|
}
|
||||||
let managed_result = crate::managed_v001_objects_exist(&transaction).await;
|
let managed_result = crate::managed_schema_objects_exist(&transaction).await;
|
||||||
let managed_objects_exist = match managed_result {
|
let managed_objects_exist = match managed_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),
|
||||||
@@ -175,12 +181,18 @@ async fn bootstrap_inner(
|
|||||||
log_schema_block("schema_adoption_disabled");
|
log_schema_block("schema_adoption_disabled");
|
||||||
return std::result::Result::Err(crate::PostgresBackendError::new(crate::PostgresBackendErrorKind::MigrationFailed, "schema_adoption_disabled"));
|
return std::result::Result::Err(crate::PostgresBackendError::new(crate::PostgresBackendErrorKind::MigrationFailed, "schema_adoption_disabled"));
|
||||||
}
|
}
|
||||||
(0, SchemaMutationMode::Create)
|
(0, SchemaMutationMode::Create, std::option::Option::None)
|
||||||
};
|
};
|
||||||
let existing_schema_result = verify_or_repair_applied_migrations(&transaction, next_index, schema_autoupdate).await;
|
let existing_schema_result = verify_or_repair_applied_migrations(&transaction, next_index, schema_autoupdate).await;
|
||||||
if let std::result::Result::Err(error) = existing_schema_result {
|
if let std::result::Result::Err(error) = existing_schema_result {
|
||||||
return std::result::Result::Err(error);
|
return std::result::Result::Err(error);
|
||||||
}
|
}
|
||||||
|
if let std::option::Option::Some(history) = applied_history.as_deref() {
|
||||||
|
let checksum_result = reconcile_applied_history_checksums(&transaction, history, next_index, schema_autoupdate).await;
|
||||||
|
if let std::result::Result::Err(error) = checksum_result {
|
||||||
|
return std::result::Result::Err(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
let existing_hook_result = run_applied_migration_hooks(&transaction, network, next_index).await;
|
let existing_hook_result = run_applied_migration_hooks(&transaction, network, next_index).await;
|
||||||
if let std::result::Result::Err(error) = existing_hook_result {
|
if let std::result::Result::Err(error) = existing_hook_result {
|
||||||
return std::result::Result::Err(error);
|
return std::result::Result::Err(error);
|
||||||
@@ -528,12 +540,14 @@ async fn set_statement_timeout(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn verify_migration_contract(transaction: &deadpool_postgres::Transaction<'_>, version: i64) -> std::result::Result<(), crate::PostgresBackendError> {
|
async fn verify_migration_contract(transaction: &deadpool_postgres::Transaction<'_>, version: i64) -> std::result::Result<(), crate::PostgresBackendError> {
|
||||||
if version == 1 {
|
let result = match version {
|
||||||
let result = crate::verify_v001_external_compatibility(transaction).await;
|
1 => crate::verify_v001_external_compatibility(transaction).await,
|
||||||
if let std::result::Result::Err(error) = result {
|
2 => crate::verify_v002_external_compatibility(transaction).await,
|
||||||
log_schema_block(error.phase());
|
_ => std::result::Result::Ok(()),
|
||||||
return std::result::Result::Err(error);
|
};
|
||||||
}
|
if let std::result::Result::Err(error) = result {
|
||||||
|
log_schema_block(error.phase());
|
||||||
|
return std::result::Result::Err(error);
|
||||||
}
|
}
|
||||||
return std::result::Result::Ok(());
|
return std::result::Result::Ok(());
|
||||||
}
|
}
|
||||||
@@ -582,6 +596,41 @@ async fn verify_or_repair_applied_migrations(
|
|||||||
return std::result::Result::Ok(());
|
return std::result::Result::Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn reconcile_applied_history_checksums(
|
||||||
|
transaction: &deadpool_postgres::Transaction<'_>,
|
||||||
|
history: &[AppliedMigration],
|
||||||
|
applied_count: usize,
|
||||||
|
schema_autoupdate: bool,
|
||||||
|
) -> std::result::Result<(), crate::PostgresBackendError> {
|
||||||
|
if !schema_autoupdate {
|
||||||
|
return std::result::Result::Ok(());
|
||||||
|
}
|
||||||
|
for (index, applied) in history.iter().take(applied_count).enumerate() {
|
||||||
|
let migration = &EMBEDDED_MIGRATIONS[index];
|
||||||
|
let expected_checksum = migration_checksum(migration);
|
||||||
|
if applied.checksum == expected_checksum {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if !migration.previous_checksums.contains(&applied.checksum.as_str()) {
|
||||||
|
return std::result::Result::Err(crate::PostgresBackendError::new(crate::PostgresBackendErrorKind::MigrationMismatch, "history_diverged"));
|
||||||
|
}
|
||||||
|
let result = transaction.execute(HISTORY_UPDATE_CHECKSUM_SQL, &[&expected_checksum, &migration.version, &migration.name, &applied.checksum]).await;
|
||||||
|
match result {
|
||||||
|
std::result::Result::Ok(1) => {
|
||||||
|
ksp_logging_lib::warn!(
|
||||||
|
target: crate::TRACING_TARGET,
|
||||||
|
migration_version = migration.version,
|
||||||
|
"upgraded known prerelease PostgreSQL Store migration checksum after schema reconciliation"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
std::result::Result::Ok(_) | std::result::Result::Err(_) => {
|
||||||
|
return std::result::Result::Err(crate::PostgresBackendError::new(crate::PostgresBackendErrorKind::MigrationFailed, "history_checksum_update"));
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return std::result::Result::Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
fn schema_autoupdate_disabled_error() -> crate::PostgresBackendError {
|
fn schema_autoupdate_disabled_error() -> crate::PostgresBackendError {
|
||||||
return crate::PostgresBackendError::new(crate::PostgresBackendErrorKind::MigrationMismatch, "schema_autoupdate_disabled");
|
return crate::PostgresBackendError::new(crate::PostgresBackendErrorKind::MigrationMismatch, "schema_autoupdate_disabled");
|
||||||
}
|
}
|
||||||
@@ -615,7 +664,11 @@ fn validate_embedded_registry(migrations: &[EmbeddedMigration]) -> std::result::
|
|||||||
return std::result::Result::Ok(());
|
return std::result::Result::Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_history(history: &[AppliedMigration], migrations: &[EmbeddedMigration]) -> std::result::Result<usize, crate::PostgresBackendError> {
|
fn validate_history(
|
||||||
|
history: &[AppliedMigration],
|
||||||
|
migrations: &[EmbeddedMigration],
|
||||||
|
allow_previous_checksums: bool,
|
||||||
|
) -> std::result::Result<usize, crate::PostgresBackendError> {
|
||||||
if history.is_empty() {
|
if history.is_empty() {
|
||||||
return std::result::Result::Err(crate::PostgresBackendError::new(crate::PostgresBackendErrorKind::MigrationMismatch, "history_missing"));
|
return std::result::Result::Err(crate::PostgresBackendError::new(crate::PostgresBackendErrorKind::MigrationMismatch, "history_missing"));
|
||||||
}
|
}
|
||||||
@@ -630,7 +683,8 @@ fn validate_history(history: &[AppliedMigration], migrations: &[EmbeddedMigratio
|
|||||||
}
|
}
|
||||||
let expected = &migrations[index];
|
let expected = &migrations[index];
|
||||||
let expected_checksum = migration_checksum(expected);
|
let expected_checksum = migration_checksum(expected);
|
||||||
if applied.version != expected.version || applied.name != expected.name || applied.checksum != expected_checksum {
|
let previous_checksum_matches = allow_previous_checksums && expected.previous_checksums.contains(&applied.checksum.as_str());
|
||||||
|
if applied.version != expected.version || applied.name != expected.name || (applied.checksum != expected_checksum && !previous_checksum_matches) {
|
||||||
return std::result::Result::Err(crate::PostgresBackendError::new(crate::PostgresBackendErrorKind::MigrationMismatch, "history_diverged"));
|
return std::result::Result::Err(crate::PostgresBackendError::new(crate::PostgresBackendErrorKind::MigrationMismatch, "history_diverged"));
|
||||||
}
|
}
|
||||||
index += 1;
|
index += 1;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-store-postgres-lib/src/schema.rs
|
// file: crates/ksp-store-postgres-lib/src/schema.rs
|
||||||
// version: 8
|
// version: 9
|
||||||
|
|
||||||
/// Immutable V000 physical schema resource inventory.
|
/// Immutable V000 physical schema resource inventory.
|
||||||
pub(crate) const V000_RESOURCES: &[SchemaResource] = &[SchemaResource {
|
pub(crate) const V000_RESOURCES: &[SchemaResource] = &[SchemaResource {
|
||||||
@@ -370,7 +370,7 @@ pub(crate) const V001_RESOURCES: &[SchemaResource] = &[
|
|||||||
access_method: "btree",
|
access_method: "btree",
|
||||||
key_fragment: "slot,signature",
|
key_fragment: "slot,signature",
|
||||||
name: "ix_ksp_raw_transactions_slot_signature",
|
name: "ix_ksp_raw_transactions_slot_signature",
|
||||||
predicate_fragment: "retention_state<>'purged'",
|
predicate_fragment: std::option::Option::Some("retention_state<>'purged'"),
|
||||||
table: "ksp_raw_transactions",
|
table: "ksp_raw_transactions",
|
||||||
unique: false,
|
unique: false,
|
||||||
}),
|
}),
|
||||||
@@ -378,7 +378,7 @@ pub(crate) const V001_RESOURCES: &[SchemaResource] = &[
|
|||||||
sql: include_str!("../migrations/v001_raw_transaction/indexes/001_ix_ksp_raw_transactions_slot_signature.sql"),
|
sql: include_str!("../migrations/v001_raw_transaction/indexes/001_ix_ksp_raw_transactions_slot_signature.sql"),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
/// V002 physical schema resource inventory for the minimal RAW account state foundation.
|
/// Final V002 physical schema resource inventory for RAW account state persistence.
|
||||||
pub(crate) const V002_RESOURCES: &[SchemaResource] = &[
|
pub(crate) const V002_RESOURCES: &[SchemaResource] = &[
|
||||||
SchemaResource {
|
SchemaResource {
|
||||||
id: "tables/001_ksp_raw_account_states.sql",
|
id: "tables/001_ksp_raw_account_states.sql",
|
||||||
@@ -426,6 +426,259 @@ pub(crate) const V002_RESOURCES: &[SchemaResource] = &[
|
|||||||
repair_existing: true,
|
repair_existing: true,
|
||||||
sql: include_str!("../migrations/v002_raw_account_state/constraints/003_fk_ksp_raw_account_observations_state.sql"),
|
sql: include_str!("../migrations/v002_raw_account_state/constraints/003_fk_ksp_raw_account_observations_state.sql"),
|
||||||
},
|
},
|
||||||
|
SchemaResource {
|
||||||
|
id: "constraints/004_ck_ksp_raw_account_states_pubkey.sql",
|
||||||
|
object: SchemaObjectContract::Constraint(ConstraintContract { kind: "c", name: "ck_ksp_raw_account_states_pubkey", table: "ksp_raw_account_states" }),
|
||||||
|
repair_existing: true,
|
||||||
|
sql: include_str!("../migrations/v002_raw_account_state/constraints/004_ck_ksp_raw_account_states_pubkey.sql"),
|
||||||
|
},
|
||||||
|
SchemaResource {
|
||||||
|
id: "constraints/005_ck_ksp_raw_account_states_slot.sql",
|
||||||
|
object: SchemaObjectContract::Constraint(ConstraintContract { kind: "c", name: "ck_ksp_raw_account_states_slot", table: "ksp_raw_account_states" }),
|
||||||
|
repair_existing: true,
|
||||||
|
sql: include_str!("../migrations/v002_raw_account_state/constraints/005_ck_ksp_raw_account_states_slot.sql"),
|
||||||
|
},
|
||||||
|
SchemaResource {
|
||||||
|
id: "constraints/006_ck_ksp_raw_account_states_state_hash.sql",
|
||||||
|
object: SchemaObjectContract::Constraint(ConstraintContract {
|
||||||
|
kind: "c",
|
||||||
|
name: "ck_ksp_raw_account_states_state_hash",
|
||||||
|
table: "ksp_raw_account_states",
|
||||||
|
}),
|
||||||
|
repair_existing: true,
|
||||||
|
sql: include_str!("../migrations/v002_raw_account_state/constraints/006_ck_ksp_raw_account_states_state_hash.sql"),
|
||||||
|
},
|
||||||
|
SchemaResource {
|
||||||
|
id: "constraints/007_ck_ksp_raw_account_states_lamports.sql",
|
||||||
|
object: SchemaObjectContract::Constraint(ConstraintContract { kind: "c", name: "ck_ksp_raw_account_states_lamports", table: "ksp_raw_account_states" }),
|
||||||
|
repair_existing: true,
|
||||||
|
sql: include_str!("../migrations/v002_raw_account_state/constraints/007_ck_ksp_raw_account_states_lamports.sql"),
|
||||||
|
},
|
||||||
|
SchemaResource {
|
||||||
|
id: "constraints/008_ck_ksp_raw_account_states_owner.sql",
|
||||||
|
object: SchemaObjectContract::Constraint(ConstraintContract { kind: "c", name: "ck_ksp_raw_account_states_owner", table: "ksp_raw_account_states" }),
|
||||||
|
repair_existing: true,
|
||||||
|
sql: include_str!("../migrations/v002_raw_account_state/constraints/008_ck_ksp_raw_account_states_owner.sql"),
|
||||||
|
},
|
||||||
|
SchemaResource {
|
||||||
|
id: "constraints/009_ck_ksp_raw_account_states_rent_epoch.sql",
|
||||||
|
object: SchemaObjectContract::Constraint(ConstraintContract {
|
||||||
|
kind: "c",
|
||||||
|
name: "ck_ksp_raw_account_states_rent_epoch",
|
||||||
|
table: "ksp_raw_account_states",
|
||||||
|
}),
|
||||||
|
repair_existing: true,
|
||||||
|
sql: include_str!("../migrations/v002_raw_account_state/constraints/009_ck_ksp_raw_account_states_rent_epoch.sql"),
|
||||||
|
},
|
||||||
|
SchemaResource {
|
||||||
|
id: "constraints/010_ck_ksp_raw_account_states_data.sql",
|
||||||
|
object: SchemaObjectContract::Constraint(ConstraintContract { kind: "c", name: "ck_ksp_raw_account_states_data", table: "ksp_raw_account_states" }),
|
||||||
|
repair_existing: true,
|
||||||
|
sql: include_str!("../migrations/v002_raw_account_state/constraints/010_ck_ksp_raw_account_states_data.sql"),
|
||||||
|
},
|
||||||
|
SchemaResource {
|
||||||
|
id: "constraints/011_ck_ksp_raw_account_observations_key.sql",
|
||||||
|
object: SchemaObjectContract::Constraint(ConstraintContract {
|
||||||
|
kind: "c",
|
||||||
|
name: "ck_ksp_raw_account_observations_key",
|
||||||
|
table: "ksp_raw_account_observations",
|
||||||
|
}),
|
||||||
|
repair_existing: true,
|
||||||
|
sql: include_str!("../migrations/v002_raw_account_state/constraints/011_ck_ksp_raw_account_observations_key.sql"),
|
||||||
|
},
|
||||||
|
SchemaResource {
|
||||||
|
id: "constraints/012_ck_ksp_raw_account_observations_account_pubkey.sql",
|
||||||
|
object: SchemaObjectContract::Constraint(ConstraintContract {
|
||||||
|
kind: "c",
|
||||||
|
name: "ck_ksp_raw_account_observations_account_pubkey",
|
||||||
|
table: "ksp_raw_account_observations",
|
||||||
|
}),
|
||||||
|
repair_existing: true,
|
||||||
|
sql: include_str!("../migrations/v002_raw_account_state/constraints/012_ck_ksp_raw_account_observations_account_pubkey.sql"),
|
||||||
|
},
|
||||||
|
SchemaResource {
|
||||||
|
id: "constraints/013_ck_ksp_raw_account_observations_account_slot.sql",
|
||||||
|
object: SchemaObjectContract::Constraint(ConstraintContract {
|
||||||
|
kind: "c",
|
||||||
|
name: "ck_ksp_raw_account_observations_account_slot",
|
||||||
|
table: "ksp_raw_account_observations",
|
||||||
|
}),
|
||||||
|
repair_existing: true,
|
||||||
|
sql: include_str!("../migrations/v002_raw_account_state/constraints/013_ck_ksp_raw_account_observations_account_slot.sql"),
|
||||||
|
},
|
||||||
|
SchemaResource {
|
||||||
|
id: "constraints/014_ck_ksp_raw_account_observations_account_state_hash.sql",
|
||||||
|
object: SchemaObjectContract::Constraint(ConstraintContract {
|
||||||
|
kind: "c",
|
||||||
|
name: "ck_ksp_raw_account_observations_account_state_hash",
|
||||||
|
table: "ksp_raw_account_observations",
|
||||||
|
}),
|
||||||
|
repair_existing: true,
|
||||||
|
sql: include_str!("../migrations/v002_raw_account_state/constraints/014_ck_ksp_raw_account_observations_account_state_hash.sql"),
|
||||||
|
},
|
||||||
|
SchemaResource {
|
||||||
|
id: "constraints/015_ck_ksp_raw_account_observations_provider.sql",
|
||||||
|
object: SchemaObjectContract::Constraint(ConstraintContract {
|
||||||
|
kind: "c",
|
||||||
|
name: "ck_ksp_raw_account_observations_provider",
|
||||||
|
table: "ksp_raw_account_observations",
|
||||||
|
}),
|
||||||
|
repair_existing: true,
|
||||||
|
sql: include_str!("../migrations/v002_raw_account_state/constraints/015_ck_ksp_raw_account_observations_provider.sql"),
|
||||||
|
},
|
||||||
|
SchemaResource {
|
||||||
|
id: "constraints/016_ck_ksp_raw_account_observations_protocol.sql",
|
||||||
|
object: SchemaObjectContract::Constraint(ConstraintContract {
|
||||||
|
kind: "c",
|
||||||
|
name: "ck_ksp_raw_account_observations_protocol",
|
||||||
|
table: "ksp_raw_account_observations",
|
||||||
|
}),
|
||||||
|
repair_existing: true,
|
||||||
|
sql: include_str!("../migrations/v002_raw_account_state/constraints/016_ck_ksp_raw_account_observations_protocol.sql"),
|
||||||
|
},
|
||||||
|
SchemaResource {
|
||||||
|
id: "constraints/017_ck_ksp_raw_account_observations_method.sql",
|
||||||
|
object: SchemaObjectContract::Constraint(ConstraintContract {
|
||||||
|
kind: "c",
|
||||||
|
name: "ck_ksp_raw_account_observations_method",
|
||||||
|
table: "ksp_raw_account_observations",
|
||||||
|
}),
|
||||||
|
repair_existing: true,
|
||||||
|
sql: include_str!("../migrations/v002_raw_account_state/constraints/017_ck_ksp_raw_account_observations_method.sql"),
|
||||||
|
},
|
||||||
|
SchemaResource {
|
||||||
|
id: "constraints/018_ck_ksp_raw_account_observations_origin.sql",
|
||||||
|
object: SchemaObjectContract::Constraint(ConstraintContract {
|
||||||
|
kind: "c",
|
||||||
|
name: "ck_ksp_raw_account_observations_origin",
|
||||||
|
table: "ksp_raw_account_observations",
|
||||||
|
}),
|
||||||
|
repair_existing: true,
|
||||||
|
sql: include_str!("../migrations/v002_raw_account_state/constraints/018_ck_ksp_raw_account_observations_origin.sql"),
|
||||||
|
},
|
||||||
|
SchemaResource {
|
||||||
|
id: "constraints/019_ck_ksp_raw_account_observations_received_at.sql",
|
||||||
|
object: SchemaObjectContract::Constraint(ConstraintContract {
|
||||||
|
kind: "c",
|
||||||
|
name: "ck_ksp_raw_account_observations_received_at",
|
||||||
|
table: "ksp_raw_account_observations",
|
||||||
|
}),
|
||||||
|
repair_existing: true,
|
||||||
|
sql: include_str!("../migrations/v002_raw_account_state/constraints/019_ck_ksp_raw_account_observations_received_at.sql"),
|
||||||
|
},
|
||||||
|
SchemaResource {
|
||||||
|
id: "constraints/020_ck_ksp_raw_account_observations_capture_session.sql",
|
||||||
|
object: SchemaObjectContract::Constraint(ConstraintContract {
|
||||||
|
kind: "c",
|
||||||
|
name: "ck_ksp_raw_account_observations_capture_session",
|
||||||
|
table: "ksp_raw_account_observations",
|
||||||
|
}),
|
||||||
|
repair_existing: true,
|
||||||
|
sql: include_str!("../migrations/v002_raw_account_state/constraints/020_ck_ksp_raw_account_observations_capture_session.sql"),
|
||||||
|
},
|
||||||
|
SchemaResource {
|
||||||
|
id: "constraints/021_ck_ksp_raw_account_observations_commitment.sql",
|
||||||
|
object: SchemaObjectContract::Constraint(ConstraintContract {
|
||||||
|
kind: "c",
|
||||||
|
name: "ck_ksp_raw_account_observations_commitment",
|
||||||
|
table: "ksp_raw_account_observations",
|
||||||
|
}),
|
||||||
|
repair_existing: true,
|
||||||
|
sql: include_str!("../migrations/v002_raw_account_state/constraints/021_ck_ksp_raw_account_observations_commitment.sql"),
|
||||||
|
},
|
||||||
|
SchemaResource {
|
||||||
|
id: "constraints/022_ck_ksp_raw_account_observations_endpoint.sql",
|
||||||
|
object: SchemaObjectContract::Constraint(ConstraintContract {
|
||||||
|
kind: "c",
|
||||||
|
name: "ck_ksp_raw_account_observations_endpoint",
|
||||||
|
table: "ksp_raw_account_observations",
|
||||||
|
}),
|
||||||
|
repair_existing: true,
|
||||||
|
sql: include_str!("../migrations/v002_raw_account_state/constraints/022_ck_ksp_raw_account_observations_endpoint.sql"),
|
||||||
|
},
|
||||||
|
SchemaResource {
|
||||||
|
id: "constraints/023_ck_ksp_raw_account_observations_filter.sql",
|
||||||
|
object: SchemaObjectContract::Constraint(ConstraintContract {
|
||||||
|
kind: "c",
|
||||||
|
name: "ck_ksp_raw_account_observations_filter",
|
||||||
|
table: "ksp_raw_account_observations",
|
||||||
|
}),
|
||||||
|
repair_existing: true,
|
||||||
|
sql: include_str!("../migrations/v002_raw_account_state/constraints/023_ck_ksp_raw_account_observations_filter.sql"),
|
||||||
|
},
|
||||||
|
SchemaResource {
|
||||||
|
id: "constraints/024_ck_ksp_raw_account_observations_observed_at.sql",
|
||||||
|
object: SchemaObjectContract::Constraint(ConstraintContract {
|
||||||
|
kind: "c",
|
||||||
|
name: "ck_ksp_raw_account_observations_observed_at",
|
||||||
|
table: "ksp_raw_account_observations",
|
||||||
|
}),
|
||||||
|
repair_existing: true,
|
||||||
|
sql: include_str!("../migrations/v002_raw_account_state/constraints/024_ck_ksp_raw_account_observations_observed_at.sql"),
|
||||||
|
},
|
||||||
|
SchemaResource {
|
||||||
|
id: "constraints/025_ck_ksp_raw_account_observations_time_order.sql",
|
||||||
|
object: SchemaObjectContract::Constraint(ConstraintContract {
|
||||||
|
kind: "c",
|
||||||
|
name: "ck_ksp_raw_account_observations_time_order",
|
||||||
|
table: "ksp_raw_account_observations",
|
||||||
|
}),
|
||||||
|
repair_existing: true,
|
||||||
|
sql: include_str!("../migrations/v002_raw_account_state/constraints/025_ck_ksp_raw_account_observations_time_order.sql"),
|
||||||
|
},
|
||||||
|
SchemaResource {
|
||||||
|
id: "constraints/026_ck_ksp_raw_account_observations_source_hash.sql",
|
||||||
|
object: SchemaObjectContract::Constraint(ConstraintContract {
|
||||||
|
kind: "c",
|
||||||
|
name: "ck_ksp_raw_account_observations_source_hash",
|
||||||
|
table: "ksp_raw_account_observations",
|
||||||
|
}),
|
||||||
|
repair_existing: true,
|
||||||
|
sql: include_str!("../migrations/v002_raw_account_state/constraints/026_ck_ksp_raw_account_observations_source_hash.sql"),
|
||||||
|
},
|
||||||
|
SchemaResource {
|
||||||
|
id: "constraints/027_ck_ksp_raw_account_observations_source_size.sql",
|
||||||
|
object: SchemaObjectContract::Constraint(ConstraintContract {
|
||||||
|
kind: "c",
|
||||||
|
name: "ck_ksp_raw_account_observations_source_size",
|
||||||
|
table: "ksp_raw_account_observations",
|
||||||
|
}),
|
||||||
|
repair_existing: true,
|
||||||
|
sql: include_str!("../migrations/v002_raw_account_state/constraints/027_ck_ksp_raw_account_observations_source_size.sql"),
|
||||||
|
},
|
||||||
|
SchemaResource {
|
||||||
|
id: "constraints/028_ck_ksp_raw_account_observations_transaction_signature.sql",
|
||||||
|
object: SchemaObjectContract::Constraint(ConstraintContract {
|
||||||
|
kind: "c",
|
||||||
|
name: "ck_ksp_raw_account_observations_transaction_signature",
|
||||||
|
table: "ksp_raw_account_observations",
|
||||||
|
}),
|
||||||
|
repair_existing: true,
|
||||||
|
sql: include_str!("../migrations/v002_raw_account_state/constraints/028_ck_ksp_raw_account_observations_transaction_signature.sql"),
|
||||||
|
},
|
||||||
|
SchemaResource {
|
||||||
|
id: "constraints/029_ck_ksp_raw_account_observations_write_version.sql",
|
||||||
|
object: SchemaObjectContract::Constraint(ConstraintContract {
|
||||||
|
kind: "c",
|
||||||
|
name: "ck_ksp_raw_account_observations_write_version",
|
||||||
|
table: "ksp_raw_account_observations",
|
||||||
|
}),
|
||||||
|
repair_existing: true,
|
||||||
|
sql: include_str!("../migrations/v002_raw_account_state/constraints/029_ck_ksp_raw_account_observations_write_version.sql"),
|
||||||
|
},
|
||||||
|
SchemaResource {
|
||||||
|
id: "indexes/001_ix_ksp_raw_account_states_slot_pubkey_state_hash.sql",
|
||||||
|
object: SchemaObjectContract::Index(IndexContract {
|
||||||
|
access_method: "btree",
|
||||||
|
key_fragment: "slot,pubkey,state_hash",
|
||||||
|
name: "ix_ksp_raw_account_states_slot_pubkey_state_hash",
|
||||||
|
predicate_fragment: std::option::Option::None,
|
||||||
|
table: "ksp_raw_account_states",
|
||||||
|
unique: false,
|
||||||
|
}),
|
||||||
|
repair_existing: true,
|
||||||
|
sql: include_str!("../migrations/v002_raw_account_state/indexes/001_ix_ksp_raw_account_states_slot_pubkey_state_hash.sql"),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const COLUMN_LOAD_SQL: &str = r#"SELECT column_name::TEXT, udt_name::TEXT, (is_nullable = 'YES') AS nullable, numeric_precision::INTEGER, numeric_scale::INTEGER, column_default::TEXT, is_identity::TEXT, is_generated::TEXT
|
const COLUMN_LOAD_SQL: &str = r#"SELECT column_name::TEXT, udt_name::TEXT, (is_nullable = 'YES') AS nullable, numeric_precision::INTEGER, numeric_scale::INTEGER, column_default::TEXT, is_identity::TEXT, is_generated::TEXT
|
||||||
@@ -453,7 +706,7 @@ WHERE ns.nspname = current_schema() AND table_rel.relname = $1 AND index_rel.rel
|
|||||||
const MANAGED_OBJECT_EXISTS_SQL: &str = r#"SELECT EXISTS (
|
const MANAGED_OBJECT_EXISTS_SQL: &str = r#"SELECT EXISTS (
|
||||||
SELECT 1 FROM information_schema.tables
|
SELECT 1 FROM information_schema.tables
|
||||||
WHERE table_schema = current_schema()
|
WHERE table_schema = current_schema()
|
||||||
AND table_name IN ('ksp_store_identity', 'ksp_raw_transactions', 'ksp_raw_transaction_observations', 'ksp_raw_transaction_archive_payloads')
|
AND table_name IN ('ksp_store_identity', 'ksp_raw_transactions', 'ksp_raw_transaction_observations', 'ksp_raw_transaction_archive_payloads', 'ksp_raw_account_states', 'ksp_raw_account_observations')
|
||||||
AND table_type = 'BASE TABLE'
|
AND table_type = 'BASE TABLE'
|
||||||
)"#;
|
)"#;
|
||||||
const PRIMARY_KEY_LOAD_SQL: &str = r#"SELECT string_agg(att.attname::TEXT, ',' ORDER BY key_part.ord)::TEXT
|
const PRIMARY_KEY_LOAD_SQL: &str = r#"SELECT string_agg(att.attname::TEXT, ',' ORDER BY key_part.ord)::TEXT
|
||||||
@@ -513,7 +766,7 @@ struct IndexContract {
|
|||||||
access_method: &'static str,
|
access_method: &'static str,
|
||||||
key_fragment: &'static str,
|
key_fragment: &'static str,
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
predicate_fragment: &'static str,
|
predicate_fragment: std::option::Option<&'static str>,
|
||||||
table: &'static str,
|
table: &'static str,
|
||||||
unique: bool,
|
unique: bool,
|
||||||
}
|
}
|
||||||
@@ -969,6 +1222,7 @@ const RAW_TRANSACTION_ARCHIVE_PAYLOADS_COLUMNS: &[ColumnContract] = &[
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
const V001_TABLE_NAMES: &[&str] = &["ksp_store_identity", "ksp_raw_transactions", "ksp_raw_transaction_observations", "ksp_raw_transaction_archive_payloads"];
|
const V001_TABLE_NAMES: &[&str] = &["ksp_store_identity", "ksp_raw_transactions", "ksp_raw_transaction_observations", "ksp_raw_transaction_archive_payloads"];
|
||||||
|
const V002_TABLE_NAMES: &[&str] = &["ksp_raw_account_states", "ksp_raw_account_observations"];
|
||||||
|
|
||||||
/// Inspects one embedded schema resource against the effective PostgreSQL catalog.
|
/// Inspects one embedded schema resource against the effective PostgreSQL catalog.
|
||||||
pub(crate) async fn inspect_resource(
|
pub(crate) async fn inspect_resource(
|
||||||
@@ -982,8 +1236,8 @@ pub(crate) async fn inspect_resource(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns whether any V001-managed base table already exists in the active schema.
|
/// Returns whether any KSP-managed Store base table already exists in the active schema.
|
||||||
pub(crate) async fn managed_v001_objects_exist(transaction: &deadpool_postgres::Transaction<'_>) -> std::result::Result<bool, crate::PostgresBackendError> {
|
pub(crate) async fn managed_schema_objects_exist(transaction: &deadpool_postgres::Transaction<'_>) -> std::result::Result<bool, crate::PostgresBackendError> {
|
||||||
let result = transaction.query_one(MANAGED_OBJECT_EXISTS_SQL, &[]).await;
|
let result = transaction.query_one(MANAGED_OBJECT_EXISTS_SQL, &[]).await;
|
||||||
let row = match result {
|
let row = match result {
|
||||||
std::result::Result::Ok(value) => value,
|
std::result::Result::Ok(value) => value,
|
||||||
@@ -999,7 +1253,22 @@ pub(crate) async fn managed_v001_objects_exist(transaction: &deadpool_postgres::
|
|||||||
pub(crate) async fn verify_v001_external_compatibility(
|
pub(crate) async fn verify_v001_external_compatibility(
|
||||||
transaction: &deadpool_postgres::Transaction<'_>,
|
transaction: &deadpool_postgres::Transaction<'_>,
|
||||||
) -> std::result::Result<(), crate::PostgresBackendError> {
|
) -> std::result::Result<(), crate::PostgresBackendError> {
|
||||||
for table in V001_TABLE_NAMES {
|
return verify_external_compatibility(transaction, V001_TABLE_NAMES, V001_RESOURCES).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Rejects external V002 schema extensions that can constrain or mutate KSP writes.
|
||||||
|
pub(crate) async fn verify_v002_external_compatibility(
|
||||||
|
transaction: &deadpool_postgres::Transaction<'_>,
|
||||||
|
) -> std::result::Result<(), crate::PostgresBackendError> {
|
||||||
|
return verify_external_compatibility(transaction, V002_TABLE_NAMES, V002_RESOURCES).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn verify_external_compatibility(
|
||||||
|
transaction: &deadpool_postgres::Transaction<'_>,
|
||||||
|
tables: &[&str],
|
||||||
|
resources: &[SchemaResource],
|
||||||
|
) -> std::result::Result<(), crate::PostgresBackendError> {
|
||||||
|
for table in tables {
|
||||||
let table = *table;
|
let table = *table;
|
||||||
let constraint_rows = transaction.query(UNEXPECTED_CONSTRAINTS_SQL, &[&table]).await;
|
let constraint_rows = transaction.query(UNEXPECTED_CONSTRAINTS_SQL, &[&table]).await;
|
||||||
let constraint_rows = match constraint_rows {
|
let constraint_rows = match constraint_rows {
|
||||||
@@ -1024,11 +1293,11 @@ pub(crate) async fn verify_v001_external_compatibility(
|
|||||||
) => (name, kind, validated, deferrable, deferred, definition),
|
) => (name, kind, validated, deferrable, deferred, definition),
|
||||||
_ => return schema_query_error("schema_constraint_inventory_decode"),
|
_ => return schema_query_error("schema_constraint_inventory_decode"),
|
||||||
};
|
};
|
||||||
if is_expected_constraint(table, name.as_str()) {
|
if is_expected_constraint(resources, table, name.as_str()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
let definition = normalize_catalog_sql(definition.as_str());
|
let definition = normalize_catalog_sql(definition.as_str());
|
||||||
if !validated || deferrable || deferred || !matches_expected_constraint_definition(table, kind.as_str(), definition.as_str()) {
|
if !validated || deferrable || deferred || !matches_expected_constraint_definition(resources, table, kind.as_str(), definition.as_str()) {
|
||||||
return schema_incompatible("schema_external_constraint");
|
return schema_incompatible("schema_external_constraint");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1129,9 +1398,10 @@ async fn inspect_index(
|
|||||||
};
|
};
|
||||||
let definition = normalize_catalog_sql(definition.as_str());
|
let definition = normalize_catalog_sql(definition.as_str());
|
||||||
let predicate = predicate.map(|value| return normalize_catalog_sql(value.as_str()));
|
let predicate = predicate.map(|value| return normalize_catalog_sql(value.as_str()));
|
||||||
let predicate_matches = match predicate.as_deref() {
|
let predicate_matches = match (contract.predicate_fragment, predicate.as_deref()) {
|
||||||
std::option::Option::Some(value) => value.contains(contract.predicate_fragment),
|
(std::option::Option::Some(expected), std::option::Option::Some(value)) => value.contains(expected),
|
||||||
std::option::Option::None => false,
|
(std::option::Option::None, std::option::Option::None) => true,
|
||||||
|
(std::option::Option::Some(_), std::option::Option::None) | (std::option::Option::None, std::option::Option::Some(_)) => false,
|
||||||
};
|
};
|
||||||
if unique != contract.unique || access_method != contract.access_method || !definition.contains(contract.key_fragment) || !predicate_matches {
|
if unique != contract.unique || access_method != contract.access_method || !definition.contains(contract.key_fragment) || !predicate_matches {
|
||||||
return std::result::Result::Ok(SchemaResourceState::Incompatible);
|
return std::result::Result::Ok(SchemaResourceState::Incompatible);
|
||||||
@@ -1347,8 +1617,8 @@ fn expected_constraint_definition(resource_sql: &str, name: &str) -> std::option
|
|||||||
return std::option::Option::Some(normalize_catalog_sql(definition));
|
return std::option::Option::Some(normalize_catalog_sql(definition));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_expected_constraint(table: &str, name: &str) -> bool {
|
fn is_expected_constraint(resources: &[SchemaResource], table: &str, name: &str) -> bool {
|
||||||
for resource in V001_RESOURCES {
|
for resource in resources {
|
||||||
let contract = match resource.object {
|
let contract = match resource.object {
|
||||||
SchemaObjectContract::Constraint(value) => value,
|
SchemaObjectContract::Constraint(value) => value,
|
||||||
SchemaObjectContract::Index(_) | SchemaObjectContract::Table(_) => continue,
|
SchemaObjectContract::Index(_) | SchemaObjectContract::Table(_) => continue,
|
||||||
@@ -1360,8 +1630,8 @@ fn is_expected_constraint(table: &str, name: &str) -> bool {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn matches_expected_constraint_definition(table: &str, kind: &str, definition: &str) -> bool {
|
fn matches_expected_constraint_definition(resources: &[SchemaResource], table: &str, kind: &str, definition: &str) -> bool {
|
||||||
for resource in V001_RESOURCES {
|
for resource in resources {
|
||||||
let contract = match resource.object {
|
let contract = match resource.object {
|
||||||
SchemaObjectContract::Constraint(value) => value,
|
SchemaObjectContract::Constraint(value) => value,
|
||||||
SchemaObjectContract::Index(_) | SchemaObjectContract::Table(_) => continue,
|
SchemaObjectContract::Index(_) | SchemaObjectContract::Table(_) => continue,
|
||||||
|
|||||||
@@ -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: 15
|
// version: 16
|
||||||
|
|
||||||
#![warn(missing_docs)]
|
#![warn(missing_docs)]
|
||||||
#![deny(unreachable_pub)]
|
#![deny(unreachable_pub)]
|
||||||
@@ -126,18 +126,28 @@ fn pre_003_fix_001_migration_engine_uses_split_schema_contract_and_binds_network
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pre_002_v002_is_migration_only_and_does_not_open_account_repository_or_runtime_dispatch() {
|
fn pre_003_v002_schema_is_complete_but_still_does_not_open_account_repository_or_runtime_dispatch() {
|
||||||
let crate_root = include_str!("../src/lib.rs");
|
let crate_root = include_str!("../src/lib.rs");
|
||||||
let migration = include_str!("../src/migration.rs");
|
let migration = include_str!("../src/migration.rs");
|
||||||
let schema = include_str!("../src/schema.rs");
|
let schema = include_str!("../src/schema.rs");
|
||||||
let runtime = include_str!("../src/runtime.rs");
|
let runtime = include_str!("../src/runtime.rs");
|
||||||
let states = include_str!("../migrations/v002_raw_account_state/tables/001_ksp_raw_account_states.sql");
|
let states = include_str!("../migrations/v002_raw_account_state/tables/001_ksp_raw_account_states.sql");
|
||||||
let observations = include_str!("../migrations/v002_raw_account_state/tables/002_ksp_raw_account_observations.sql");
|
let observations = include_str!("../migrations/v002_raw_account_state/tables/002_ksp_raw_account_observations.sql");
|
||||||
|
let slot_check = include_str!("../migrations/v002_raw_account_state/constraints/005_ck_ksp_raw_account_states_slot.sql");
|
||||||
|
let index = include_str!("../migrations/v002_raw_account_state/indexes/001_ix_ksp_raw_account_states_slot_pubkey_state_hash.sql");
|
||||||
assert!(migration.contains("name: \"raw_account_state\""));
|
assert!(migration.contains("name: \"raw_account_state\""));
|
||||||
assert!(migration.contains("resources: crate::V002_RESOURCES"));
|
assert!(migration.contains("resources: crate::V002_RESOURCES"));
|
||||||
|
assert!(migration.contains("V002_PROVISIONAL_CHECKSUM_PRE_002"));
|
||||||
|
assert!(migration.contains("HISTORY_UPDATE_CHECKSUM_SQL"));
|
||||||
|
assert!(migration.contains("reconcile_applied_history_checksums"));
|
||||||
assert!(schema.contains("pub(crate) const V002_RESOURCES"));
|
assert!(schema.contains("pub(crate) const V002_RESOURCES"));
|
||||||
|
assert!(schema.contains("verify_v002_external_compatibility"));
|
||||||
|
assert!(schema.contains("ksp_raw_account_states', 'ksp_raw_account_observations"));
|
||||||
assert!(states.contains("ksp_raw_account_states"));
|
assert!(states.contains("ksp_raw_account_states"));
|
||||||
assert!(observations.contains("ksp_raw_account_observations"));
|
assert!(observations.contains("ksp_raw_account_observations"));
|
||||||
|
assert!(slot_check.contains("slot >= 0 AND slot <= 18446744073709551615"));
|
||||||
|
assert!(index.contains("ON ksp_raw_account_states (slot, pubkey, state_hash)"));
|
||||||
|
assert!(!index.contains("WHERE"));
|
||||||
assert!(!crate_root.contains("mod raw_account;"));
|
assert!(!crate_root.contains("mod raw_account;"));
|
||||||
assert!(!runtime.contains("impl ksp_store_api::RawAccount"));
|
assert!(!runtime.contains("impl ksp_store_api::RawAccount"));
|
||||||
assert!(!std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src/raw_account.rs").exists());
|
assert!(!std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src/raw_account.rs").exists());
|
||||||
|
|||||||
@@ -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: 7
|
// version: 8
|
||||||
|
|
||||||
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,6 +10,7 @@ fn embedded(version: i64, name: &'static str, resources: &'static [crate::Schema
|
|||||||
checksum: super::MigrationChecksum::Resources,
|
checksum: super::MigrationChecksum::Resources,
|
||||||
hook: super::MigrationHook::None,
|
hook: super::MigrationHook::None,
|
||||||
name,
|
name,
|
||||||
|
previous_checksums: &[],
|
||||||
resources,
|
resources,
|
||||||
version,
|
version,
|
||||||
};
|
};
|
||||||
@@ -36,7 +37,7 @@ fn pre_003_fix_001_embedded_registry_keeps_v000_checksum_and_uses_resource_owned
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pre_002_v002_registry_is_additive_minimal_and_keeps_v000_v001_checksums_stable() {
|
fn pre_003_v002_registry_is_complete_and_keeps_v000_v001_checksums_stable() {
|
||||||
assert_eq!(super::EMBEDDED_MIGRATIONS.len(), 3);
|
assert_eq!(super::EMBEDDED_MIGRATIONS.len(), 3);
|
||||||
let v000 = &super::EMBEDDED_MIGRATIONS[0];
|
let v000 = &super::EMBEDDED_MIGRATIONS[0];
|
||||||
let v001 = &super::EMBEDDED_MIGRATIONS[1];
|
let v001 = &super::EMBEDDED_MIGRATIONS[1];
|
||||||
@@ -46,7 +47,9 @@ fn pre_002_v002_registry_is_additive_minimal_and_keeps_v000_v001_checksums_stabl
|
|||||||
assert_eq!(v002.version, 2);
|
assert_eq!(v002.version, 2);
|
||||||
assert_eq!(v002.name, "raw_account_state");
|
assert_eq!(v002.name, "raw_account_state");
|
||||||
assert_eq!(v002.hook, super::MigrationHook::None);
|
assert_eq!(v002.hook, super::MigrationHook::None);
|
||||||
assert_eq!(v002.resources.len(), 5);
|
assert_eq!(v002.previous_checksums, &[super::V002_PROVISIONAL_CHECKSUM_PRE_002]);
|
||||||
|
assert_eq!(v002.resources.len(), 32);
|
||||||
|
assert_eq!(super::migration_checksum(v002), "ff21605ed45f7ab4c0f92bbb692700b4118a9488b04d50a31d259ac59bdb550e");
|
||||||
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(), 2);
|
assert_eq!(crate::current_migration_version(), 2);
|
||||||
let full = [
|
let full = [
|
||||||
@@ -54,7 +57,30 @@ fn pre_002_v002_registry_is_additive_minimal_and_keeps_v000_v001_checksums_stabl
|
|||||||
applied(1, v001.name, super::migration_checksum(v001).as_str()),
|
applied(1, v001.name, super::migration_checksum(v001).as_str()),
|
||||||
applied(2, v002.name, super::migration_checksum(v002).as_str()),
|
applied(2, v002.name, super::migration_checksum(v002).as_str()),
|
||||||
];
|
];
|
||||||
assert_eq!(super::validate_history(&full, super::EMBEDDED_MIGRATIONS).ok(), std::option::Option::Some(3));
|
assert_eq!(super::validate_history(&full, super::EMBEDDED_MIGRATIONS, false).ok(), std::option::Option::Some(3));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pre_003_v002_provisional_checksum_requires_schema_autoupdate_and_rejects_unknown_history() {
|
||||||
|
let v000 = &super::EMBEDDED_MIGRATIONS[0];
|
||||||
|
let v001 = &super::EMBEDDED_MIGRATIONS[1];
|
||||||
|
let v002 = &super::EMBEDDED_MIGRATIONS[2];
|
||||||
|
let provisional = [
|
||||||
|
applied(0, v000.name, super::migration_checksum(v000).as_str()),
|
||||||
|
applied(1, v001.name, super::migration_checksum(v001).as_str()),
|
||||||
|
applied(2, v002.name, super::V002_PROVISIONAL_CHECKSUM_PRE_002),
|
||||||
|
];
|
||||||
|
let strict = super::validate_history(&provisional, super::EMBEDDED_MIGRATIONS, false);
|
||||||
|
assert_eq!(strict.err().map(|value| return value.kind()), std::option::Option::Some(crate::PostgresBackendErrorKind::MigrationMismatch));
|
||||||
|
assert_eq!(super::validate_history(&provisional, super::EMBEDDED_MIGRATIONS, true).ok(), std::option::Option::Some(3));
|
||||||
|
let unknown = [
|
||||||
|
applied(0, v000.name, super::migration_checksum(v000).as_str()),
|
||||||
|
applied(1, v001.name, super::migration_checksum(v001).as_str()),
|
||||||
|
applied(2, v002.name, "unknown-v002-checksum"),
|
||||||
|
];
|
||||||
|
let unknown_result = super::validate_history(&unknown, super::EMBEDDED_MIGRATIONS, true);
|
||||||
|
assert_eq!(unknown_result.err().map(|value| return value.kind()), std::option::Option::Some(crate::PostgresBackendErrorKind::MigrationMismatch));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,10 +131,10 @@ fn pre_003_fix_001_ordered_registry_accepts_v000_prefix_and_full_v001_history()
|
|||||||
let v001 = super::EMBEDDED_MIGRATIONS[1];
|
let v001 = super::EMBEDDED_MIGRATIONS[1];
|
||||||
let v000_checksum = super::migration_checksum(&v000);
|
let v000_checksum = super::migration_checksum(&v000);
|
||||||
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, super::EMBEDDED_MIGRATIONS).ok(), std::option::Option::Some(1));
|
assert_eq!(super::validate_history(&prefix, super::EMBEDDED_MIGRATIONS, false).ok(), std::option::Option::Some(1));
|
||||||
let v001_checksum = super::migration_checksum(&v001);
|
let v001_checksum = super::migration_checksum(&v001);
|
||||||
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, super::EMBEDDED_MIGRATIONS).ok(), std::option::Option::Some(2));
|
assert_eq!(super::validate_history(&full, super::EMBEDDED_MIGRATIONS, false).ok(), std::option::Option::Some(2));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,7 +163,7 @@ fn pre_003_fix_001_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, super::EMBEDDED_MIGRATIONS);
|
let result = super::validate_history(history, super::EMBEDDED_MIGRATIONS, false);
|
||||||
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;
|
||||||
@@ -157,7 +183,7 @@ fn pre_003_fix_001_newer_history_is_rejected_without_down_migration() {
|
|||||||
applied(2, v002.name, v002_checksum.as_str()),
|
applied(2, v002.name, v002_checksum.as_str()),
|
||||||
applied(3, "future", "future-checksum"),
|
applied(3, "future", "future-checksum"),
|
||||||
];
|
];
|
||||||
let result = super::validate_history(&history, super::EMBEDDED_MIGRATIONS);
|
let result = super::validate_history(&history, super::EMBEDDED_MIGRATIONS, false);
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// file: crates/ksp-store-postgres-lib/unit_tests/schema.rs
|
// file: crates/ksp-store-postgres-lib/unit_tests/schema.rs
|
||||||
// version: 6
|
// version: 7
|
||||||
|
|
||||||
fn actual_column(name: &str, udt_name: &str, nullable: bool) -> super::ActualColumn {
|
fn actual_column(name: &str, udt_name: &str, nullable: bool) -> super::ActualColumn {
|
||||||
return super::ActualColumn {
|
return super::ActualColumn {
|
||||||
@@ -49,34 +49,96 @@ fn pre_003_fix_001_v001_resources_are_split_and_idempotent_by_object_family() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pre_002_v002_resources_are_exactly_two_tables_plus_base_pk_fk_without_indexes_or_domain_checks() {
|
fn pre_003_v002_resources_are_complete_bounded_and_have_one_unfiltered_navigation_index() {
|
||||||
assert_eq!(crate::V002_RESOURCES.len(), 5);
|
assert_eq!(crate::V002_RESOURCES.len(), 32);
|
||||||
let ids = crate::V002_RESOURCES.iter().map(|resource| return resource.id).collect::<std::vec::Vec<_>>();
|
let ids = crate::V002_RESOURCES.iter().map(|resource| return resource.id).collect::<std::vec::Vec<_>>();
|
||||||
assert_eq!(ids[..2], ["tables/001_ksp_raw_account_states.sql", "tables/002_ksp_raw_account_observations.sql"]);
|
assert_eq!(ids.iter().filter(|id| return id.starts_with("tables/")).count(), 2);
|
||||||
assert_eq!(
|
assert_eq!(ids.iter().filter(|id| return id.starts_with("constraints/")).count(), 29);
|
||||||
ids[2..],
|
assert_eq!(ids.iter().filter(|id| return id.starts_with("indexes/")).count(), 1);
|
||||||
[
|
assert!(ids[..2].iter().all(|id| return id.starts_with("tables/")));
|
||||||
"constraints/001_pk_ksp_raw_account_states.sql",
|
assert!(ids[2..31].iter().all(|id| return id.starts_with("constraints/")));
|
||||||
"constraints/002_pk_ksp_raw_account_observations.sql",
|
assert_eq!(ids[31], "indexes/001_ix_ksp_raw_account_states_slot_pubkey_state_hash.sql");
|
||||||
"constraints/003_fk_ksp_raw_account_observations_state.sql",
|
let mut unique = std::collections::BTreeSet::<&str>::new();
|
||||||
]
|
for id in &ids {
|
||||||
);
|
assert!(unique.insert(id), "duplicate V002 resource: {id}");
|
||||||
|
}
|
||||||
let sql = crate::V002_RESOURCES.iter().map(|resource| return resource.sql).collect::<std::vec::Vec<_>>().concat();
|
let sql = crate::V002_RESOURCES.iter().map(|resource| return resource.sql).collect::<std::vec::Vec<_>>().concat();
|
||||||
let normalized_sql = sql.split_ascii_whitespace().collect::<std::vec::Vec<_>>().join(" ");
|
let normalized_sql = sql.split_ascii_whitespace().collect::<std::vec::Vec<_>>().join(" ");
|
||||||
for required in [
|
for required in [
|
||||||
"CREATE TABLE IF NOT EXISTS ksp_raw_account_states",
|
"CREATE TABLE IF NOT EXISTS ksp_raw_account_states",
|
||||||
"CREATE TABLE IF NOT EXISTS ksp_raw_account_observations",
|
"CREATE TABLE IF NOT EXISTS ksp_raw_account_observations",
|
||||||
"slot NUMERIC(20, 0) NOT NULL",
|
"slot NUMERIC(20, 0) NOT NULL",
|
||||||
|
"lamports NUMERIC(20, 0) NOT NULL",
|
||||||
|
"rent_epoch NUMERIC(20, 0) NOT NULL",
|
||||||
"write_version NUMERIC(20, 0) NULL",
|
"write_version NUMERIC(20, 0) NULL",
|
||||||
"PRIMARY KEY (pubkey, slot, state_hash)",
|
"PRIMARY KEY (pubkey, slot, state_hash)",
|
||||||
"PRIMARY KEY (observation_key)",
|
"PRIMARY KEY (observation_key)",
|
||||||
"FOREIGN KEY (account_pubkey, account_slot, account_state_hash) REFERENCES ksp_raw_account_states(pubkey, slot, state_hash) ON DELETE RESTRICT",
|
"FOREIGN KEY (account_pubkey, account_slot, account_state_hash) REFERENCES ksp_raw_account_states(pubkey, slot, state_hash) ON DELETE RESTRICT",
|
||||||
|
"octet_length(pubkey) = 32",
|
||||||
|
"slot >= 0 AND slot <= 18446744073709551615",
|
||||||
|
"octet_length(state_hash) = 32",
|
||||||
|
"lamports >= 0 AND lamports <= 18446744073709551615",
|
||||||
|
"octet_length(owner) = 32",
|
||||||
|
"rent_epoch >= 0 AND rent_epoch <= 18446744073709551615",
|
||||||
|
"octet_length(data) <= 16777216",
|
||||||
|
"octet_length(observation_key) = 32",
|
||||||
|
"octet_length(account_pubkey) = 32",
|
||||||
|
"account_slot >= 0 AND account_slot <= 18446744073709551615",
|
||||||
|
"octet_length(account_state_hash) = 32",
|
||||||
|
"octet_length(provider) >= 1 AND octet_length(provider) <= 128",
|
||||||
|
"octet_length(protocol) >= 1 AND octet_length(protocol) <= 128",
|
||||||
|
"octet_length(acquisition_method) >= 1 AND octet_length(acquisition_method) <= 128",
|
||||||
|
"origin = 'backfill' OR origin = 'import' OR origin = 'live' OR origin = 'repair' OR origin = 'replay'",
|
||||||
|
"received_at_unix_millis >= 0 AND received_at_unix_millis <= 253402300799999",
|
||||||
|
"observed_at_unix_millis IS NULL OR observed_at_unix_millis <= received_at_unix_millis",
|
||||||
|
"source_payload_size_bytes IS NULL OR source_payload_size_bytes >= 0 AND source_payload_size_bytes <= 67108864",
|
||||||
|
"transaction_signature IS NULL OR octet_length(transaction_signature) = 64",
|
||||||
|
"write_version IS NULL OR write_version >= 0 AND write_version <= 18446744073709551615",
|
||||||
|
"CREATE INDEX IF NOT EXISTS ix_ksp_raw_account_states_slot_pubkey_state_hash ON ksp_raw_account_states (slot, pubkey, state_hash)",
|
||||||
] {
|
] {
|
||||||
assert!(normalized_sql.contains(required), "V002 pre.002 physical foundation is missing: {required}");
|
assert!(normalized_sql.contains(required), "V002 final physical contract is missing: {required}");
|
||||||
}
|
}
|
||||||
for forbidden in ["CREATE INDEX", " CHECK ", "ON DELETE CASCADE", "BIGSERIAL", "slot BIGINT", "data TEXT"] {
|
for forbidden in [
|
||||||
assert!(!normalized_sql.contains(forbidden), "pre.002 advanced V002 beyond the minimal table/PK/FK scope: {forbidden}");
|
"ON DELETE CASCADE",
|
||||||
|
"BIGSERIAL",
|
||||||
|
"slot BIGINT",
|
||||||
|
"lamports BIGINT",
|
||||||
|
"rent_epoch BIGINT",
|
||||||
|
"write_version BIGINT",
|
||||||
|
"data TEXT",
|
||||||
|
"CREATE UNIQUE INDEX",
|
||||||
|
] {
|
||||||
|
assert!(!normalized_sql.contains(forbidden), "V002 contains forbidden physical policy/index shape: {forbidden}");
|
||||||
}
|
}
|
||||||
|
let index_resource = &crate::V002_RESOURCES[31];
|
||||||
|
let contract = match index_resource.object {
|
||||||
|
super::SchemaObjectContract::Index(value) => value,
|
||||||
|
super::SchemaObjectContract::Constraint(_) | super::SchemaObjectContract::Table(_) => panic!("V002 final resource must be the navigation index"),
|
||||||
|
};
|
||||||
|
assert_eq!(contract.key_fragment, "slot,pubkey,state_hash");
|
||||||
|
assert_eq!(contract.predicate_fragment, std::option::Option::None);
|
||||||
|
assert!(!contract.unique);
|
||||||
|
assert!(!index_resource.sql.contains("WHERE"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pre_003_v002_external_compatibility_recognizes_only_owned_constraints() {
|
||||||
|
assert!(super::is_expected_constraint(crate::V002_RESOURCES, "ksp_raw_account_states", "ck_ksp_raw_account_states_slot"));
|
||||||
|
assert!(super::is_expected_constraint(crate::V002_RESOURCES, "ksp_raw_account_observations", "fk_ksp_raw_account_observations_state",));
|
||||||
|
assert!(!super::is_expected_constraint(crate::V002_RESOURCES, "ksp_raw_account_states", "external_check"));
|
||||||
|
let slot_resource = crate::V002_RESOURCES.iter().find(|resource| return resource.id == "constraints/005_ck_ksp_raw_account_states_slot.sql");
|
||||||
|
let slot_resource = match slot_resource {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => panic!("V002 slot constraint must remain embedded"),
|
||||||
|
};
|
||||||
|
let expected = super::expected_constraint_definition(slot_resource.sql, "ck_ksp_raw_account_states_slot");
|
||||||
|
let expected = match expected {
|
||||||
|
std::option::Option::Some(value) => value,
|
||||||
|
std::option::Option::None => panic!("V002 slot constraint definition must be extractable"),
|
||||||
|
};
|
||||||
|
assert!(super::matches_expected_constraint_definition(crate::V002_RESOURCES, "ksp_raw_account_states", "c", expected.as_str()));
|
||||||
|
assert!(!super::matches_expected_constraint_definition(crate::V001_RESOURCES, "ksp_raw_account_states", "c", expected.as_str()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
187
deltas/0.3.4/pre.003.md
Normal file
187
deltas/0.3.4/pre.003.md
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
<!-- file: deltas/0.3.4/pre.003.md -->
|
||||||
|
<!-- version: 1 -->
|
||||||
|
|
||||||
|
# Delta `0.3.4-pre.003` — contraintes, index et compatibilité V002
|
||||||
|
|
||||||
|
## 1. Base
|
||||||
|
|
||||||
|
Base appliquée :
|
||||||
|
|
||||||
|
```text
|
||||||
|
0.3.4-pre.002-fix.001
|
||||||
|
workspace.package.version = 0.3.4-pre.2.fix.1
|
||||||
|
```
|
||||||
|
|
||||||
|
Le gate opérateur fourni le 2026-08-30 est intégralement vert : audits Rust/Markdown, `cargo check --workspace`, Clippy all-targets, suites `ksp-store-api`, `ksp-store-lib`, `ksp-store-postgres-lib`, `ksp-config-lib` et `cargo check -p ksp-store-lib --no-default-features` passent. Le backend PostgreSQL exécute notamment 43 tests unitaires sans échec après `pre.002-fix.001`.
|
||||||
|
|
||||||
|
## 2. Version
|
||||||
|
|
||||||
|
Cette tranche contient des changements Rust/SQL fonctionnels :
|
||||||
|
|
||||||
|
```text
|
||||||
|
workspace.package.version = 0.3.4-pre.3
|
||||||
|
label = 0.3.4-pre.003
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Scope exécuté
|
||||||
|
|
||||||
|
`pre.003` ferme uniquement le schéma physique V002 et sa compatibilité de migration. Aucun repository account ni dispatch façade n'est ouvert.
|
||||||
|
|
||||||
|
Inventaire V002 final :
|
||||||
|
|
||||||
|
```text
|
||||||
|
2 tables
|
||||||
|
29 contraintes = 2 PK + 1 FK + 26 CHECK
|
||||||
|
1 index non unique
|
||||||
|
32 resources au total
|
||||||
|
```
|
||||||
|
|
||||||
|
Les 26 `CHECK` ajoutés matérialisent les invariants déjà possédés par `ksp-store-api` :
|
||||||
|
|
||||||
|
- pubkeys, state hashes, observation keys et source hashes aux largeurs fixes attendues ;
|
||||||
|
- transaction signature optionnelle à 64 bytes ;
|
||||||
|
- `slot`, `lamports`, `rent_epoch`, `account_slot` et `write_version` dans le domaine `u64` exact, avec stockage `NUMERIC(20,0)` lorsque nécessaire ;
|
||||||
|
- `data` account bornée à 16 MiB, vide autorisé ;
|
||||||
|
- provenance logique obligatoire/optionnelle bornée à 1..128 octets avec l'alphabet sûr KSP ;
|
||||||
|
- origin limité à `backfill`, `import`, `live`, `repair`, `replay` ;
|
||||||
|
- timestamps bornés à `MAX_RAW_UNIX_MILLIS` et ordre `observed_at <= received_at` ;
|
||||||
|
- source payload size optionnelle bornée à 64 MiB.
|
||||||
|
|
||||||
|
`is_startup` reste un `BOOLEAN NULL` sans contrainte artificielle supplémentaire.
|
||||||
|
|
||||||
|
## 4. Index de navigation
|
||||||
|
|
||||||
|
Un seul index métier est ajouté :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ix_ksp_raw_account_states_slot_pubkey_state_hash
|
||||||
|
(slot, pubkey, state_hash)
|
||||||
|
```
|
||||||
|
|
||||||
|
Il est non unique et sans prédicat. Aucun index owner/provider/method/time/status n'est introduit sans query publique qui le justifie.
|
||||||
|
|
||||||
|
## 5. Checksum V002 final et transition de prerelease
|
||||||
|
|
||||||
|
Checksum V002 intermédiaire écrit par `pre.002` :
|
||||||
|
|
||||||
|
```text
|
||||||
|
30ac87496f1bb3805d816660891d7eab2127c599a636eb40c17ade5926311f55
|
||||||
|
```
|
||||||
|
|
||||||
|
Checksum V002 final calculé sur les 32 resources ordonnées :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ff21605ed45f7ab4c0f92bbb692700b4118a9488b04d50a31d259ac59bdb550e
|
||||||
|
```
|
||||||
|
|
||||||
|
V000 et V001 restent byte-inchangées et conservent leurs checksums :
|
||||||
|
|
||||||
|
```text
|
||||||
|
V000 = d29068b8c13b9dc0cc9ef6aaadd0fa12d41e0fe4c56541a1118c4bfc846a1450
|
||||||
|
V001 = 31488cda2f08f3f46c4cdbdbb6c18c243662fada02eac4487040c8735d72cc51
|
||||||
|
```
|
||||||
|
|
||||||
|
Le moteur accepte le checksum V002 intermédiaire **uniquement** avec `schema_update=update_if_needed`. Dans ce cas, sous la transaction de migration existante :
|
||||||
|
|
||||||
|
1. l'historique reconnaît uniquement le checksum provisoire explicitement connu ;
|
||||||
|
2. les resources V002 manquantes sont matérialisées et réinspectées ;
|
||||||
|
3. la compatibilité externe V002 est vérifiée ;
|
||||||
|
4. l'entrée V002 de `ksp_store_schema_migrations` est remplacée par le checksum final avec un `UPDATE` conditionné par version, nom et ancien checksum ;
|
||||||
|
5. le commit publie ensemble schéma et historique final.
|
||||||
|
|
||||||
|
Avec `schema_update=disabled`, le checksum provisoire reste un `MigrationMismatch`. Tout checksum V002 inconnu reste rejeté même avec update activé.
|
||||||
|
|
||||||
|
## 6. Schema compatibility V002
|
||||||
|
|
||||||
|
La vérification externe générique couvre désormais V001 et V002 avec leurs inventaires propres. Pour les deux tables account, elle conserve les mêmes principes que V001 :
|
||||||
|
|
||||||
|
- colonnes attendues et types physiques compatibles ;
|
||||||
|
- contraintes KSP attendues reconnues par nom/définition ;
|
||||||
|
- extension externe bloquante rejetée ;
|
||||||
|
- unique index externe non adossé à une contrainte rejeté ;
|
||||||
|
- triggers/rules externes rejetés.
|
||||||
|
|
||||||
|
Le probe d'adoption inclut désormais les deux tables V002 afin qu'un schéma KSP account existant sans metadata de migration ne soit pas considéré comme vide.
|
||||||
|
|
||||||
|
Le contrat d'index interne supporte explicitement les deux formes possédées : V001 avec prédicat `retention_state <> 'purged'`, V002 sans prédicat.
|
||||||
|
|
||||||
|
## 7. Canaries et tests modifiés
|
||||||
|
|
||||||
|
Les canaries backend prouvent désormais :
|
||||||
|
|
||||||
|
- 32 resources V002, ordonnées par tables/constraints/index ;
|
||||||
|
- inventaire exact 2/29/1 ;
|
||||||
|
- checksum final exact et checksums V000/V001 stables ;
|
||||||
|
- acceptation conditionnelle du checksum provisoire et rejet des historiques arbitraires ;
|
||||||
|
- bornes physiques majeures et absence de narrowing `u64` ;
|
||||||
|
- index unique de navigation non filtré/non unique ;
|
||||||
|
- compatibilité externe V002 ;
|
||||||
|
- absence persistante de `src/raw_account.rs`, d'implémentation `RawAccount*` et de dispatch runtime account.
|
||||||
|
|
||||||
|
## 8. Scope négatif préservé
|
||||||
|
|
||||||
|
Cette tranche n'ajoute pas :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ksp-store-api change
|
||||||
|
raw_account repository module
|
||||||
|
RawAccount* implementation on PostgresBackend
|
||||||
|
RawAccount* implementation on Store
|
||||||
|
Store account dispatch
|
||||||
|
account retention/archive/purge
|
||||||
|
owner/provider/time/status indexes
|
||||||
|
worker/job/backfill policy
|
||||||
|
Transport -> Store coupling
|
||||||
|
new backend
|
||||||
|
```
|
||||||
|
|
||||||
|
## 9. Documentation
|
||||||
|
|
||||||
|
`docs/plans/025-V0_3_4_STORE_POSTGRES_RAW_ACCOUNT_PLAN.md` conserve le forecast souple sous forme de sous-paragraphes `### pre.NNN` / `#### pre.NNN-fix.MMM`. `pre.002-fix.001` y passe au statut gate opérateur complet PASS et `pre.003` au statut réalisé / gate opérateur à rejouer.
|
||||||
|
|
||||||
|
`docs/validation/021-V0_3_4_STORE_POSTGRES_RAW_ACCOUNT.md` enregistre le gate vert de la base, l'inventaire V002 final, les checksums et la stratégie de transition de prerelease.
|
||||||
|
|
||||||
|
## 10. Validation exécutée dans l'environnement d'assemblage
|
||||||
|
|
||||||
|
```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.4
|
||||||
|
=> Markdown table audit: clean (239 table(s), 138 file(s))
|
||||||
|
```
|
||||||
|
|
||||||
|
Le calcul indépendant de checksum sur les bytes des resources V002 donne :
|
||||||
|
|
||||||
|
```text
|
||||||
|
ff21605ed45f7ab4c0f92bbb692700b4118a9488b04d50a31d259ac59bdb550e
|
||||||
|
```
|
||||||
|
|
||||||
|
`cargo`, `clippy` et les tests Rust ne sont pas disponibles dans l'environnement d'assemblage. Aucun résultat Cargo de `pre.003` n'est donc revendiqué ici.
|
||||||
|
|
||||||
|
## 11. Gate opérateur demandé
|
||||||
|
|
||||||
|
```text
|
||||||
|
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.4
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
Les preuves PostgreSQL live account restent hors de cette tranche et sont prévues plus tard dans le forecast.
|
||||||
|
|
||||||
|
## 12. Fichiers
|
||||||
|
|
||||||
|
Le delta contient uniquement `Cargo.toml`, les fichiers Rust/tests/schema réellement modifiés, les nouvelles resources V002, les deux documents de suivi modifiés et ce fichier `pre.003.md`. Aucun snapshot complet, aucune archive historique et aucun artefact de build ne sont inclus.
|
||||||
|
|
||||||
|
## 13. Verdict
|
||||||
|
|
||||||
|
`0.3.4-pre.003` : **READY FOR OPERATOR GATE**.
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
<!-- file: docs/plans/025-V0_3_4_STORE_POSTGRES_RAW_ACCOUNT_PLAN.md -->
|
<!-- file: docs/plans/025-V0_3_4_STORE_POSTGRES_RAW_ACCOUNT_PLAN.md -->
|
||||||
<!-- version: 4 -->
|
<!-- version: 5 -->
|
||||||
|
|
||||||
# Plan `0.3.4` — Store/PostgreSQL `RawAccountState` + complétude RAW
|
# Plan `0.3.4` — Store/PostgreSQL `RawAccountState` + complétude RAW
|
||||||
|
|
||||||
## 1. Statut de la release
|
## 1. Statut de la release
|
||||||
|
|
||||||
`0.3.4-pre.001` a figé le design. `0.3.4-pre.002` matérialise uniquement la fondation physique V002 : registry logique, deux tables, deux clés primaires et la FK observation -> state. Aucun repository account, aucun dispatch façade, aucun index métier et aucune contrainte de domaine complète ne sont ouverts dans cette tranche.
|
`0.3.4-pre.001` a figé le design. `0.3.4-pre.002` a matérialisé la fondation physique V002 minimale puis `pre.002-fix.001` a corrigé deux canaris sans toucher au SQL. `0.3.4-pre.003` complète V002 avec les contraintes de domaine, l'index de navigation, la compatibilité de schéma et le checksum final, toujours sans repository account ni dispatch façade.
|
||||||
|
|
||||||
Base canonique auditée :
|
Base canonique auditée :
|
||||||
|
|
||||||
@@ -17,8 +17,8 @@ workspace.package.version = 0.3.3
|
|||||||
Version de travail de cette prerelease :
|
Version de travail de cette prerelease :
|
||||||
|
|
||||||
```text
|
```text
|
||||||
workspace.package.version = 0.3.4-pre.1
|
workspace.package.version = 0.3.4-pre.3
|
||||||
label = 0.3.4-pre.001
|
label = 0.3.4-pre.003
|
||||||
```
|
```
|
||||||
|
|
||||||
Décision de scope : `ksp-store-api` reste inchangée. L'audit n'a révélé aucun gap backend-agnostic bloquant ; la difficulté restante est exclusivement l'implémentation physique PostgreSQL et son dispatch par la façade.
|
Décision de scope : `ksp-store-api` reste inchangée. L'audit n'a révélé aucun gap backend-agnostic bloquant ; la difficulté restante est exclusivement l'implémentation physique PostgreSQL et son dispatch par la façade.
|
||||||
@@ -569,7 +569,7 @@ Remplacement du sizing tabulaire par des sous-sections éditables avec statut ex
|
|||||||
|
|
||||||
### `pre.002` — Registry V002 et tables minimales
|
### `pre.002` — Registry V002 et tables minimales
|
||||||
|
|
||||||
**Statut : réalisé ; corrigé par `pre.002-fix.001`, gate opérateur du correctif à rejouer.**
|
**Statut : réalisé ; corrigé par `pre.002-fix.001`, gate opérateur complet PASS.**
|
||||||
|
|
||||||
Budget cible : **15-20 min**. V002 `raw_account_state` est enregistrée avec exactement cinq resources : deux tables, les PK `(pubkey, slot, state_hash)` / `(observation_key)` et la FK composite observation -> state. Aucun repository account ni dispatch façade n'est ajouté.
|
Budget cible : **15-20 min**. V002 `raw_account_state` est enregistrée avec exactement cinq resources : deux tables, les PK `(pubkey, slot, state_hash)` / `(observation_key)` et la FK composite observation -> state. Aucun repository account ni dispatch façade n'est ajouté.
|
||||||
|
|
||||||
@@ -577,7 +577,7 @@ Le checksum resources calculable sur cet état intermédiaire est `30ac87496f1bb
|
|||||||
|
|
||||||
#### `pre.002-fix.001` — correction des canaris V002
|
#### `pre.002-fix.001` — correction des canaris V002
|
||||||
|
|
||||||
**Statut : livré ; gate opérateur à rejouer.**
|
**Statut : réalisé ; gate opérateur complet PASS.**
|
||||||
|
|
||||||
Le gate opérateur de `pre.002` compile le workspace et passe Clippy, Store API, Store façade, Config et `--no-default-features`, mais révèle deux canaris backend obsolètes. Le premier construisait encore un historique « futur » en version 2 alors que V002 occupe désormais cette version ; il est recalibré avec V002 valide suivie d'une version 3 inconnue. Le second comparait la FK V002 à une chaîne SQL sensible aux retours à la ligne ; il normalise désormais uniquement les espaces avant de vérifier le contrat physique.
|
Le gate opérateur de `pre.002` compile le workspace et passe Clippy, Store API, Store façade, Config et `--no-default-features`, mais révèle deux canaris backend obsolètes. Le premier construisait encore un historique « futur » en version 2 alors que V002 occupe désormais cette version ; il est recalibré avec V002 valide suivie d'une version 3 inconnue. Le second comparait la FK V002 à une chaîne SQL sensible aux retours à la ligne ; il normalise désormais uniquement les espaces avant de vérifier le contrat physique.
|
||||||
|
|
||||||
@@ -585,9 +585,15 @@ Aucune ressource SQL, migration, table, contrainte, index, surface runtime ou ca
|
|||||||
|
|
||||||
### `pre.003` — Contraintes, index et schema compatibility V002
|
### `pre.003` — Contraintes, index et schema compatibility V002
|
||||||
|
|
||||||
**Statut : planifié.**
|
**Statut : réalisé ; gate opérateur à rejouer.**
|
||||||
|
|
||||||
Budget cible : **15-20 min**. Compléter les contraintes physiques, l'index de navigation, la schema compatibility et figer le checksum V002 final, sans avancer les repositories. Cette tranche doit traiter explicitement la transition depuis l'état V002 intermédiaire de `pre.002` avant toute utilisation live persistante.
|
Budget cible : **15-20 min**. V002 est complétée à **32 resources** : deux tables, 29 contraintes (PK/FK incluses) et un index non unique `(slot, pubkey, state_hash)` sans prédicat. Les contraintes matérialisent les bornes backend-agnostic existantes : fixed-width 32/64 bytes, domaines `u64` via `NUMERIC(20,0)`, data `<= 16 MiB`, codes de provenance 1..128 octets/alphabet sûr, timestamps bornés, ordre `observed_at <= received_at` et source payload `<= 64 MiB`.
|
||||||
|
|
||||||
|
Le checksum V002 final est `ff21605ed45f7ab4c0f92bbb692700b4118a9488b04d50a31d259ac59bdb550e`. La transition depuis le checksum provisoire `30ac87496f1bb3805d816660891d7eab2127c599a636eb40c17ade5926311f55` est acceptée **uniquement** sous `schema_update=update_if_needed` : le moteur répare/valide d'abord les resources V002 manquantes, vérifie la compatibilité externe puis remplace atomiquement le checksum d'historique dans la même transaction. `schema_update=disabled` conserve un mismatch sûr et tout checksum inconnu reste rejeté.
|
||||||
|
|
||||||
|
Le probe d'adoption reconnaît désormais aussi les deux tables V002. Aucun `src/raw_account.rs`, aucune implémentation `RawAccount*` et aucun dispatch Store ne sont ouverts dans cette tranche.
|
||||||
|
|
||||||
|
Gate de base `pre.002-fix.001` fourni le 2026-08-30 : audits Rust/Markdown, `cargo check --workspace`, Clippy all-targets, suites `ksp-store-api`, `ksp-store-lib`, `ksp-store-postgres-lib`, `ksp-config-lib` et `ksp-store-lib --no-default-features` sont tous PASS.
|
||||||
|
|
||||||
### `pre.004` — Mapping privé et lectures `get`
|
### `pre.004` — Mapping privé et lectures `get`
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<!-- file: docs/validation/021-V0_3_4_STORE_POSTGRES_RAW_ACCOUNT.md -->
|
<!-- file: docs/validation/021-V0_3_4_STORE_POSTGRES_RAW_ACCOUNT.md -->
|
||||||
<!-- version: 3 -->
|
<!-- version: 4 -->
|
||||||
|
|
||||||
# Validation `0.3.4` — Store/PostgreSQL `RawAccountState` + complétude RAW
|
# Validation `0.3.4` — Store/PostgreSQL `RawAccountState` + complétude RAW
|
||||||
|
|
||||||
## 1. Portée
|
## 1. Portée
|
||||||
|
|
||||||
Cette matrice est ouverte par `0.3.4-pre.001`. `0.3.4-pre.002` matérialise uniquement la fondation V002 : registry, deux tables et PK/FK de base. Son gate opérateur révèle deux canaris unitaires obsolètes sans défaut de migration ; `0.3.4-pre.002-fix.001` les corrige sans modifier les resources SQL. Les contraintes de domaine, l'index, la compatibilité externe complète, les repositories et les quatre capabilities account restent volontairement pending.
|
Cette matrice est ouverte par `0.3.4-pre.001`. `0.3.4-pre.002` matérialise la fondation V002 minimale et `pre.002-fix.001` corrige deux canaris sans modifier le SQL ; son gate opérateur complet est PASS. `0.3.4-pre.003` complète les contraintes de domaine, l'index de navigation, la compatibilité externe et le checksum V002 final. Les repositories et les quatre capabilities account restent volontairement pending.
|
||||||
|
|
||||||
Base :
|
Base :
|
||||||
|
|
||||||
@@ -272,8 +272,8 @@ Le test live account devra être opt-in/ignored, URI stdin, sans environnement n
|
|||||||
| Tranche | Objet | État |
|
| Tranche | Objet | État |
|
||||||
|---------|-------------------------------------------------------------------|---------|
|
|---------|-------------------------------------------------------------------|---------|
|
||||||
| pre.001 | audit, kbot3, threat model, V002 design, sizing, plan/validation | DONE |
|
| pre.001 | audit, kbot3, threat model, V002 design, sizing, plan/validation | DONE |
|
||||||
| pre.002 | V002 registry + deux tables + PK/FK de base, sans repository | FIX.001 |
|
| pre.002 | V002 registry + deux tables + PK/FK de base, sans repository | PASS |
|
||||||
| pre.003 | contraintes complètes, index, schema compatibility, checksum V002 | PLANNED |
|
| pre.003 | contraintes complètes, index, schema compatibility, checksum V002 | GATE |
|
||||||
| pre.004 | mapping privé state/observation + get reads + hostile rows | PLANNED |
|
| pre.004 | mapping privé state/observation + get reads + hostile rows | PLANNED |
|
||||||
| pre.005 | acquisition atomique state+observation + idempotence/conflict | PLANNED |
|
| pre.005 | acquisition atomique state+observation + idempotence/conflict | PLANNED |
|
||||||
| pre.006 | observation supplémentaire + races/cancellation unitaires | PLANNED |
|
| pre.006 | observation supplémentaire + races/cancellation unitaires | PLANNED |
|
||||||
@@ -350,4 +350,35 @@ Invariants du correctif :
|
|||||||
- aucun module repository account, dispatch Store ou implémentation `RawAccount*` ouvert ;
|
- aucun module repository account, dispatch Store ou implémentation `RawAccount*` ouvert ;
|
||||||
- `workspace.package.version = 0.3.4-pre.2.fix.1` conformément à `VER-ID-007/010`, car des sources Rust de tests sont modifiées.
|
- `workspace.package.version = 0.3.4-pre.2.fix.1` conformément à `VER-ID-007/010`, car des sources Rust de tests sont modifiées.
|
||||||
|
|
||||||
Gate opérateur du correctif : **À REJOUER**.
|
Gate opérateur du correctif du 2026-08-30 : **PASS complet** — audits Rust/Markdown, workspace check, Clippy all-targets, Store API, Store façade, backend PostgreSQL, Config et `--no-default-features` sont verts.
|
||||||
|
|
||||||
|
## 19. Verdict `pre.003`
|
||||||
|
|
||||||
|
V002 finale : **PASS statique / gate Cargo opérateur à rejouer**.
|
||||||
|
|
||||||
|
Inventaire final :
|
||||||
|
|
||||||
|
```text
|
||||||
|
V002 resources = 32
|
||||||
|
tables = 2
|
||||||
|
constraints = 29 (2 PK + 1 FK + 26 CHECK)
|
||||||
|
indexes = 1
|
||||||
|
final checksum = ff21605ed45f7ab4c0f92bbb692700b4118a9488b04d50a31d259ac59bdb550e
|
||||||
|
provisional pre.002= 30ac87496f1bb3805d816660891d7eab2127c599a636eb40c17ade5926311f55
|
||||||
|
```
|
||||||
|
|
||||||
|
Contraintes matérialisées : exactitude fixed-width des identités/hash/signature, domaines `u64` en `NUMERIC(20,0)`, data account `<= 16 MiB`, provenance logique 1..128 octets/alphabet sûr, origin fermé aux cinq variantes API, timestamps `0..MAX_RAW_UNIX_MILLIS`, `observed_at <= received_at`, hash source 32 bytes et source size `<= 64 MiB`. `is_startup` reste un booléen nullable sans contrainte artificielle.
|
||||||
|
|
||||||
|
L'unique index métier V002 est non unique et non filtré :
|
||||||
|
|
||||||
|
```text
|
||||||
|
(slot, pubkey, state_hash)
|
||||||
|
```
|
||||||
|
|
||||||
|
Aucun index owner/provider/method/time/status n'est ajouté.
|
||||||
|
|
||||||
|
Compatibilité de prerelease : le checksum provisoire V002 de `pre.002` n'est accepté que lorsque `schema_update=update_if_needed`. Dans ce cas, les resources manquantes sont matérialisées et vérifiées, la compatibilité externe V002 est contrôlée, puis l'entrée d'historique est mise à jour atomiquement vers le checksum final. Avec `schema_update=disabled`, le checksum provisoire est rejeté ; tout checksum non explicitement connu reste `MigrationMismatch`.
|
||||||
|
|
||||||
|
Le probe d'adoption couvre V001 + V002. V000/V001 et leurs checksums restent inchangés. Aucune surface repository/runtime account n'est ouverte : `src/raw_account.rs` absent, aucune implémentation `RawAccount*` et aucun dispatch Store.
|
||||||
|
|
||||||
|
Audits exécutables dans l'environnement d'assemblage : Rust rules **PASS** ; audit Markdown à rejouer après cette mise à jour documentaire. Les commandes Cargo ne sont pas disponibles dans cet environnement et restent donc explicitement à rejouer par l'opérateur.
|
||||||
|
|||||||
Reference in New Issue
Block a user