feat: harden community production foundation through story 1.5

This commit is contained in:
2026-08-14 00:21:59 +03:00
parent c30461cc92
commit f6fc2e5c9b
161 changed files with 16758 additions and 2515 deletions
+242
View File
@@ -0,0 +1,242 @@
use std::{process::ExitCode, time::Duration};
use crank_config::{ConfigSource, DatabaseSettings, parse_migrator};
use crank_registry::{
BackfillPolicy, MigrationApplyResult, MigrationAuthority, MigrationPreflight,
};
use serde_json::json;
use sqlx::{
PgPool,
postgres::{PgConnectOptions, PgPoolOptions},
};
#[tokio::main]
async fn main() -> ExitCode {
match run().await {
Ok(code) => code,
Err(error) => {
eprintln!(
"{}",
json!({
"status": "error",
"code": error.code,
"stage": error.stage,
"version": error.version,
"recovery": error.recovery,
})
);
ExitCode::FAILURE
}
}
}
#[derive(Clone, Copy)]
struct CliError {
code: &'static str,
stage: &'static str,
recovery: &'static str,
version: Option<i64>,
}
impl CliError {
const fn new(code: &'static str, stage: &'static str, recovery: &'static str) -> Self {
Self {
code,
stage,
recovery,
version: None,
}
}
fn from_migration(error: crank_registry::MigrationError) -> Self {
Self {
code: error.code(),
stage: error.stage(),
recovery: error.recovery(),
version: error.version(),
}
}
}
async fn run() -> Result<ExitCode, CliError> {
let mut arguments = std::env::args().skip(1);
let requested = arguments.next();
let option = arguments.next();
if arguments.next().is_some() {
return Err(CliError::new(
"invalid_command",
"cli.arguments",
"run_preflight",
));
}
let command = match requested.as_deref() {
None | Some("preflight") => "preflight",
Some("plan") => "plan",
Some("apply") => "apply",
Some(_) => {
return Err(CliError::new(
"invalid_command",
"cli.arguments",
"run_preflight",
));
}
};
if command == "plan" {
MigrationAuthority::validate_sequence().map_err(CliError::from_migration)?;
let sequence = MigrationAuthority::sequence()
.into_iter()
.map(|migration| {
let backfill = match migration.backfill {
BackfillPolicy::None => json!({ "kind": "none" }),
BackfillPolicy::Bounded {
max_batch_rows,
max_batch_ms,
resumable,
} => json!({
"kind": "bounded",
"max_batch_rows": max_batch_rows,
"max_batch_ms": max_batch_ms,
"resumable": resumable,
}),
};
json!({
"version": migration.version,
"name": migration.name,
"checksum": migration.checksum,
"source_digest": migration.source_digest,
"phase": migration.phase,
"compatibility": migration.compatibility,
"owner": migration.owner,
"transactional": migration.transactional,
"backfill": backfill,
"readable_schema_min": migration.readable_schema_min,
"readable_schema_max": migration.readable_schema_max,
"contract_evidence": migration.contract_evidence,
})
})
.collect::<Vec<_>>();
let plan = json!({ "schema_version": 1, "sequence": sequence });
if option.as_deref() == Some("--check") {
let bytes = std::fs::read("docs/schemas/migration-sequence.json")
.map_err(|_| CliError::new("contract_drift", "plan.read", "contact_operator"))?;
if bytes.len() > 65_536 {
return Err(CliError::new(
"contract_drift",
"plan.size",
"contact_operator",
));
}
let committed: serde_json::Value = serde_json::from_slice(&bytes)
.map_err(|_| CliError::new("contract_drift", "plan.parse", "contact_operator"))?;
if committed != plan {
return Err(CliError::new(
"contract_drift",
"plan.compare",
"contact_operator",
));
}
println!("{}", json!({ "status": "contract_current" }));
return Ok(ExitCode::SUCCESS);
}
if option.is_some() {
return Err(CliError::new(
"invalid_command",
"cli.arguments",
"run_preflight",
));
}
println!("{plan}");
return Ok(ExitCode::SUCCESS);
}
if option.is_some() {
return Err(CliError::new(
"invalid_command",
"cli.arguments",
"run_preflight",
));
}
let config = parse_migrator(
ConfigSource::from_os_for_migrator()
.map_err(|_| CliError::new("config_invalid", "config.source", "run_preflight"))?,
)
.map_err(|_| CliError::new("config_invalid", "config.validate", "run_preflight"))?;
let pool = connect(&config.database).await?;
if command == "apply" {
let result = MigrationAuthority::apply(&pool)
.await
.map_err(CliError::from_migration)?;
let (status, from, to) = match result {
MigrationApplyResult::Applied { from, to } => ("applied", from, to),
MigrationApplyResult::AlreadyCurrent { version } => {
("already_current", version, version)
}
};
println!(
"{}",
json!({ "status": status, "from_version": from, "to_version": to })
);
return Ok(ExitCode::SUCCESS);
}
match MigrationAuthority::preflight(&pool)
.await
.map_err(CliError::from_migration)?
{
MigrationPreflight::Current { version } => {
println!("{}", json!({ "status": "current", "version": version }));
Ok(ExitCode::SUCCESS)
}
MigrationPreflight::MigrationRequired { current, target } => {
println!(
"{}",
json!({
"status": "migration_required",
"current_version": current,
"target_version": target,
"recovery": "run_controlled_migration",
})
);
Ok(ExitCode::from(2))
}
}
}
async fn connect(config: &DatabaseSettings) -> Result<PgPool, CliError> {
let options = if let Some(url) = &config.url {
url.expose_secret()
.parse::<PgConnectOptions>()
.map_err(|_| CliError::new("config_invalid", "database.source", "run_preflight"))?
} else {
PgConnectOptions::new()
.host(&config.host)
.port(config.port)
.database(&config.database)
.username(&config.username)
.password(config.password.expose_secret())
};
for attempt in 1..=10 {
let result = PgPoolOptions::new()
.max_connections(config.pool.max_connections)
.min_connections(config.pool.min_connections)
.acquire_timeout(Duration::from_millis(config.pool.acquire_timeout_ms))
.idle_timeout(Duration::from_millis(config.pool.idle_timeout_ms))
.max_lifetime(Duration::from_millis(config.pool.max_lifetime_ms))
.connect_with(options.clone())
.await;
match result {
Ok(pool) => return Ok(pool),
Err(_) if attempt < 10 => {
tokio::time::sleep(Duration::from_secs(1)).await;
}
Err(_) => break,
}
}
Err(CliError::new(
"storage_unavailable",
"database.connect",
"contact_operator",
))
}