feat(types)!: drop the blob column type (ADR-0005 Amendment 2)
blob was a dead-end: declarable but never fillable (no literal in either mode, seed-unsupported), so a blob column could only ever hold NULL. Remove Type::Blob + Value/CellValue::Blob + the base64 CSV path + the grammar slot + completion/render/type-change/seed handling + the binder refusal (whose message also carried a user-facing "DSL" copy-rule bug). The vocabulary is now nine types; base64 stays (clipboard OSC-52). Backward compat is the ADR-0015 migration framework's first real use: a v1->v2 format bump (CURRENT_SCHEMA_VERSION across serializer/parser/skeleton) with a migrator that rewrites `type: blob` -> `type: text`, and a forced .db rebuild from the migrated text when a blob column was actually converted (the stale .db keeps a STRICT BLOB engine column). Conversion to text is non-destructive and CSV-free. Covered by a full-stack integration test and a Tier-4 PTY test that opens a real legacy v1-blob project. Also sweeps the nine-type vocabulary through CLAUDE.md, requirements.md, the website (type reference, seed doc, highlight grammar), and the ADR-0030/0033/ 0035 cross-references; CHANGELOG Removed entry; handoff-79. BREAKING CHANGE: the `blob` column type is removed. Existing projects that declared a blob column are migrated on first open (the column becomes text; the original project.yaml is kept as a .v1.bak).
This commit is contained in:
+25
-3
@@ -176,6 +176,13 @@ pub async fn run(args: Args) -> Result<()> {
|
||||
// `project.yaml.v<N>.bak` breadcrumb on disk; that's
|
||||
// sufficient v1 UX and lets us defer dedicated event
|
||||
// plumbing until a real migrator demands it.
|
||||
// ADR-0005 Amendment 2: note a pre-migration `blob` column so we can
|
||||
// force a `.db` rebuild after the v1→v2 migration converts it to text
|
||||
// (the stale `.db` keeps a `STRICT … BLOB` engine column + `"blob"`
|
||||
// metadata that load otherwise uses as-is).
|
||||
let had_blob_column = std::fs::read_to_string(project.path().join(crate::project::PROJECT_YAML))
|
||||
.map(|b| crate::persistence::migrations::body_declares_blob_column(&b))
|
||||
.unwrap_or(false);
|
||||
let migrate_registry = crate::persistence::migrations::MigratorRegistry::production();
|
||||
let migration_outcome = crate::persistence::migrations::ensure_project_yaml_migrated(
|
||||
project.path(),
|
||||
@@ -235,7 +242,12 @@ pub async fn run(args: Args) -> Result<()> {
|
||||
Database::open_with_persistence_and_undo(db_path.as_path(), persistence, undo_enabled)
|
||||
.context("open database")?;
|
||||
let mut initial_events: Vec<AppEvent> = Vec::new();
|
||||
if !db_existed {
|
||||
// ADR-0005 Amendment 2: a blob→text migration leaves the existing
|
||||
// `.db` with a stale `STRICT … BLOB` engine column, so rebuild it from
|
||||
// the migrated text. A pure version bump with no schema change does not.
|
||||
let force_rebuild_after_migration =
|
||||
migration_outcome.migrated_from.is_some() && had_blob_column;
|
||||
if !db_existed || force_rebuild_after_migration {
|
||||
match database.rebuild_from_text(project_path.clone(), None).await {
|
||||
Ok(()) => {
|
||||
// Surface the silent rebuild as a system note
|
||||
@@ -927,7 +939,13 @@ async fn perform_switch(
|
||||
// state momentarily, but the next user action will
|
||||
// surface the error and they can retry).
|
||||
let migrate_registry = crate::persistence::migrations::MigratorRegistry::production();
|
||||
crate::persistence::migrations::ensure_project_yaml_migrated(
|
||||
// ADR-0005 Amendment 2: see the matching note in `run()` — a blob→text
|
||||
// migration needs the `.db` rebuilt from the migrated text.
|
||||
let had_blob_column =
|
||||
std::fs::read_to_string(new_project.path().join(crate::project::PROJECT_YAML))
|
||||
.map(|b| crate::persistence::migrations::body_declares_blob_column(&b))
|
||||
.unwrap_or(false);
|
||||
let migration_outcome = crate::persistence::migrations::ensure_project_yaml_migrated(
|
||||
new_project.path(),
|
||||
&migrate_registry,
|
||||
)
|
||||
@@ -949,7 +967,11 @@ async fn perform_switch(
|
||||
let new_database =
|
||||
Database::open_with_persistence_and_undo(&db_path, persistence, undo_enabled)
|
||||
.map_err(|e| e.to_string())?;
|
||||
if !db_existed && let Err(e) = new_database.rebuild_from_text(new_path.clone(), None).await {
|
||||
let force_rebuild_after_migration =
|
||||
migration_outcome.migrated_from.is_some() && had_blob_column;
|
||||
if (!db_existed || force_rebuild_after_migration)
|
||||
&& let Err(e) = new_database.rebuild_from_text(new_path.clone(), None).await
|
||||
{
|
||||
return Err(e.friendly_message());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user