feat: complete Epic 1 production foundation
This commit is contained in:
@@ -0,0 +1,331 @@
|
||||
use sqlx::{PgConnection, Row, query};
|
||||
|
||||
use super::authority::MigrationError;
|
||||
use super::schema_guard::{
|
||||
column_exists, constraint_expression, normalize_definition, relation_exists, schema_error,
|
||||
};
|
||||
|
||||
pub(super) async fn validate_v11_absent(
|
||||
connection: &mut PgConnection,
|
||||
) -> Result<bool, MigrationError> {
|
||||
Ok(!relation_exists(connection, "product_events").await?
|
||||
&& !relation_exists(connection, "product_event_daily_rollups").await?
|
||||
&& !relation_exists(connection, "onboarding_selections").await?
|
||||
&& !column_exists(connection, "invocation_logs", "platform_api_key_id").await?)
|
||||
}
|
||||
|
||||
pub(super) async fn validate_v11_onboarding_product_events(
|
||||
connection: &mut PgConnection,
|
||||
) -> Result<(), MigrationError> {
|
||||
for relation in [
|
||||
"product_events",
|
||||
"product_event_daily_rollups",
|
||||
"onboarding_selections",
|
||||
] {
|
||||
if !relation_exists(connection, relation).await? {
|
||||
return Err(schema_error(11));
|
||||
}
|
||||
}
|
||||
if !column_exists(connection, "invocation_logs", "platform_api_key_id").await? {
|
||||
return Err(schema_error(11));
|
||||
}
|
||||
for relation in [
|
||||
"product_events_workspace_id_idempotency_key_key",
|
||||
"product_events_workspace_occurred_idx",
|
||||
"invocation_logs_workspace_agent_key_success_idx",
|
||||
] {
|
||||
if !relation_exists(connection, relation).await? {
|
||||
return Err(schema_error(11));
|
||||
}
|
||||
}
|
||||
for (table, constraint, required) in [
|
||||
(
|
||||
"product_events",
|
||||
"product_events_id_check",
|
||||
&["id~", "^pe_[a-za-z0-9_-]{1,128}$"][..],
|
||||
),
|
||||
(
|
||||
"product_events",
|
||||
"product_events_name_check",
|
||||
&[
|
||||
"event_name=any",
|
||||
"onboarding_eligible",
|
||||
"onboarding_started",
|
||||
"onboarding_resumed",
|
||||
"onboarding_dismissed",
|
||||
"onboarding_abandoned",
|
||||
"onboarding_completed",
|
||||
][..],
|
||||
),
|
||||
(
|
||||
"product_events",
|
||||
"product_events_schema_version_check",
|
||||
&["schema_version=1"][..],
|
||||
),
|
||||
(
|
||||
"product_events",
|
||||
"product_events_idempotency_key_check",
|
||||
&[
|
||||
"octet_lengthidempotency_key>=1",
|
||||
"octet_lengthidempotency_key<=256",
|
||||
][..],
|
||||
),
|
||||
(
|
||||
"product_events",
|
||||
"product_events_properties_check",
|
||||
&[
|
||||
"jsonb_typeofproperties_json='object'",
|
||||
"pg_column_sizeproperties_json<=4096",
|
||||
"event_name<>'onboarding_eligible'",
|
||||
"properties_json@>'{\"eligible\":true}'",
|
||||
"jsonb_typeofproperties_json->'eligible_since'",
|
||||
"='string'",
|
||||
][..],
|
||||
),
|
||||
(
|
||||
"product_event_daily_rollups",
|
||||
"product_event_daily_rollups_name_check",
|
||||
&[
|
||||
"event_name=any",
|
||||
"onboarding_eligible",
|
||||
"onboarding_completed",
|
||||
][..],
|
||||
),
|
||||
(
|
||||
"product_event_daily_rollups",
|
||||
"product_event_daily_rollups_counts_check",
|
||||
&[
|
||||
"events_total>=0",
|
||||
"eligible_total>=0",
|
||||
"eligible_total<=events_total",
|
||||
][..],
|
||||
),
|
||||
] {
|
||||
let definition =
|
||||
normalize_definition(&constraint_expression(connection, table, constraint).await?);
|
||||
if required.iter().any(|snippet| !definition.contains(snippet)) {
|
||||
return Err(schema_error(11));
|
||||
}
|
||||
}
|
||||
validate_index(
|
||||
connection,
|
||||
"product_events_workspace_occurred_idx",
|
||||
"product_events",
|
||||
false,
|
||||
&["workspace_id", "occurred_at", "id"],
|
||||
&[],
|
||||
)
|
||||
.await?;
|
||||
validate_index(
|
||||
connection,
|
||||
"invocation_logs_workspace_agent_key_success_idx",
|
||||
"invocation_logs",
|
||||
false,
|
||||
&[
|
||||
"workspace_id",
|
||||
"agent_id",
|
||||
"platform_api_key_id",
|
||||
"created_atdesc",
|
||||
],
|
||||
&[
|
||||
"platform_api_key_idisnotnull",
|
||||
"source='agent_tool_call'",
|
||||
"status='ok'",
|
||||
],
|
||||
)
|
||||
.await?;
|
||||
let key_scope_fk = constraint_definition(
|
||||
connection,
|
||||
"invocation_logs",
|
||||
"invocation_logs_platform_key_scope_fk",
|
||||
)
|
||||
.await?;
|
||||
for required in [
|
||||
"foreignkeyworkspace_id,agent_id,platform_api_key_id",
|
||||
"referencesplatform_api_keysworkspace_id,agent_id,id",
|
||||
"ondeletesetnullplatform_api_key_id",
|
||||
] {
|
||||
if !key_scope_fk.contains(required) {
|
||||
return Err(schema_error(11));
|
||||
}
|
||||
}
|
||||
for (kind, name) in [
|
||||
("constraint", "invocation_logs_platform_key_scope_fk"),
|
||||
("trigger", "product_events_append_only_guard"),
|
||||
] {
|
||||
let present: bool = match kind {
|
||||
"constraint" => query(
|
||||
"select exists (select 1 from pg_constraint c
|
||||
join pg_namespace n on n.oid = c.connamespace
|
||||
where n.nspname = current_schema() and c.conname = $1) as present",
|
||||
),
|
||||
_ => query(
|
||||
"select exists (select 1 from pg_trigger t
|
||||
join pg_class c on c.oid = t.tgrelid
|
||||
join pg_namespace n on n.oid = c.relnamespace
|
||||
where n.nspname = current_schema() and t.tgname = $1 and not t.tgisinternal) as present",
|
||||
),
|
||||
}
|
||||
.bind(name)
|
||||
.fetch_one(&mut *connection)
|
||||
.await
|
||||
.map_err(|_| MigrationError::storage("preflight.schema"))?
|
||||
.try_get("present")
|
||||
.map_err(|_| MigrationError::storage("preflight.schema"))?;
|
||||
if !present {
|
||||
return Err(schema_error(11));
|
||||
}
|
||||
}
|
||||
let nullable = query(
|
||||
"select is_nullable from information_schema.columns
|
||||
where table_schema = current_schema()
|
||||
and table_name = 'invocation_logs'
|
||||
and column_name = 'platform_api_key_id'",
|
||||
)
|
||||
.fetch_optional(&mut *connection)
|
||||
.await
|
||||
.map_err(|_| MigrationError::storage("preflight.schema"))?
|
||||
.and_then(|row| row.try_get::<String, _>("is_nullable").ok());
|
||||
if nullable.as_deref() != Some("YES") {
|
||||
return Err(schema_error(11));
|
||||
}
|
||||
let shape = constraint_expression(
|
||||
connection,
|
||||
"onboarding_selections",
|
||||
"onboarding_selections_shape_check",
|
||||
)
|
||||
.await?;
|
||||
let normalized_shape = normalize_definition(&shape);
|
||||
for required in [
|
||||
"operation_idisnull",
|
||||
"operation_version>0",
|
||||
"catalog_revision>0",
|
||||
"invocation_log_idisnotnull",
|
||||
"selected_atisnull",
|
||||
"selected_atisnotnull",
|
||||
] {
|
||||
if !normalized_shape.contains(required) {
|
||||
return Err(schema_error(11));
|
||||
}
|
||||
}
|
||||
let function_definition = query(
|
||||
"select p.prosrc as definition
|
||||
from pg_proc p
|
||||
join pg_namespace n on n.oid = p.pronamespace
|
||||
where n.nspname = current_schema()
|
||||
and p.proname = 'crank_reject_product_event_mutation'",
|
||||
)
|
||||
.fetch_optional(connection)
|
||||
.await
|
||||
.map_err(|_| MigrationError::storage("preflight.schema"))?
|
||||
.and_then(|row| row.try_get::<String, _>("definition").ok());
|
||||
if !function_definition.is_some_and(|definition| {
|
||||
let normalized = normalize_definition(&definition);
|
||||
[
|
||||
"notexists",
|
||||
"fromworkspaces",
|
||||
"old.workspace_id",
|
||||
"returnold",
|
||||
"producteventisappend-only",
|
||||
]
|
||||
.into_iter()
|
||||
.all(|required| normalized.contains(required))
|
||||
}) {
|
||||
return Err(schema_error(11));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn constraint_definition(
|
||||
connection: &mut PgConnection,
|
||||
table: &str,
|
||||
constraint: &str,
|
||||
) -> Result<String, MigrationError> {
|
||||
query(
|
||||
"select pg_get_constraintdef(c.oid, true) as definition
|
||||
from pg_catalog.pg_constraint c
|
||||
join pg_catalog.pg_class t on t.oid = c.conrelid
|
||||
join pg_catalog.pg_namespace n on n.oid = t.relnamespace
|
||||
where n.nspname = current_schema() and t.relname = $1 and c.conname = $2",
|
||||
)
|
||||
.bind(table)
|
||||
.bind(constraint)
|
||||
.fetch_optional(&mut *connection)
|
||||
.await
|
||||
.map_err(|_| MigrationError::storage("preflight.schema"))?
|
||||
.ok_or_else(|| schema_error(11))?
|
||||
.try_get::<String, _>("definition")
|
||||
.map(|definition| normalize_definition(&definition))
|
||||
.map_err(|_| MigrationError::storage("preflight.schema"))
|
||||
}
|
||||
|
||||
async fn validate_index(
|
||||
connection: &mut PgConnection,
|
||||
index: &str,
|
||||
expected_table: &str,
|
||||
expected_unique: bool,
|
||||
expected_columns: &[&str],
|
||||
predicate_snippets: &[&str],
|
||||
) -> Result<(), MigrationError> {
|
||||
let row = query(
|
||||
"select t.relname as table_name, am.amname as access_method,
|
||||
i.indisvalid, i.indisready, i.indisunique,
|
||||
pg_get_indexdef(i.indexrelid, 1, true) as first_column,
|
||||
pg_get_indexdef(i.indexrelid, 2, true) as second_column,
|
||||
pg_get_indexdef(i.indexrelid, 3, true) as third_column,
|
||||
pg_get_indexdef(i.indexrelid, 4, true) as fourth_column,
|
||||
pg_get_indexdef(i.indexrelid) as definition,
|
||||
coalesce(pg_get_expr(i.indpred, i.indrelid), '') as predicate
|
||||
from pg_catalog.pg_index i
|
||||
join pg_catalog.pg_class idx on idx.oid = i.indexrelid
|
||||
join pg_catalog.pg_class t on t.oid = i.indrelid
|
||||
join pg_catalog.pg_namespace n on n.oid = t.relnamespace
|
||||
join pg_catalog.pg_am am on am.oid = idx.relam
|
||||
where n.nspname = current_schema() and idx.relname = $1",
|
||||
)
|
||||
.bind(index)
|
||||
.fetch_optional(&mut *connection)
|
||||
.await
|
||||
.map_err(|_| MigrationError::storage("preflight.schema"))?;
|
||||
let valid = row.is_some_and(|row| {
|
||||
let actual_columns = [
|
||||
"first_column",
|
||||
"second_column",
|
||||
"third_column",
|
||||
"fourth_column",
|
||||
]
|
||||
.into_iter()
|
||||
.filter_map(|field| row.try_get::<Option<String>, _>(field).ok().flatten())
|
||||
.map(|value| normalize_definition(&value))
|
||||
.filter(|value| !value.is_empty())
|
||||
.collect::<Vec<_>>();
|
||||
let expected_base_columns = expected_columns
|
||||
.iter()
|
||||
.map(|column| column.strip_suffix("desc").unwrap_or(column).to_owned())
|
||||
.collect::<Vec<_>>();
|
||||
let definition = row
|
||||
.try_get::<String, _>("definition")
|
||||
.ok()
|
||||
.map(|value| normalize_definition(&value))
|
||||
.unwrap_or_default();
|
||||
let predicate = row
|
||||
.try_get::<String, _>("predicate")
|
||||
.ok()
|
||||
.map(|value| normalize_definition(&value))
|
||||
.unwrap_or_default();
|
||||
row.try_get::<String, _>("table_name").ok().as_deref() == Some(expected_table)
|
||||
&& row.try_get::<String, _>("access_method").ok().as_deref() == Some("btree")
|
||||
&& row.try_get::<bool, _>("indisvalid").ok() == Some(true)
|
||||
&& row.try_get::<bool, _>("indisready").ok() == Some(true)
|
||||
&& row.try_get::<bool, _>("indisunique").ok() == Some(expected_unique)
|
||||
&& actual_columns == expected_base_columns
|
||||
&& expected_columns
|
||||
.iter()
|
||||
.filter(|column| column.ends_with("desc"))
|
||||
.all(|column| definition.contains(column))
|
||||
&& predicate_snippets
|
||||
.iter()
|
||||
.all(|snippet| predicate.contains(snippet))
|
||||
});
|
||||
if valid { Ok(()) } else { Err(schema_error(11)) }
|
||||
}
|
||||
Reference in New Issue
Block a user