All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
- Parquet — Bundle-Preflight-Exit-Codes folgen jetzt AP12 §9
(2026-06-09, Parquet Cut A S9a-0) — die CLI-Exit-Codes fuer
Parquet-Bundle-Fehler entsprechen jetzt dem AP12-§9-Vertrag. Fuer
Skripte, die auf konkrete Exit-Codes pruefen, aendert sich:
MANIFEST_*-Fehler (fehlendes/ungueltigesmanifest.yaml, SHA-256-Mismatch, Kollisionen) → Exit 4 (vorher Exit 3).tableFilter/tableOrder-Fehler → Exit 5 mit den stabilen CodesBUNDLE_FILTER_UNKNOWN_TABLE,BUNDLE_ORDER_DUPLICATE,BUNDLE_ORDER_UNKNOWN_TABLE,BUNDLE_ORDER_INCOMPLETE(vorher Exit 2 mit irrefuehrendem CodeMANIFEST_FILE_MISSING).- Ein partieller
tableOrderist jetzt ein harter Fehler (BUNDLE_ORDER_INCOMPLETE) statt stillschweigend nicht-gelistete Tabellen zu droppen (Bugfix). - Per-Tabelle-Importfehler eines Bundle-Laufs geben jetzt den
stabilen Code
BUNDLE_TABLE_IMPORT_FAILED: table='…' cause='…'aus (Exit 5 unveraendert). Die Exit-Codes 4/5 sind mit Connection-/Streaming-Fehlern geteilt; der stderr-Code-Praefix unterscheidet. JSON/YAML/CSV/Single-File-Importe sind nicht betroffen. --resume-Abbrueche eines Bundle-Imports geben jetzt feldweise benannte Codes statt einer generischen „fingerprint mismatch"- Meldung (Exit 3 unveraendert):BUNDLE_FORMAT_VERSION_INCOMPATIBLE_WITH_CHECKPOINT,BUNDLE_MANIFEST_CHANGED_SINCE_CHECKPOINT,BUNDLE_TABLE_ORDER_CHANGED(AP8 §8.4). Reine Diagnose-Verfeinerung — Skripte, die auf den alten stderr-Text geprueft haben, muessen die neuen Codes lesen.
- Parquet — Pre-0.9.8-Bundle-Checkpoints sind nach 0.9.8 nicht
mehr wiederaufnehmbar (2026-06-09, Parquet Cut A S8) —
Pre-0.9.8-Parquet-Checkpoints fuer Bundle-Importe sind nach
0.9.8 nicht mehr wiederaufnehmbar (Fehlercode
BUNDLE_CHECKPOINT_MISSING_BUNDLE_FINGERPRINT). DerImportCheckpointManagerverlangt fuer einen Parquet-Bundle-Resume jetzt den in 0.9.8 eingefuehrtenBundleCheckpointSpecifics- Resume-Fingerprint im Manifest; ein Manifest ohne diesen Block kann einen laufenden Parquet-Bundle-Import nicht fortsetzen. JSON/YAML/CSV-Checkpoints und Single-File-Importe ohne vorherigen Checkpoint sind nicht betroffen. Pre-0.9.8-Parquet-Bundle- Checkpoints existierten in der Wildnis nicht (Parquet kam mit 0.9.8 live); der Bruch ist defensiv im Code, nicht praktisch spuerbar.
- Parquet — Single-File-Resume funktioniert jetzt (2026-06-09) —
ein
--resumeeines Single-File-Parquet-Imports scheiterte bisher immer mitBUNDLE_CHECKPOINT_MISSING_BUNDLE_FINGERPRINT(Exit 3), weil der Erstlauf den Content-SHA-256 nicht persistierte (er wurde nur bei--resumeberechnet). Der Hash wird jetzt auch beim Erstlauf berechnet, wenn ein Checkpoint-Verzeichnis aktiv ist (--checkpoint-dir), sodass ein späterer--resumeihn validieren kann (PARQUET_SINGLE_FILE_CONTENT_CHANGED_SINCE_CHECKPOINTbei geänderter Datei). Hinweis: derzeit nur mit explizitem--checkpoint-dirvoll wirksam (config-onlypipeline.checkpoint.directoryist Folge-Scope).
- Parquet — Scope-/Versions-Korrektur: Cut A statt Cut B,
Ziel 0.9.8 statt 1.0.0 (2026-06-06) — AP13 §5.4 / §7
hatten am 2026-06-05 „Cut B (Bundle-only Pilot) als 1.0.0"
empfohlen; Stakeholder-Entscheid e7f3f714 folgte dieser
Empfehlung. Am 2026-06-06 wurde diese Empfehlung in
parquet-decision-template.md§8 superseded auf Cut A (Voller Vertrag) als 0.9.8. Begruendung: Operator-Mehrwert (Spark-/Hive-/DuckDB- Dateien direkt importieren) macht Cut B zu eng, sobald der Bundle-Pfad steht; die Versionsabsenkung auf 0.9.8 macht den 1.0-Stamp explizit zum Engineering-Reife-Punkt (Native-Image, Distributions-Cut, Hadoop-Footprint- Minimierung) statt zum Feature-Vollstaendigkeits-Stamp. Folge-Releases (AP13 §8.3): 1.0.0 = Native-Image-Cut- Distributions-Cut (Default-JAR vs.
--parquet-Variante) - optionaler Hadoop-API-Shim + Hadoop-Footprint- Minimierung (vormals 1.2.0-Scope plus §6.2-Distributions- Frage plus der aus dem Umbrella herausgehaltene Footprint-Cut); 1.0.0+ / spaeter = MCP-Server- Spiegelung; vormaliger 1.1.0-Single-File-Scope entfaellt (Bestandteil von 0.9.8).
- Distributions-Cut (Default-JAR vs.
- Parquet-Plan-Doc nach
done/migriert — nur Evaluierungsphase abgeschlossen (2026-06-05) — perin-progress/-Konvention (ADR-0004) wandernparquet-export-import-evaluation.mdund alle zehn Sub-Docs (AP1-AP13) nachdocs/planning/done/, weil die Evaluierungsphase abgeschlossen ist. Hauptplan bekommt eine## Closure (2026-06-05)-Sektion mit Commit-Tabelle aller 16 Parquet-Commits in chronologischer Reihenfolge (a9bf941e Plan-Refresh, 4 Code-Spike-Commits AP3-AP6, 10 Sub-Doc-Commits AP7-AP13 inkl. AP10-Befund-Rueckspiel, 1 Stakeholder-Entscheid e7f3f714), fuenf verbleibenden Pre-Implementation- Aufgaben (Engineering-Goal, Native-Image-Smoketest, Sealed-rg-Sweep, Branch-Anlage, erster Implementierungs- Commit) und Folge-Threads. Cross-Verweise auf den altenin-progress/-Pfad sind in CHANGELOG, Spec, Sub-Docs, AP3-Spike-Tests undgradle.propertiesaktualisiert. Wichtig: die Closure deckt die Plan-Doc-/Spike-Phase ab, nicht den produktiven Code; der Spike unteradapters/driven/formats-parquet/src/main/kotlin/.../spike/bleibt, der produktive Pfad (SeekableDataChunkReaderFactory,SeekableChunkSource,ParquetChunkReader/Writer,DataExportFormat.PARQUET, CLI---format parquet, Dependency-Hygiene, Hadoop- Footprint-Inventar fuer 1.0.0-Input) laeuft als Per-Feature-Umbrellaparquet-productive-cut-a.mdunterin-progress/. Trigger sind vier Befunde aus Code-Sichtung 2026-06-06 (DataExportFormat.kt:10ohnePARQUET,DataImportCommand.kt:41.choice("json", "yaml", "csv"), transitivesorg.apache.avro:avro:1.9.2ueber Hadoop, restliche Hadoop-Transitive HDFS/YARN/Jersey/ reload4j/Zookeeper/Netty). Der Umbrella deckt nach AP13 §8 Cut A (Voll-Scope) ab.
-
S3-kompatibler Object-Storage fuer die MCP-Byte-Stores (S3.0..S3.6) (2026-06-09–2026-06-12) —
mcp servekann Upload-Segmente und Artefakt-Bytes statt im lokalen State-Dir in einem S3-kompatiblen Object-Storage ablegen (AWS S3, SeaweedFS u. a.):- Adapter: neues Modul
adapters:driven:storage-s3mitS3ArtifactContentStore+S3UploadSegmentStore(AWS SDK v2 miturl-connection-client; SHA-256-Idempotenz viax-amz-meta-sha256, Multipart-Pfad > 8 MiB, paginierte Listen-/Batch-Delete-Operationen, ChecksumsWHEN_REQUIREDfuer SeaweedFS-Kompatibilitaet). - Konfiguration: neue
artifacts-Sektion der.d-migrate.yaml(store: file|s3; beis3:endpoint/bucket/region/prefix/pathStyle). Credentials stehen NIE im YAML — sie kommen aus der Env (AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEYviaDefaultCredentialsProviderChain); Logs/stderr nennen endpoint und bucket, nie Credentials. Fehlende Sektion → file-backed (Default, Bestandsverhalten unveraendert). - Wiring/Retention:
artifacts.store: s3selektiert die S3-Stores immcp serve-Pfad; der file-basierte Orphan-Sweep fuer segments/artifacts wird uebersprungen (der lokale Assembly-Spool und sein Cleanup bleiben). Der geteilte S3-Client wird beim Shutdown ueber den neuenMcpRuntimeWiring.ownedResources-Mechanismus geschlossen. - Tests: SeaweedFS-Vertragssuiten + Multipart-IT, Wiring-IT
(YAML → Composition-Root → Round-Trip) und MCP-Protokoll-E2E mit der
echten CLI als Subprozess (
McpS3SubprocessE2ETest). - Footprint: Release-JAR +8,02 MiB (AWS SDK, nur
url-connection-Transport — kein Netty/Apache aus dem SDK).
ImpPlan mit Slice-Tabelle und Bewusst-nicht-Scope:
ImpPlan-0.9.8-object-storage-s3.md. - Adapter: neues Modul
-
Parquet als Export-/Import-Format (Cut A, S0..S9b) (2026-06-06–2026-06-08) —
d-migrate data export --format parquetunddata import --format parquetsind produktiv. Voller Vertrag (AP13 §8 „Cut A"):- Reader/Writer:
ParquetChunkReader/ParquetChunkWriter+ParquetSeekableDataChunkReaderFactoryim Moduladapters:driven:formats-parquet; neuesDataExportFormat.PARQUET. DieDefault…Factorybleibt Parquet-/Hadoop-frei (Contract-Branch). - Bundle (Multi-Tabelle):
manifest.yaml+ stabile Dateinamen (ParquetManifestWriter); Single-File: Footer-KVd-migrate.manifestmit Phase-1/2-Preflight. - CLI:
--format parquetfuer Im- und Export,CompositeDataChunkWriterFactory-Wiring, Pfad-only/Stdin-Ablehnung; Checkpoint/Resume fuer Bundle und Single-File; DuckDB-/Arrow- Interop durch KV-Toleranz-Tests verifiziert. - Abgrenzung: keine Lakehouse-Formate (Iceberg/Delta/Hudi); Hadoop-Footprint-Minimierung ist 1.0.0-Folgeinput (S10a-Snapshot).
Per-Feature-Umbrella mit vollstaendiger Sub-Slice-Tabelle:
parquet-productive-cut-a.md§3.4. Slice-Lead-Commits in chronologischer Reihenfolge (Headline-Commit pro Slice; vollstaendige Sub-Commit-Listen im Umbrella): - Reader/Writer:
| Datum | Commit | Slice |
|---|---|---|
| 2026-06-06 | 9c840986 |
S0 — ChunkSchema/ChunkColumnSchema/SchemaOrigin |
| 2026-06-06 | 7670a393 |
S0b — JDBC→NeutralType-Mapping |
| 2026-06-06 | 40d7c551 |
S2 — SeekableDataChunkReaderFactory-Port |
| 2026-06-06 | 2b5826d8 |
S10a — Avro-Hygiene + Footprint-Inventar |
| 2026-06-06 | 0a992c0c |
S3 — ParquetChunkReader/Writer (produktiv) |
| 2026-06-06 | 9ba956ff |
S10b — Native-Image-Befund |
| 2026-06-06 | 97c74757 |
S3b — ParquetManifestWriter + Bundle-Closure |
| 2026-06-06 | 28048ef2 |
S4 — Single-File-Footer-KV (Phase 1/2) |
| 2026-06-06 | 24cbf4c5 |
S5a — ParquetBundlePreflight + Resolver |
| 2026-06-06 | 4279c326 |
S5b — ImportInput.ResolvedSingleFile |
| 2026-06-07 | 7759294d |
S6 — CLI-Wiring Im-/Export (+ 4 Folge-Commits) |
| 2026-06-07 | 34eea7ce |
S7 — End-to-End (+ Sub-Slices a–e) |
| 2026-06-07 | df733244 |
S8 — Checkpoint-Erweiterung Bundle/Single-File |
| 2026-06-08 | 7808968c |
S9a-0 — Exit-Code-Vertrag (AP12 §9) |
| 2026-06-08 | 31f1f6ef |
S9a — Bundle-Tests (+ 3 Folge-Commits) |
| 2026-06-08 | c687b47c |
--table-order-Flag (S9a-Folge-Feature) |
| 2026-06-08 | 3306808e |
S9b-0 — Single-File-Format-Codes → Exit 4 |
| 2026-06-08 | 591493f3 |
S9b — Single-File-Tests (+ Resume-Fix) |
-
--table-order-Flag fuerdata import(2026-06-09) — explizite Import-Reihenfolge fuer Directory-/Bundle-Quellen (kommagetrennt, analog--tables). Beim Ordering authoritative gegenueber dem--schema-FK-Topo-Sort:--schemavalidiert dann nur noch (Praezedenz--table-order> Schema-Topo-Sort > Discovery). Macht die Parquet-Bundle-Order-Codes (BUNDLE_ORDER_DUPLICATE/BUNDLE_ORDER_UNKNOWN_TABLE/BUNDLE_ORDER_INCOMPLETE, Exit 5) CLI-erreichbar. Usage-Fehler (--table-orderauf stdin/single-file, Konflikt mit--table, kaputte Syntax) → Exit 2. -
Parquet-Evaluierung — Stakeholder-Entscheid: Go fuer Cut B als 1.0.0 (2026-06-05) — alle fuenf offenen Punkte aus
parquet-decision-template.md§6 sind beantwortet (durchgaengig nach AP13-Empfehlung):- Release-Branch:
feature/parquet-1.0mit Schritt-fuer-Schritt-Commits, Merge indevelopnach Schritt 9. Begruendung: Schritt 1 (Enum) ohne Schritt 6 (CLI-Wiring) hat keinen Funktionsnutzen. - Distributions-Cut: Parquet immer im Default-JAR (1.0/1.1). Eine separate Variante wird im 1.2-Cut zusammen mit Native-Image reevaluiert.
- DuckDB-/Arrow-Tests: bleiben
testImplementationplus CI-Smoke-Lauf (AP4/AP5/§11.4); kein Heraufstufen zu Pflicht-Tests. - MCP-Server-Spiegelung: nicht in 1.0.0; eine MCP-Exposition von Parquet-Bundle-Export/-Import wird fruehestens beim 1.1-Planning entschieden.
- Hadoop-API-Shim: Entscheid „eigener Shim vs.
hadoop-common-Subset pinnen" wird zusammen mit dem 1.2-Cut anhand der Native-Image-Smoketest-Daten getroffen — kein harter Vorab-Termin, Datengrundlage zuerst.
Damit ist die Bedingung „offene Punkte beantwortet" aus AP13 §7 erfuellt; verbleibende Pre-Implementation-Schritte (Engineering-Zeitbudget-Commit, Native-Image-Smoketest in AP12-Schritt 3 verankern, Sealed-
rg-Sweep in PR-Checkliste) sind Engineering-Aufgaben beim Cut-B-Start, keine offenen Entscheidungsfragen. Plan-Doc und Sub-Docs wandern bei Cut-B-Start nachdocs/planning/done/. - Release-Branch:
-
Parquet-Evaluierung — AP13 Entscheidungsvorlage (2026-06-05) — neuer Sub-Doc
parquet-decision-template.mdsynthetisiert die AP1-AP12-Evaluierung in eine Go/No-Go- Vorlage. Damit ist die Plan-Doc-Phase abgeschlossen. Inhalt:- Aufwandschaetzung pro AP12-Implementierungsschritt mit
Bundle-/Single-File-Split (elf Tabellen-Eintraege —
1, 2, 3, 3b neu fuer
ParquetManifestWriter+StreamingExporter-Bundle-Closure, 4, 5a Bundle, 5b SingleFile, 6, 7, 8, 9a Bundle-Tests, 9b SingleFile- Tests). Voll-Scope: 29.5-47 PT netto, 38-65 PT brutto inkl. Review-Zyklen. Cut B (Bundle-only): 20.5-32.5 PT netto, 27-45 PT brutto. Plus Folgekosten: 5-15 PT Native-Image, 5-10 PT optionaler Hadoop-API-Shim, 2-3 PT Doku. Kalenderaufwand bei Vollzeit: Voll-Scope 8-15 Wochen, Cut B 5-9 Wochen. - Risiko-Gesamtbild in vier Gruppen:
- Wahrscheinlich-und-aufwaendig: Native-Image-Reachability,
Hadoop-Footprint im JAR, Sealed-Sweep-Vollstaendigkeit
(mit konkreten Lueckenkategorien —
else-Zweige, nicht-exhaustivewhenohne Ausdruckszwang, Reflection-/Service-Loader, Test-Code;rg-Sweep ist Go-Bedingung, nicht durchgradle assembleersetzbar). - Wahrscheinlich-und-billig: CSV-Flag-Skript-Bruch, Auto-Detection-Falle, pre-AP8-Checkpoint-Bruch.
- Unwahrscheinlich-aber-teuer: parquet-java 1.18-Wechsel waehrend Umsetzung, CVE in parquet-hadoop.
- Akzeptierte Restrisiken: semantischer Schema-Drift, Sealed-Modul-Lokalitaet, Single-File-Bundle-Manifest- Asymmetrie.
- Wahrscheinlich-und-aufwaendig: Native-Image-Reachability,
Hadoop-Footprint im JAR, Sealed-Sweep-Vollstaendigkeit
(mit konkreten Lueckenkategorien —
- Drei gestaffelte Scope-Cuts mit klarer Empfehlung:
- Cut A (1.0.0 voller Vertrag) — alle neun Schritte; maximaler Operator-Mehrwert, traegt die volle Single-File-Phase-1/2-Komplexitaet im 1.0-Risiko.
- Cut B (1.0.0 Bundle-Pilot, empfohlen) —
Bundle-Export inklusive
ParquetManifestWriter+StreamingExporter-Bundle-Closure (Schritt 3b) + Bundle-Import + Bundle-Resume, Single-File scheitert mitPARQUET_SINGLE_FILE_NOT_YET_SUPPORTED;--no-checkpointnicht eingefuehrt. Folge-Release 1.1.0 (11-20 PT brutto) ergaenzt Single-File +--no-checkpoint; 1.2.0 (10-25 PT) den Native-Image-Cut + optionalen Hadoop-API-Shim. - Cut C (1.0.0 Bundle ohne Resume) — schnellster
Pilot, aber Wertversprechen gegenueber
pg_dump | psqlzu duenn; verworfen.
- Fuenf offene Punkte, die vor der Implementierung geklaert sein muessen (Release-Branch-Strategie, Gradle-Distributions-Cut, DuckDB-/Arrow-Test-Status in CI, MCP-Server-Spiegelung, Hadoop-API-Shim-Folge- Entscheidungsdatum).
- Empfehlung: Go mit Cut B als 1.0.0, unter den
Bedingungen: offene Punkte beantwortet,
Engineering-Goal committed, Native-Image-Smoketest in
Schritt 3 statt spaeter. Wenn No-Go: Plan-Doc und
Sub-Docs unveraendert nach
docs/planning/done/, Spike-Modul bleibt im Repo.
- Aufwandschaetzung pro AP12-Implementierungsschritt mit
Bundle-/Single-File-Split (elf Tabellen-Eintraege —
1, 2, 3, 3b neu fuer
-
Parquet-Evaluierung — AP12 CLI- und Factory-Wiring-Skizze (2026-06-05) — neuer Sub-Doc
parquet-cli-wiring.mdzieht alle AP1-AP11-Vorentscheidungen ins konkrete CLI- und Wiring-Bild. Implementierungsfertiges Skelett, das nach AP13 (Entscheidungsvorlage) umgesetzt werden kann; trifft selbst keine neuen Architekturentscheidungen, verteilt die bestehenden auf die richtigen Code-Stellen.Inhalt (Sektionen 3-12):
DataExportFormat.PARQUETals additive Enum-Erweiterung; Clikt---format-Choice automatisch ueberentries.map { cliName }.- Neue Parquet-Flags:
--manifest-sha256(Export, opt-in, aktiviert AP7-Per-Datei-SHA-256),--no-checkpoint(Import, schaltet die Single-File-Content-Hash- Berechnung inphase1aus und unterdrueckt alleFileCheckpointStore-Schreiboperationen). Konflikt--no-checkpoint+--resume <ref>-> Exit 2 (CHECKPOINT_OPTIONS_CONFLICT). - CSV-Flag-Ablehnung bei
--format parquet(CSV_FLAG_INVALID_FOR_PARQUET): nur bei explizit gesetzten Flags, nicht bei Default-Werten. Dafuer wird--csv-null-stringauf nullableString?umgestellt (kein.default("")-Maskerade-Problem); einnull -> ""-Mapping passiert an genau einer Stelle imCsvChunkReader/Writer-Konstruktor.--encodingsilent-ignored (Skript-Kompatibilitaet, Schluesselargument: Shell-Skripte iterieren oft mehrere Formate mit globalem--encoding). - Format-Auto-Detection-Reihenfolge:
--format> Verzeichnis-manifest.yaml> Endungs-Inferenz (.parquetneu inEXTENSION_FORMAT_MAP). --table-Precedence fuer Single-File-Parquet aus AP11 §5.5 (Mismatch- und Required-Fehler).- Factory-Wiring:
DataImportWiringinstanziiertDefaultDataChunkReaderFactory()undParquetSeekableDataChunkReaderFactory(),StreamingImporter-Constructor bekommtseekableReaderFactoryals Pflichtparameter (AP10-Befund-Rueckspiel).DefaultDataChunkWriterFactorybekommtPARQUET -> ParquetChunkWriter(...)-Zweig. - Bundle-/Single-File-Adapter-CLI-Skelette:
ParquetBundlePreflight.run(bundleRoot, requireSha256Verify = !request.resume.isNullOrBlank())ParquetBundleResolver+ParquetBundleAdapter. toResolvedBundle(resolver). Single-File bekommt einen zwei-phasigen Preflight: Phase 1 inresolveRequest(vor DB-Connect) liest Footer-KV und macht Tabellen-Resolution; Phase 2 inrunImport(nachconnect()) baut dieChunkSchemamit Live- Target-Schema-Zugriff. Neuer port-eigener Sealed-SubtypImportInput.ResolvedSingleFile(table, path, schema, expectedSha256, resumeFingerprint)analog zu AP9-ResolvedBundle; machtResolvedTableInput.Seekableaus demImportInputResolverableitbar ohne neuen Schema-Slot inSchemaPreflightResult/ImportExecutionContext.
- Writer-Wiring: separate
ParquetChunkWriterFactoryim Parquet-Adapter-Modul plusCompositeDataChunkWriterFactoryim CLI-Modul.DefaultDataChunkWriterFactoryinadapters:driven:formatsbleibt Hadoop-frei; der generische formats-Stack zieht den Parquet-Adapter nicht transitiv. - Checkpoint-Persistenz:
FileCheckpointStore.toMap/fromMapserialisiertoperationSpecificmitkind-Diskriminator (parquet-bundle->BundleCheckpointSpecifics,parquet-single-file->SingleFileCheckpointSpecifics). Kein Schema-Versionsbump (Feld optional, pre-AP8-Checkpoints fuer JSON/YAML/CSV bleiben funktional).ImportCheckpointManager.validateManifestbekommt zwei neue Validierungsmethoden (validateBundleResume,validateSingleFileResume), der heutige Pfad bleibt fueroperationSpecific == nullerhalten — bricht aber bei Parquet-Bundle- bzw. Single-File-Resume strukturell mitBUNDLE_CHECKPOINT_MISSING_BUNDLE_FINGERPRINT.buildCallbacks/saveManifestreicht den Fingerprint bei jedem Update durch (AP9-Befund- Rueckspiel). InputContextumbundleExpectedSha256ByTable: Map<String, String?>?undsingleFileContentSha256: String?erweitert.- Vollstaendige Sealed-Sweep-Liste fuer fuenf
Hierarchien (
ImportInput,SchemaOrigin,SeekableChunkSource,CheckpointOperationSpecifics,DataExportFormat) mit konkretemrg-Suchmuster aus AP9 §7.8 plus bekannten Stellen (ImportInputResolver,ImportPreflightValidatorZ. 105/113/118). - Exit-Code-Mapping fuer alle in AP7-AP11 definierten Fehlerklassen, gruppiert in Familien (Manifest/Bundle/Resume/SingleFile-Parquet/Reader/ Checkpoint/CLI-Flag) mit Begruendung pro Exit-Code-Wahl.
- Native-Image-Strategie: GraalVM-Reachability-Metadaten
sind Pflicht; eigener Hadoop-API-Shim (parquet-libraries.md
§8) ist erst nach GraalVM-Smoketest noetig — bis dahin
hadoop-common+hadoop-mapreduce-client-corebelassen. AP6-Befundfs.file.impl.disable.cache=truean genau einer Stelle (ParquetHadoopConfigBuilder). - Test-Strategie: sechs Pflicht-Familien (CLI-Preflight,
Format-Resolver mit
--format json+manifest.yaml- Vorrang, Resume-Akzeptanz, DuckDB-/Arrow-KV-Toleranz erweitert die AP4/AP5-Linien, Sealed-Sweep als Build-Zeit-Dokumentation). - Bindender Implementierungsplan in neun entkoppelten
Schritten (Enum-Erweiterung > Port > Reader-/Writer-
Implementation > Single-File-Adapter > Bundle-Adapter
CLI-Wiring > Resolver-Integration > Checkpoint > Tests). Big-Bang-Commit explizit abgelehnt.
AP13 (Entscheidungsvorlage) entscheidet, welche der neun Implementierungs-Schritte im 1.x-Cut zwingend sind.
-
Parquet-Evaluierung — AP11 Single-File-Metadatenvertrag (2026-06-05) — neuer Sub-Doc
parquet-single-file-metadata.mdfixiert den letzten Vertragspunkt vor AP12/AP13. Inhalt:- Drei Optionen aus Hauptplan §6 verglichen
(Footer-KV vs. Sidecar vs. Footer-only); bindende Wahl
Option A — Footer-Key-Value-Metadaten mit Key
d-migrate.manifest. parquet-java 1.17.1 unterstuetzt das verlaesslich viawithExtraMetaData/getKeyValueMetaData(parquet-libraries.md§3.1). - Value-Format: UTF-8-YAML-Bytestrom als strikte
Teilmenge des AP7-Bundle-Manifests
(
parquet-manifest-format.md§5). Genau eintables[]-Eintrag, ohnefile(Datei kennt sich selbst) und ohnesha256(Hash ueber den eigenen Bytestrom inkl. Hash-Feld waere zirkulaer). - Fremde Parquet-Dateien ohne
d-migrate.manifest-Key bleiben lesbar als best-effort: Reader baut dieChunkSchemaaus Footer-MessageType+ Ziel-JDBC- Schema; CLI-Warnung weist auf moeglichen Decimal-/ Temporal-Verlust hin. Damit kein Bruch mit Spark-/Hive- erzeugten Parquet-Dateien. - Sidecar (Option B) abgelehnt: bricht das Single- Artefakt-Versprechen und fuehrt zwei parallele Manifest-Vertraege ein. Footer-only (Option C) abgelehnt: verletzt AP2 §4.4 (Schema-vor-Chunk).
- Stdin-Single-File-Import bleibt unzulaessig
(
parquet-libraries.md§7); CLI-Preflight wirftPARQUET_STDIN_NOT_SUPPORTED(AP12-Fehlercode). - Code-Konsequenzen: neue Klassen
ParquetSingleFileManifestWriter/ReaderplusParquetSingleFilePreflightim Parquet-Adapter. Der Preflight ist die einzige Stelle mit Footer-Parsing — der Streaming-Layer (adapters:driven:streaming) bleibt parquet-frei und sieht nur die port-neutraleResolvedTableInput.Seekable(table, source, schema, expectedSha256)-Variante (AP10 §5.4). Dadurch teilen Bundle- und Single-File-Pfad denselbenTableImporter-Zweig. - Tabellennamens-Aufloesung mit klarer Precedence:
--tablegewinnt; bei vorhandenem Footer-KV + Mismatch ->PARQUET_SINGLE_FILE_TABLE_MISMATCH; bei--tablefehlt + Footer-tables[0].tablevorhanden -> Footer-Wert wird mitstderr-Note uebernommen; bei beidem fehlend ->PARQUET_SINGLE_FILE_TABLE_REQUIRED. Die heutige--table-Pflicht fuer JSON/YAML/CSV- Single-File bleibt unveraendert. - Resume fuer Single-File: AP8 §8.1 verbietet Resume
ohne Datei-Hash. Da der Footer-KV keinen Hash tragen
kann (zirkulaer ueber den eigenen Bytestrom), wird der
SHA-256 ueber den gesamten Dateibytestrom im
Preflight berechnet und im Checkpoint via neuer
SingleFileCheckpointSpecifics(bundleKind= "parquet-single-file", contentSha256, table) : CheckpointOperationSpecifics-Variante persistiert. Mismatch beim Resume ->PARQUET_SINGLE_FILE_CONTENT_CHANGED_SINCE_CHECKPOINT. parquet-manifest-format.md§5.2 Korrektur:tables[].fileist jetzt bedingt Pflicht — Pflicht im Bundle-Manifest, optional im Single-File-Footer-KV. Das macht das AP11-YAML zu einer konditionell strikten Teilmenge des Bundle-YAML (ein Parser-Pfad, Kontext-Validierungsregel statt zweites Schema).
Implementierungscode folgt nach AP12.
- Drei Optionen aus Hauptplan §6 verglichen
(Footer-KV vs. Sidecar vs. Footer-only); bindende Wahl
Option A — Footer-Key-Value-Metadaten mit Key
-
Parquet-Evaluierung — AP10 Stream-vs-Datei-Portentscheidung (2026-06-05) — neuer Sub-Doc
parquet-port-shape.mdhebt die Vorentscheidung ausparquet-libraries.md§7 in eine bindende Reader-Port-Skizze. Inhalt:- Neuer Port
SeekableDataChunkReaderFactoryinhexagon:ports-readparallel zur bestehendenDataChunkReaderFactory— bewusst Format-agnostisch (PARQUETheute, kuenftige seekable Formate ohne Vertragsbruch). Kein vereinheitlichter Port, weil dasInputStream/Path-Variantenproblem dann den Temp-Spool-Pfad wiedereroeffnen oder das schwaechere Schema- Inferenz-Modell durchsetzen wuerde. SeekableChunkSourceals sealed interface mit konkretemLocal(path: Path)-Subtyp; reineInputStream-Quellen sind bewusst nicht Teil der Hierarchie (kein impliziter Temp-Spool,parquet-libraries.md§7 Bullet 2).ChunkSchemaist Pflichtparameter dercreate(...)-Signatur; der Reader vertraut dem Schema aus dem Bundle-Preflight und rekonstruiert nicht aus dem Datei-Footer.- Writer-Seite bleibt unveraendert stream-basiert; der
Parquet-Writer wraps den
OutputStreamintern in einenPositionOutputStream/OutputFile-Adapter (stdout-tauglich). Kein neuer Writer-Port. - Default-Implementation
ParquetSeekableDataChunkReaderFactorylebt imadapters:driven:formats-parquet-Modul; nutzt parquet-java 1.17.1 plus die Hadoop-API-via-LocalFileSystem- Befunde aus §5.1/§7/§8. Bewusstpublic class(keininternal), parallel zur Konvention vonDefaultDataChunkReaderFactory, damit CLI/MCP die Factory direkt instanziieren koennen. - Minimaler Footer-vs-ChunkSchema-Konsistenzcheck im Reader
(Spaltenanzahl + -namen) mit Fehler
BUNDLE_SCHEMA_PARQUET_MISMATCH; schliesst die Luecke, die der opt-in AP7-Live-Hash nicht abdeckt. Vollstaendige Typgleichheits-Pruefung bewusst nicht — akzeptiertes Restrisiko (semantischer Drift). - Sealed-Erweiterungsregel explizit: neue
SeekableChunkSource-Varianten kommen additiv ins Port-Modulhexagon:ports-readund brechen exhaustivewhen-Konsumenten bewusst. Externe Module koennen die Sealed-Hierarchie nicht selbst erweitern; das ist gewollt, weil ein offenes Interface den InputStream-/Temp-Spool-Pfad wiedereroeffnen wuerde. - Migrations-Analyse fuer sechs Module mit konkretem Sweep:
StreamingImporter-Constructor (Z. 21-28) bekommt die zweite Factory-Referenz und reicht sie an den internenTableImporterdurch — Pflichtparameter (keinnull- Default), weil ein vergessenes Wiring beiformat=PARQUETzur Laufzeit eine schlecht diagnostizierbare NPE waere. AP12 macht das Wiring.
Implementierungscode folgt nach AP12.
- Neuer Port
-
Parquet-Evaluierung — AP9 Importpfad-Vertrag (bindende DTO-Wahl) (2026-06-05) — neuer Sub-Doc
parquet-import-input-dto.mdhebt die AP7-/AP8-Vorentscheidungen in die Implementierungs- Entscheidung. Inhalt:- Bindung auf neuen Sealed-Subtyp
ImportInput.ResolvedBundlestatt Magic-Field-Erweiterung vonImportInput.Directory; Kotlin-Skelett mitResolvedBundleTableBinding(table, path, schema, expectedSha256)undBundleResumeFingerprint(manifestSha256, formatVersion, producerVersion, tableOrder)inhexagon:ports-write. Bewusst Parquet-frei im Vertrag — der Port spricht nur „Bundle", Adapter befuellen format-spezifisch. - Konkrete
BundleCheckpointSpecifics : CheckpointOperationSpecifics-Variante mit PflichtfeldbundleKind: String(z.B.KIND_PARQUET = "parquet-bundle") plusfingerprint.bundleKindist der serialisierungs- stabile YAML-Diskriminator; unbekannte Werte fuehren infromMapzuCHECKPOINT_OPERATION_SPECIFICS_UNKNOWN_KIND. - Fingerprint-Konsistenz:
BundleResumeFingerprint.tableOrderwird im Translator ausbindings.map { it.table }abgeleitet (nicht als separater Parameter angenommen); damit koennen DTO-Order und Fingerprint-Order strukturell nicht divergieren. - Resume-Enforcement sitzt im
ImportCheckpointManager, nicht im DTO.expectedSha256: String?bleibt nullable (Initial-Lauf ohne--manifest-sha256zulaessig); im--resumeprueft der Manager pro Tabelle und faellt sonst aufBUNDLE_RESUME_REQUIRES_FILE_HASHES. ImportCheckpointManager.buildCallbacks/saveManifest()mussBundleCheckpointSpecificsbei jedem Update fortschreiben (heute bautsaveManifest()ein neues Manifest ohneoperationSpecific-Feld, was den im Initial-Lauf geschriebenen Fingerprint sofort ueberschriebe). Fingerprint ist eine Bundle-Lauf- Invariante.- Sealed-Bruch ehrlich modelliert:
ImportPreflightValidatorhat drei exhaustivewhen (input)(Z. 105-122,effectiveTables/inputTopology/inputPath); AP12 muss sie alle ergaenzen. DTO traegt deshalbbundleRoot: Path, damitinputPathohne externe Lookups ableitbar bleibt. Sweep-Befehl als robustesrg --type kotlin -n 'is ImportInput\.' .plusrg --type kotlin -n 'when \(' . | grep -F 'ImportInput'; einfachesgit grep "when.*ImportInput"verfehlt Stellen wiewhen (val input = ctx.input) { is ImportInput.Stdin -> ... }. - Resume-Enforcement konkret verdrahtet:
InputContext(ImportRunnerTypes.kt:127) bekommtbundleExpectedSha256ByTable: Map<String, String?>?— null fuer JSON/YAML/CSV, Map mit Per-Tabellen-Hash-Status fuer Bundles.ImportCheckpointManager.validateManifest(Z. 93-124) erweitert um drei Bundle-Pruefungen:BundleCheckpointSpecifics-Vorhandensein, Fingerprint- Gleichheit, Per-Tabelle-Hash-Vorhandensein im Resume-Scope (= IN_PROGRESS-Tabellen austableSlices). CLI-Resolver aktiviert den AP7-Preflight-SHA256-Check beim--resumezwangsweise (requireSha256Verify = true). - AP7
ResolvedParquetBundle-DTO im Sub-Doc §4.4 mit konkretem Mindestumfang skizziert (bundleRoot,manifestSha256,formatVersion,producerVersion,schemaSource,tables); diese Adapter-internen Felder waren vorher implizit und werden jetzt explizit referenzierbar gemacht (Translator- Skelett §4.3 nutzt sie direkt). - Adapter-Translator
ParquetBundleAdapterimadapters:driven:formats-parquet-Modul: einzige Stelle, an der adapter-interne Manifest-Begriffe (ResolvedParquetBundle,schemaSource, ...) auf Port-Begriffe abgebildet werden. - Begleitentscheidung 1 (AP2-Erweiterung):
SchemaOrigin- Enum inparquet-schema-source.md§4.4 umMANIFEST_FALLBACKerweitert (additiv,hexagon:ports-common). Semantisch verschieden vonMERGED(„aus mehreren Quellen kombiniert");MANIFEST_FALLBACKmarkiert best-effort-Manifest-Typen ohne SchemaReader-/ JDBC-Provenance. - Begleitentscheidung 2 (AP1-Aufraeumung):
parquet-libraries.md§7.1 Bullet 4 finalisiert. Die urspruengliche AP1-Aussage „ImportInput.Directorywird nicht ersetzt" ist praezisiert, nicht verworfen:Directorybleibt fuer JSON/YAML/CSV und fuer Single-File-Bundles (AP11) erhalten, Multi-Table- Bundles mit verpflichtendemmanifest.yamllaufen ueberImportInput.ResolvedBundle. Die Korrektur-Notiz aus dem AP8-Commit ist damit obsolet und entfernt. - Migrations-/Impact-Analyse fuer sieben Module
(
hexagon:ports-write,hexagon:ports-common,adapters:driven:streaming,adapters:driven:streaming/checkpoint,hexagon:application,adapters:driven:formats-parquet,adapters:driving:cli). JSON/YAML/CSV-Pfade bleiben funktional unveraendert (additivesis ResolvedBundle).
Implementierungscode folgt nach AP12.
- Bindung auf neuen Sealed-Subtyp
-
Parquet-Evaluierung — AP8 manifestgebundene Directory-Import- Aufloesung (2026-06-05) — neuer Sub-Doc
parquet-directory-import.mdals Resolver-Skizze fuer Bundle-Importe. Inhalt:- Aufloesungsmodell mit
ParquetBundleResolver: Wrapper umResolvedParquetBundle,resolve()liefert eineList<ParquetTableBinding>(kein one-shotIterable, weil derStreamingImporterdiscoveredInputs.sizevor der Per-Tabellen-Iteration braucht). - Tabellenordnung primaer aus Manifest-
tables[]-Reihenfolge, optional vontableOrderausImportInputexplizit ueberschrieben (User > Producer).tableFilterist Teilmenge gegen Manifest-Bestand. ChunkSchema-Konstruktion pro Tabelle in drei Stufen (Manifest-neutralType-> JDBC-Hint-Tupel ->sqlTypeName- Heuristik ->BUNDLE_SCHEMA_UNRESOLVED); JDBC-Hints fliessen NUR als Eingaben in die NeutralType-Ableitung ein und werden nicht im neutralenChunkColumnSchemapersistiert (konsistent mitparquet-schema-source.md§4.4).- Strikte Mid-stream-Fehlerbehandlung
(
BUNDLE_TABLE_IMPORT_FAILED): kein Skip-Modus, kein Bundle-weiter Rollback (Atomic-Preserve ist Ziel-DB-Sache). - Resume-Bedingung: erfordert manifestseitige Datei-Hashes
(
tables[].sha256) fuer jede Tabelle im Resume-Scope; sonstBUNDLE_RESUME_REQUIRES_FILE_HASHES. Begruendung: AP7 §7.1 laesst SHA-256 optional, ohne Hash kann eine Dateiaenderung nicht erkannt werden. Resume macht zwei klar getrennte Pruefungen: (P1) AP7-Preflight-SHA256 zwangsweise aktiv — live-berechneter Datei-Digest gegen Manifest-tables[].sha256erkennt Datei-Aenderungen alsMANIFEST_SHA256_MISMATCH; (P2) Checkpoint-Fingerprint (manifestSha256,formatVersion,producerVersion, effektivetableOrder) erkennt Manifest- Edits seit Checkpoint.fileSha256ByTableist bewusst nicht Teil des Checkpoints (redundant zumanifestSha256). - Format-Autodetection:
data import --source <dir>ohne--formatroutet bei vorhandenemmanifest.yamlautomatisch auf Parquet (resolveFormat-Hook vorinferFormatFromExtension). Vorbedingung:DataExportFormat.PARQUETmuss eingefuehrt werden — heute kennt der Enum nur JSON/YAML/CSV (AP12-Vorgriff, im Sub-Doc explizit gemacht). - Code-Konsequenzen: neuer port-eigener Subtyp
ImportInput.ResolvedBundlemitResolvedBundleTableBinding(table, path, schema, expectedSha256)undBundleResumeFingerprint(manifestSha256,formatVersion,producerVersion,tableOrder) — bewusst Parquet-frei, damithexagon:ports-writenicht vonadapters:driven:formats-parquetabhaengt. Adapter haelt sein reichhaltigeresResolvedParquetBundleintern und uebersetzt am Port-Eintritt. - Checkpoint-Persistenz (§10.5): neue konkrete Implementierung
BundleCheckpointSpecifics : CheckpointOperationSpecificswird imhexagon:ports-write-Modul gebraucht — bewusst Parquet-frei im Klassennamen, konsistent zur §10.1- Port-Sauberkeit; der YAML-kind-Diskriminator"parquet-bundle"traegt den Adapter-Bezug.FileCheckpointStoremussoperationSpecificmit-serialisieren (heute ignoriert),ImportCheckpointManager.writeInitialManifestnimmt einen optionalenBundleResumeFingerprint-Parameter durch — beides AP12-Wiring. Schema-Bump aufCURRENT_SCHEMA_VERSION=3ist nicht noetig, weil das Feld optional bleibt und pre-AP8-Checkpoints sich beim Bundle-Resume strukturell mit dem neuen CodeBUNDLE_CHECKPOINT_MISSING_BUNDLE_FINGERPRINTabmelden (eigener Code, nichtBUNDLE_RESUME_REQUIRES_FILE_HASHES— das beschreibt das fehlendetables[].sha256, eine andere Konstellation). SchemaOrigin-Mapping: AP2 §4.4 traegt heute nurJDBC_METADATA/SCHEMA_READER/MERGED. FuerschemaSource = manifest-fallbackistMERGEDsemantisch falsch (es bedeutet „aus mehreren Quellen kombiniert", nicht „best-effort"). AP8 schlaegt eine vierte VarianteMANIFEST_FALLBACKvor; AP2 wird beim AP9-Abschluss nachgezogen.
Bindende DTO-Wahl ist AP9; Implementierungscode folgt nach AP12.
- Aufloesungsmodell mit
-
Parquet-Evaluierung — AP1 §7.1 ImportInput.Directory-Aussage als ueberstimmt markiert (2026-06-05) —
parquet-libraries.md§7.1 Bullet 4 traegt jetzt einen Korrektur-Hinweis: AP7 §10.2 und AP8 §10.1 empfehlen inzwischen einen neuenImportInput.ResolvedBundle-Subtyp statt der urspruenglich geplanten Beibehaltung vonImportInput.Directory. Endgueltiger Wortlaut wird beim AP9-Abschluss aufgeraeumt. -
Parquet-Evaluierung — AP7 Manifest-Format und Import-Preflight (2026-06-05) — neuer Sub-Doc
parquet-manifest-format.mdals architektonische Skizze fuer Multi-Table-/Directory-Bundle- Exporte. Inhalt:- YAML-Schema von
manifest.yaml: PflichtfelderformatVersion,producer,producerVersion,exportedAt,schemaSource,tables[].{table,file,columns}; optionale FelderrowCount,sha256, plusNeutralType-YAML-Repraesentation mitkind-Diskriminator. - Tabelle-zu-Datei-Aufloesung mit Default-Konvention
<schema>.<table>.parquetund Kollisionsschutz K1-K5 (doppelte Tabelle/Datei, Pfadausbruch, fehlende und unreferenzierte Dateien — letztere verhindern stillen Import laut Hauptplan §6). - SHA-256-Verfahren als Opt-in: gehasht wird der fertige Parquet-
Bytestrom nach Writer-
close(), Lowercase-Hex, kein Praefix. Verifikation einmal im Preflight, nicht waehrend des Streamings. - Formatversionierung
MAJOR.MINORmit Start bei1.0: Major-Bump = inkompatibel und vom Reader abgelehnt; Minor-Bump = additiv und mit Warnung tolerierbar. - Preflight-Vertrag mit elf stabilen Fehlerklassen
(
MANIFEST_NOT_FOUND,MANIFEST_PARSE_ERROR,MANIFEST_VERSION_INCOMPATIBLE,MANIFEST_FIELD_MISSING/INVALID,MANIFEST_TABLE_DUPLICATE,MANIFEST_FILE_DUPLICATE/OUTSIDE_BUNDLE/MISSING/UNREFERENCED,MANIFEST_SHA256_MISMATCH). - Konsequenzen: neue Klassen
ParquetBundleManifest,ParquetManifestReader/Writer,ParquetBundlePreflightimadapters:driven:formats-parquet-Modul; Vorzugsoption fuer AP9 ist neuerResolvedParquetBundleInput-Subtyp statt Magic-Field-Erweiterung anImportInput.Directory.
AP7-Vorentscheidungen werden in AP8 (Directory-Aufloesung) und AP9 (Importpfad-DTO) bestaetigt; Implementierungscode folgt nach AP12.
- YAML-Schema von
-
Parquet-Evaluierung — AP6 Importpfad-Spike (2026-06-05) — neuer Test
ParquetSpikeImportPathTestund drei neueParquetSpike-Funktionen demonstrieren am Spike- Output, dass der Footer als Schema-Quelle reicht und dass sich Spike-Rows ueber das neutraleDataChunk-Modell leiten lassen:readSchemaFromFootermapptMessageType-Felder aus demParquetFileReader-Footer zudev.dmigrate.core.data.ColumnDescriptor-Tupeln (name/nullable/sqlTypeNameals opaker Parquet-Originaltyp).readAsChunkkombiniert das mitParquetReader-Rows zu einemdev.dmigrate.core.data.DataChunk(chunkIndex=0; Multi-Chunk- Akkumulation ist Sache des produktiven Adapters).writeWithoutCrcdemonstriert die.crc-Sidecar-Mitigation ausparquet-libraries.md§7 Variante (a) viafs.file.impl=RawLocalFileSystem. AP6-Befund: in Hadoop 3.4.1 istfs.file.impl.disable.cache=trueals zweite Direktive noetig, sonst haelt derFileSystem-Service-Loader-Cache dieLocalFileSystem-Default-Instanz vor und der Sidecar bleibt.
-
Parquet-Evaluierung — AP5 Arrow-Metadateninspektion (2026-06-05) — neuer Test
ParquetSpikeArrowInspectTestim Spike-Modul.parquet-arrow1.17.1 (ausschliesslichtestImplementation, anparquetVersiongekoppelt; bewusst nichtarrow-dataset— vgl.parquet-libraries.md§3.4: JNI-frei, kein produktiver Arrow-Pfad) liest den Datei-Footer ueberParquetFileReader, konvertiert dasMessageTypeviaSchemaConverter#fromParquetzu einem ArrowSchemaund der Test verifiziert fuer die drei Spike-SpaltenInt(32, signed),Utf8,BoolplusisNullable=false. Damit ist Akzeptanzkriterium §7 Bullet 2 ("Beispiel-Export kann mit Arrow-Werkzeugen oder Arrow-Java-Metadaten inspiziert werden") abgehakt. -
Parquet-Evaluierung — AP4 DuckDB-Akzeptanzlauf (2026-06-05) — neuer Test
ParquetSpikeDuckDbReadTestim Spike-Modul. DuckDB JDBC 1.5.3.0 (ausschliesslichtestImplementation, kein produktiver Pfad — vgl.parquet-libraries.md§3.5) liest den GZIP-komprimierten Spike-Output viaSELECT * FROM read_parquet(?), verifiziert den Round-Trip aller drei Zeilen und meldet die Spalten alsINTEGER/VARCHAR/BOOLEAN. Damit ist Akzeptanzkriterium §7 Bullet 1 ("Beispiel-Export kann mit DuckDB gelesen werden") und fuer das Spike-Schemafragment implizit Bullet 3 (Round-Trip-Typen) abgehakt. Hadoop bleibt bewusst auf 3.4.1: parquet-hadoop 1.17.1 ist gegen Hadoop 3.3.0 (provided) kompiliert, 3.5.0 ist ungetestete Kombination und lohnt sich erst mit dem 1.18-Wechsel.
-
Parquet-Evaluierung —
.crc-Sidecar-Mitigation in §7 praezisiert (2026-06-05) — AP6-Befund:parquet-libraries.md§7 Bullet zum.crc-Sidecar ergaenzt. In Hadoop 3.4.1 reichtfs.file.impl=RawLocalFileSystemallein nicht; ohnefs.file.impl.disable.cache=truebedient derFileSystem-Service-Loader-Cache den Writer aus einer vorinstanziiertenLocalFileSystemund der.crc-Sidecar bleibt. -
Parquet-Evaluierung —
iceberg-parquetals bewusst ausgeschlossener Kandidat dokumentiert (2026-06-05) —parquet-libraries.md§3.6 neu:org.apache.iceberg:iceberg-parquetist ein Adapter zwischen Iceberg-Tabellen und Parquet-Dateien (nutzt internparquet-java), nicht ein eigener Writer/Reader. Strukturell ausgeschlossen, weil Iceberg/Delta/Hudi laut Hauptplan §3.2 Nicht-Scope und ein Lakehouse-Adapter laut §4 explizit in einen spaeteren Folge-Schritt verschoben ist; nicht in die Bewertungsmatrix §4 aufgenommen, weil der Ausschluss strukturell und nicht kriteriumsgetrieben ist. Natuerlicher Hauptkandidat fuer den spaeteren Lakehouse-Folgeplan. -
Parquet-Evaluierung — AP3-Spike-Befunde zurueckgespielt (2026-06-05) — die drei Befunde aus dem AP3-Round-Trip-Spike (Commit
3b051ec) sind aus dem Status-Header der Plan-Doc in die AP1-Bibliothekssichtung verschoben und damit final dokumentiert:parquet-libraries.md§5.1 (neu) — Hadoop-API-Kanal in 1.17.1 praezisiert (Hadoop-Path+Configuration+LocalFileSystemrein NIO; diePlainParquetConfiguration-/LocalOutputFile-Pfade kommen erst mit 1.18+).- §7 —
.crc-Sidecar von Hadoop-LocalFileSystemals Writer-Folgeentscheidung dokumentiert (RawLocalFileSystemvs. aktives Aufraeumen; Stdout-Weg ueberPositionOutputStreamdavon nicht betroffen). - §8 —
hadoop-mapreduce-client-core:3.4.1(mit denselben Exclusions wiehadoop-common) als Reader-Compile-Dependency nachgezogen, weilParquetReader.builderueberParquetInputFormat extends FileInputFormatMapReduce-Klassen laedt. parquet-export-import-evaluation.md§8 Arbeitspaket 3 markiert AP3 als erledigt; Status-Block auf Pointer nachparquet-libraries.mdreduziert. Naechste Arbeitspakete: AP4 (DuckDB-Akzeptanzlauf), AP5 (Arrow-Inspektion).
-
BI-Demo Compose-Stack (2026-06-04) — vollstaendige reproduzierbare Demo-Umgebung unter
examples/bi-demo/, died-migratein einen komponierbaren Analytics-Stack einbettet. Sub-Slices BD.1-BD.5 nach Plandocs/planning/done/bi-demo-compose.md:- BD.1 — Compose-Skeleton mit fuenf Services: Postgres
17.10, SeaweedFS 4.31 (S3-API-Server),
seaweed-config+seaweed-init-One-Shots (bash-Parameter-Expansion fuer JSON-Safety der gerendertens3.json), AWS-CLI 2.34.61 alsaws-tools-Service (Entrypoint-Wrapper mit--endpoint-url-Default). Object-Storage von MinIO auf SeaweedFS umgestellt (MinIO Community Edition 2025-Q3 archiviert), S3-Client vonminio/mcaufamazon/aws-cli. Healthcheck-Vertraege viajq -s-State-Pinnung (Compose v2.24+ JSONL-Output-Konvention). - BD.2 — Demo-Schema mit fuenf Tabellen
(customers/products/orders/order_items/events;
50/30/500/1500/10000 Zeilen). Deterministischer Seed via
setseed(0.42)+\set demo_start_date+SET timezone='UTC'+SET max_parallel_workers_per_gather=0. Idempotenz empirisch verifiziert viapg_dump | grep -v restrict | sha256sum-Hash-Vergleich. - BD.3 — Metabase v0.55.24.1 als BI-Frontend mit persistenter H2-Konfiguration im Named Volume; README dokumentiert Erstkonfiguration + drei Beispiel-Fragen (Umsatz/Tag, Bestellungen/Status, Top-Kunden).
- BD.4 —
dmigrate-Compose-Service (Imaged-migrate:dev, Profile=tools) +examples/bi-demo/.d-migrate.yamlmit zwei Named-Connections (Host-CLI + Container-CLI). End-to-End-Workflow reverse/profile/generate gegen Demo-Postgres +aws s3 cpzum S3-Bucket. - BD.5 —
examples/bi-demo/scripts/smoke.shfuer End-to-End-Smoke; Repo-Root-Makefile-Targetsbi-demo-{env,pull,up,down,purge,smoke}kapseln den Compose-Pfad; optionaler.github/workflows/bi-demo-smoke.yml(Best-Effort,continue-on-error: true).
Verifiziert gegen Compose v5.1.4: postgres healthy, seaweed-config/init exited(0), Metabase
/api/health={"status":"ok"}, alle fuenf Tabellen reversed + profiled + als DDL gerendert, fuenf BD.4-Warning-Codes im Profil-Report (CONTAINS_EMPTY_STRINGS,CONTAINS_BLANK_STRINGS,POSSIBLE_PLACEHOLDER_VALUES,LOW_CARDINALITY,DUPLICATE_VALUES) +numericStats.max=99999.99-Outlier. - BD.1 — Compose-Skeleton mit fuenf Services: Postgres
17.10, SeaweedFS 4.31 (S3-API-Server),
-
ProfileReportWriterJSON+YAML-Serializer-Bugs (2026-06-04) — zwei Befunde aus dem BD.4-Smoke (Stand 0.9.8-SNAPSHOT, in BD.5-Review behoben):renderTargetCompatibilityJsonhaengte pro Feld einen Extra-Quote an ("INTEGER"","50"") durch fehlerhafte Raw-String-Verkettung (5-fach-"""-Pattern, das Kotlin als"""-open + content +"""-close + trail-"parst). Rewrite mit klassischen Strings undjsonStr()-Helfer eliminiert die Mehrdeutigkeit.yamlStr/needsYamlQuotingquotierte leere und whitespace-only Strings nicht (exampleInvalidValues: [, customer10@..., ...]), YAML-Parser brachen.isBlank()und Edge-Whitespace-Check erzwingen jetzt das Quoting.
Zwei neue Tests in
ProfileReportWriterTestparsen die gerenderten Outputs mitcom.fasterxml.jackson.databind.ObjectMapperbzw.com.fasterxml.jackson.dataformat.yaml.YAMLMapper— vorher liefen nurshouldContain-Substring-Checks, die den Bug nicht fingen.
scripts/verify-doc-refs.sh(2026-06-04) — der awk-Filter strippt jetzt Single-Backtick-Inline-Code-Spans bevor das Markdown-Link-Pattern matched. Damit faengt der Doc-Link-Check keine Regex-/Code-Beispiele mehr ein, die](-Patterns innerhalb von Backticks enthalten (z. B. Kotlin-Raw-String- Regex indocs/planning/next/trino.md:461).
-
0.9.7 Atomic-Sequence-Preserve Phasen A + B + C + D + E (2026-06-01) — Schließt die Race zwischen
SequencePreserveStage-Probe und dem späterensetval/UPDATEwährend des Live-schema migrate-Laufs. Bisher las die Stagecurrent_valuein einer eigenen Connection, ließ den Renderer einen Restore-Statement-Block bauen und führte diesen außerhalb jeder Sperre aus — eine gleichzeitig laufende App-nextval-Sequenz konnte zwischen Probe und Restore eine Lücke reißen. Der neue Pfad faltet Probe + Restore + die geschützten DDL-Statements in eine einzige Transaktion unter Per-Dialect-Lock.Phase A — Datenmodell + Ports (Commit
174c3891): Neue OperationAlterSequenceCurrentValueincore:diff, neue Sealed- HierarchieAtomicSequencePreserveBatch/AtomicSequencePreserveRequest/AtomicSequencePreserveResultinhexagon:ports-execute; Sentinel-KonstanteATOMIC_PRESERVE_SENTINEL_CURRENT_VALUE = 0Lmarkiert den noch unbekannten Probe-Wert in plan-only-Artefakten.SequenceCapabilitylernt die FeldersupportsAtomicPreserve,supportsAtomicPreserveAllInPlanundtransactionalProtectedSequenceOperations(in Phase A nur Datenmodell, Defaults bleibenfalsebzw. leer).Phase B — Per-Dialect-Executor (Commits
dc6d2ad6,24eb6e17,e882bcb1): Drei neueAtomicSequencePreserveExecutor-Implementierungen, deterministisch nachSequenceObjectRef.namesortiert:- PostgreSQL:
pg_advisory_xact_lock(hashtext(seq_name))pro Sequenz; Probe + protected statements +setvalin einerBEGIN/COMMIT-Klammer. - MySQL:
SELECT … FOR UPDATEauf derdmg_sequences-Helper-Row mitMAX_EXECUTION_TIME-Exempt-Pfad für SLEEP/BENCHMARK (Driver-Quirk). - SQLite:
BEGIN IMMEDIATE+ xerial-spezifischersetQueryTimeout-Lock-Wait; Hikari-Pool umgangen, weil die xerial-Connection-Pool-Interaktion den Lock auf eine andere Connection wandern lässt.
Phase C — Stage- + Executor-Integration (Commits
833d1796,1c09147d,8c2e0a07,11d04e57,b4f548b0,39bcaa29,d72e572f):- Neues
hexagon:ports-execute-Modul + Sealed-InterfaceExecutableSegmentmitPlainSqlSegmentundAtomicPreserveSegment(C.2). SegmentAwareMigrationExecutorersetzt den bisherigen direktenJdbcMigrationExecutor-Aufruf inSchemaMigrateWiringund routet pro Segment zum passenden Executor (C.3).SequenceCapabilityDefaults.supportsAtomicPreserve = truefür PG/MySQL/SQLite;transactionalProtectedSequenceOperationsbefüllt (C.4).SequencePreserveStagebaut denAtomicSequencePreserveBatchdirekt im Stage-Pfad — kein Vorab-Probe-Call mehr — und reicht ihn über die Render-Pipeline an den Segment-Executor (C.1).- Neue Atomic-Preserve-Integration-Tests pro Dialekt in
:test:integration-postgresql/-mysql/-sqlite(C.5); die bestehendenSequencePreserveRaceTest-Suites in:test:integration-concurrencywurden auf den atomaren Pfad migriert und beweisen jetzt „keine Race möglich" statt „Race-Toleranz".
Phase D — Cross-Plan-Deadlock-Beweis + AllInPlan-Flag-Flip (Commit folgt): drei Cross-Plan-Deadlock-Integration-Tests (
PostgresAtomicPreserveCrossPlanDeadlockTest,MysqlAtomicPreserveCrossPlanDeadlockTest,SqliteAtomicPreserveCrossPlanDeadlockTest). PG + MySQL haben positiven (zwei parallele Runs committen) und negativen Smoke (manuell invertierte Lock-Order → SQLSTATE 40P01/55P03 bzw. ER_LOCK_DEADLOCK/WAIT_TIMEOUT). SQLite hat nur den positiven Beweis — die DB-weite RESERVED-Lock macht das Deadlock-Diamant konstruktiv unmöglich.SequenceCapabilityDefaults.supportsAtomicPreserveAllInPlan = truepro Dialekt;SequencePreserveStage.allInPlanBlockeremittiertSEQUENCE_PRESERVE_ATOMIC_UNSUPPORTED, wenn ein Plan ≥ 2 Preserve- Kandidaten enthält und das Flag pro Dialekt auffalsesteht (greift heute nur für synthetische Capability-Overrides — alle produktiven Dialekte sind nach D auftrue). Finding #1 aus dem Code-Review (Contiguity-Crash inSchemaMigrateExecutionStage) ist mit demselben Slice gefixt:segmentForExecute(...)läuft jetzt innerhalb des try-Blocks, eineIllegalStateExceptionmapped zu einem strukturiertenExecutionTracemittransactionRolledBack = true,sideEffectsPossible = falseund einem „Atomic-preserve plan shape invalid"-Hinweis statt unhandled durchzureichen. Finding #3 (Diagnostic-Überzählung inSegmentAwareMigrationExecutor.mapAtomicResultToTrace) ebenfalls gefixt:statementsAttemptedzählt nachAppliednur noch die protected statements (Audit-Follow-ups bleiben im Plan-Artefakt, inflationieren aber den Trace-Counter nicht mehr).Phase E — Docs-Sync (Commit folgt): dieser CHANGELOG- Eintrag selbst, User-Guide-Update („preserveCurrentValue atomar seit 0.9.7" inkl. dialekt-spezifischer App-
nextval-Race- Aufklärung), KDoc-Sync aufSequencePreserveStage(Restrictions- Block),SequenceCapability(C.4-Verweis korrigiert + §3.2-Out- of-Scope-Block + AllInPlan-Update) undSequenceCurrentValueProbe(dead-code-Status-Header — Cleanup eigener Slice).Carve-Outs:
- Cross-Database-Locks, App-side Retry/Backpressure und globaler Schema-Lock bleiben permanent out-of-scope (Plan-Doc §3.2).
- Auf PostgreSQL bleibt
pg_advisory_xact_lockper Design app- nextval-blind: der Lock schliesst die Race zwischen zwei Migrationen, nicht zwischen Migration und App. Plan-Doc §6 Risiko Nr. 8 / User-Guide-Restrisiko dokumentieren dies. - 5 verbleibende Code-Review-Findings (Mittel/Niedrig: Sentinel-
Render, --mysql/sqlite-named-sequences-Fallback, Race-Test-
Assertion, LockTimeout-Decorator) in
docs/planning/in-progress/atomic-preserve-followups.mdals Post- Release-Slices erfasst. - Dead-Code-Cleanup der ungenutzten Probe-Adapter-Klassen ist eigener Folge-Slice.
Plan-Doc:
docs/planning/in-progress/sequence-preserve-atomic-lock-plan.md. - PostgreSQL:
-
0.9.7 SQLite-Sequence preserveCurrentValue Folge-Slice (2026-05-29) — schliesst die
SequencePreserveStage-Lucke fuer SQLite-Targets aus der 0.9.7-E.3-Erstscheibe. NeuerSqliteSequenceCurrentValueProbe-Adapter liestdmg_sequences.next_valuelive;SequenceCurrentValueProbeRunnerdispatcht SQLite jetzt auf den neuen Adapter stattNotApplicable.SequencePreserveStagelistet SQLite in der Allowlist und blockt ohne--sqlite-named-sequences helper_tablemitSEQUENCE_PRESERVE_OPT_IN_REQUIRED(Classifier:MANUAL_ACTION_REQUIRED) BEVOR die Probe-Connection geoeffnet wird.SqliteDiffSequenceOps.renderAlterSequenceCurrentValuerendert Down jetzt deterministisch alsUPDATE dmg_sequences SET next_value = <restoreValue> WHERE name = <probeSequenceRef.name>; ein fehlenderrestoreValue(CreateSequence ohne deterministischen Vorzustand) surfaced alsSQLITE_SEQUENCE_CURRENT_VALUE_DOWN_ROLLBACK_IMPOSSIBLE-Skip statt als stillem No-Op.SequenceCapabilityDefaults.SQLite.supportsCurrentValuePreservevonfalseauftruegeflippt. Neue--sqlite-named-sequences- Option aufschema migrate(parallel zuschema generate); wird durchSchemaMigrateRenderPipelinealsDdlDialectContext.Sqlite.namedSequenceModean die Renderer durchgereicht.Carve-out: Atomare Probe + Setval unter Lock bleibt out-of-scope (siehe Plan-Doc §6 Risiken); langlaufende Apps muessen den Schreibverkehr vor
--executeweiterhin manuell anhalten.Plan-Doc:
docs/planning/done/ImpPlan-0.9.7-sqlite-sequence-preserve-current-value.md. -
0.9.7 SQLite-Sequence-Emulation Phase B.2 — Validator-Regeln (2026-05-28) — zwei zusammengehörige Validator-Schichten, die laut Plan-Doc §3.4 / §3.6 / §7 Phase B vor dem
helper_table- Generator (B.3) feststehen müssen.Step 1 — SequenceDefinition-internal-Regeln (
E125,25f59f73): Neuer dialektagnostischerSchemaSequenceValidationRulesinhexagon:core/validation, verdrahtet inSchemaValidator.validate(...). Blocktincrement = 0,increment = Long.MIN_VALUE,min_value > max_value,start ∉ [min, max]und|increment| > max − min. Der overflow-sichereisIncrementInRange(inc, min, max)folgt der Plan-Doc-§3.6- Herleitung und vermeidet diemax − min-Subtraktion komplett — Grenzfälle beiLong.MIN_VALUE/Long.MAX_VALUEsind im Test pinned.Step 2 — SQLite-helper_table-Cross-Check (
E059,09068f79): NeuerPreGenerationValidator-Port inhexagon:ports(Default-MethodeDatabaseDriver.preGenerationValidator(): NoOp, PG/MySQL bleiben unberührt). SQLite-Driver liefertSqliteHelperTableSequenceValidatorüber dieSqlitePreGenerationValidator-Bridge: blockt imhelper_table-Modus jede Spalte, dieDefaultValue.SequenceNextValträgt und Teil desPRIMARY KEYist. Symmetrisch verdrahtet inSchemaGenerateRunnerundToolExportRunner(jeweils nach dem dialektagnostischenSchemaValidator, vorDdlGenerator.generate). PG/MySQL erlauben PK +SequenceNextValweiterhin — die Regel ist bewusst SQLite-Helper-Table-spezifisch, statt sie in den generischen Validator zu pressen.Out of scope für B.2: "CHECK
IS NOT NULLaufSequenceNextVal-Spalte" — Plan-Doc §3.4 (Z. 728-735) sagt explizit Generator-Zeit-Auto-Suppression mit Warn-Code, kein Validator-Error → wandert nach B.3. "SequenceNextVal + expliziter DEFAULT" ist bereits durch bestehendesE131("identity generation and default are mutually exclusive") abgedeckt.Ledger: neuer
error-code-ledger-0.9.7.yamlregistriertE125undE059alsactive;CodeLedgerValidationTestpinnt beide.spec/ledger.mdCode-Number-Ranges um beide Codes erweitert (E059in derE052-E060-Gruppe,E125anE122-E124-Sequence-Default-Cluster anschließend).Plan-Doc:
docs/planning/done/sqlite-sequence-emulation-plan.mdPhase B.2. Bleiben offen: B.3 (helper_table-DDL +_bi/_ai-Trigger-Paar inkl. CHECK-Auto-Suppression), B.4 (SequenceCapability-Defaults flippen), C/D/E. -
0.9.7 SQLite-Sequence-Emulation Phase B.1 — CLI-Plumbing (2026-05-27) — neues
SqliteNamedSequenceMode-Enum (action_required/helper_table) imhexagon:ports-read, Slot inDdlDialectContext.Sqlite.namedSequenceMode. CLI-Flag--sqlite-named-sequences <action_required|helper_table>aufschema generate; Default für SQLite-Targets bleibtaction_required. Runner-seitige Validierung lehnt das Flag mit Exit 2 + Hinweistext ab, wenn--targetnichtsqliteist. JSON-Output und YAML-Sidecar-Report zeigen das Feld (sqlite_named_sequences) wenn gesetzt.Der Generator-Pfad (
helper_table-Mode emittiert noch keine DDL) folgt in Phase B.3. Phase A § 11 Pre-Code-Klärungen sind alle geschlossen: Min-Version3.35.0,DefaultValue.SequenceNextValvorhanden, Trigger-Body-Layout gegen SQLite 3.53.1 prototyp- validiert (/tmp/sqlite-two-trigger-prototype.sql, 8/8 Szenarien), W114-Vertrag via ADR-0003 fixiert. -
0.9.7 E.3 Schirm — Cross-Dialect Sequencing (Sub-Slices A–E) (2026-05-27) — retroaktive Harmonisierung der drei bereits abgeschlossenen Sequence-Slices (PG-DDL, MySQL helper_table, preserveCurrentValue) hinter einem einzigen Capability-Vertrag.
Neu im Code:
hexagon:ports-read:SequenceCapability+SequenceCapabilityDefaultsanalog zuRoutineCapability/TriggerCapability. Acht Felder pro Dialekt:supportsNamedSequences,supportsStart,supportsMinMaxValue,supportsCycle,supportsCache,emitsCachePreallocationWarning,supportsCurrentValuePreserve,supportsOwnedBy. SQLite-Defaults sind reality-first (supportsNamedSequences=false); sie flippen, wenn der offene SQLite-Sequence-Emulation-Plan landet.PlannerBlockerClassifier: zwei forward-kompatible CodesSEQUENCE_ATTRIBUTE_NOT_SUPPORTED_BY_DIALECTundSEQUENCE_OWNED_BY_NOT_REPRESENTABLE_IN_DIALECT, beide aufMANUAL_ACTION_REQUIRED. Kein Renderer emittiert sie heute — OP-level SQLite-Block bleibtDIALECT_UNSUPPORTED_OPERATION.MysqlDiffSequenceOps: emittiertW114im Diff-Pfad beiCreateSequenceUP,AlterSequence(beidseitig wenncachedifferiert),DropSequenceDOWN — gegated überSequenceCapability.emitsCachePreallocationWarning. Bisher warW114nur im Full-Schema-Pfad (MysqlSequenceDdlSupport) aktiv; ein Operator mitschema migrate --executesah die Warnung für sequence-altering Diffs nicht.
Spec-/ADR-Updates:
spec/neutral-model-spec.md§9.2 (neu): Cross-Dialect- Capability-Matrix proSequenceDefinition-Attribut.spec/cli-spec.md§4.5:W114in der kanonischen Warning-Tabelle (war zuvor nur in §6.1-Prosa).spec/cli-spec.md§4.7 (neu): String-codiertes Sequence- Blocker-Katalog mit Routing pro Code.docs/adr/0003-cross-dialect-sequencing.md(neu): Decisions D1–D5 in binder Form für künftige Sequence-Tranchen (SQLite-helper-table, MariaDB-native, Ownership-Feld).
Plan-Doc:
docs/planning/done/ImpPlan-0.9.7-cross-dialect-sequencing.md. -
0.9.7 E.1 Folge-Slice — MySQL Routine-Identity Reverse-Read (2026-05-22) —
MysqlRoutineReader.readFunctions/readProcedurespopulieren jetztsecurity(SQL SECURITY DEFINER/INVOKER),definer(roher'user'@'host'-String) undsqlMode(komma-getrennter Snapshot zur Routine-Erzeugungszeit) ausinformation_schema.routines. PG-Slice E lieferte die analogen Felder schon seit E.1; MySQL blieb auf den Data-Class-Defaultsnullstehen, sodass file-zu-DB-Diffs gegen ein MySQL-Schema mit expliziten Identity-Attributen spurious-Replace-Diagnosen emittierten.Leere
sql_mode-Strings werden zunullnormalisiert; ein unbekanntessecurity_type(älteres MySQL oder eingeschränkteinformation_schema-Sicht) fällt ebenfalls aufnullzurück.RoutineIdentityNormalizer.normalizeMysqlSqlMode(bereits seit E.1 im Comparator/Fingerprint) sortiert und dedupliziert die Modus-Liste, sodass Reihenfolge-Drift kein spurious-Replace mehr ist.Plan-Doc:
docs/planning/done/ImpPlan-0.9.7-mysql-routine-identity-reverse-read.md. -
0.9.7 E.3 Folge-Slice — Sequence preserveCurrentValue (Sub-Slices A–E) (2026-05-21) — neue, opt-in pro Sequenz steuerbare Policy
SequenceDefinition.preserveCurrentValue. Wenntrueund das Migrate-Target ist eine Live-DB, probt die Pipeline vor dem Render den runtime-Wert der Sequenz (PGlast_value/is_called, MySQLdmg_sequences.next_value) und emittiert einenAlterSequenceCurrentValue-Follow-up direkt hinter der parent-Op (Create/Alter/Rename). Der Renderer übersetzt das per Dialekt inSELECT setval(...)(PG) oderUPDATE dmg_sequences SET next_value = ...(MySQL). Damit landen Migrationen gegen Bestandsdatenbanken mit befüllten sequence-tragenden Tabellen produktiv, ohne dass der erste Schreibzugriff in einen PK-Konflikt läuft.Probe & Port (Sub-Slices B
c93e44ef/ C4eb0f525):- Neuer Port
SequenceCurrentValueProbe(hexagon:ports-read) mit sealedSequenceCurrentValueProbeResult(Read{value, matchedRows, isCalled?, managedBy?, formatVersion?},Failed{code, message},NotFound,NotApplicable). - JDBC-Adapter
PostgresSequenceCurrentValueProbe(adapters/driven/driver-postgresql):SELECT last_value, is_called FROM "<schema>"."<seq>"mit SQLSTATE-Mapping42P01 → NotFound,42501 → Failed(PROBE_PERMISSION_DENIED). - JDBC-Adapter
MysqlSequenceCurrentValueProbe(adapters/driven/driver-mysql):SELECT next_value, managed_by, format_version FROM dmg_sequences WHERE name = …mit Error-Code-Mapping (1146/1142) undSUPPORTED_MANAGED_BY/SUPPORTED_FORMAT_VERSIONS- Validation gegen operator-eingefügte oder fremdformat Helper-Table-Zeilen (PROBE_UNMANAGED_ROW/PROBE_UNKNOWN_FORMAT_VERSION). MysqlSequenceSupportNaming.SUPPORTED_MANAGED_BYwird alsSet<String>geführt (single value"d-migrate"heute); Renderer + Probe beziehen die Filter-Liste aus derselben Konstante, damit sich Marker-Erweiterungen (d-migrate-legacyetc.) in einem einzigen Edit ergeben (Refactor-Commit12f4b812).
Renderer-Pfade (Sub-Slice A
29ade761+feature(core)… no carve-outsFolge-Commit):- PG:
PostgresDiffSequenceOps.renderAlterSequenceCurrentValueemittiertSELECT setval('<seq>', <value>, <isCalled>);als Up-Statement; Down setzt aufrestoreValue/restoreIsCalledzurück.isCalledist auf PG zwingend (Renderer-Assertion verweigert einen half-builtsetval(seq, value, null)). - MySQL:
MysqlDiffSequenceOps.renderAlterSequenceCurrentValueemittiertUPDATE dmg_sequences SET next_value = … WHERE name = … AND managed_by IN (...) AND format_version IN (...);— dieIN-Listen iterierenSUPPORTED_MANAGED_BY/SUPPORTED_FORMAT_VERSIONSdeterministisch. - SQLite: bleibt
OpCategory.UNSUPPORTED→DIALECT_UNSUPPORTED_OPERATION. Endzustand, kein „kommt später"-Carve-out, bis ein SQLite-Sequence- Emulationsplan landet. - Plan-Augmentation (Sub-Slice D): jede
AlterSequenceCurrentValue-Follow-up trägtdependencies = setOf(parent.id); der Renderer-Switch routet sie über die SEQUENCE-Category zu denselben Per-Op-Renderern.
Pipeline-Integration + Planner-Emit (Sub-Slice D
257908fd):- Neue
SequencePreserveStage(hexagon:application,cli/commands/SequencePreserveStage.kt) mit drei-Outcome- Shape (Succeeded(augmentedPlan, infos)/Failed(diags)/NotRun). Skip-Pfade:!--execute, file-Target, nicht-PG/ MySQL-Dialect (per-OpNOT_SUPPORTED_BY_DIALECT-Blocker), kein Probe wired (per-OpNOT_RUN_POLICY-INFO), keine preserve-Kandidaten im Plan. - Kandidatenfilter:
AlterSequence(immer, wenn preserve=true),CreateSequencenur mitrenameProvenance != null(konservative Pre-Probe-Gate; Widening in Folge-Slice),RenameSequencewenn die Quell-Sequenz auscurrentSchemapreserve=true trägt.DropSequenceist nicht Kandidat. - Routing (
SequencePreserveStage.routeProbeResult):Read(matchedRows=1)→ Follow-up;Read(matchedRows≠1)→PROBE_FAILED-Blocker;NotFound→ INFO für Create, Block für Alter+Rename;Failed/NotApplicable→ entsprechende Blocker-Codes. Probe-Exceptions werden gefangen und alsPROBE_FAILED-Diagnose pro betroffener parent-Op gestempelt. - Plan-Augmentation: jede
AlterSequenceCurrentValue-Op landet direkt hinter ihrer parent-Op imDiffResult.operations-Stream. Der augmentierte Plan ersetzt den Original-Plan fürgenerateUp/generateDown,maybeWritePlanArtefact(migration-plan.v1-Artefakt reflektiert den tatsächlich ausgeführten Op-Stream), Report-Builder und Rollback-Composer. - CLI-Wiring:
SequenceCurrentValueProbeRunnerals dialect-dispatchender Lambda anSchemaMigrateRunner.sequenceCurrentValueProbe. Routet proSequenceObjectRef.dialectanPostgresSequenceCurrentValueProbeoderMysqlSequenceCurrentValueProbe; SQLite bleibtNotApplicable.
Diagnose-Codes +
PlannerBlockerClassifier-Mapping:SEQUENCE_PRESERVE_PROBE_FAILED→MANUAL_ACTION_REQUIREDSEQUENCE_PRESERVE_CONFIG_INVALID→MANUAL_ACTION_REQUIREDSEQUENCE_PRESERVE_REQUIRES_DB_TARGET→MANUAL_ACTION_REQUIRED(für zukünftige E-Slice-Erweiterung; D emittiert ihn heute nicht, weilSchemaMigratePreparationbereits Exit 2 für--executemit File-Target hat).SEQUENCE_PRESERVE_NOT_SUPPORTED_BY_DIALECT→DIALECT_UNSUPPORTED_OPERATION(SQLite).- INFO-Codes (
SEQUENCE_PRESERVE_NOT_FOUNDfür CreateSequence ohne Vorzustand,SEQUENCE_PRESERVE_NOT_RUN_POLICYfür fehlende Probe) bleiben absichtlich aus dem Classifier-Mapping — die Stage surfaced sie überMigrationDdlResult.diagnosticsohne Blocker-Klassifizierung.
Out-of-Scope (eigene Folge-Slices):
- Atomare Probe + setval/UPDATE unter Lock: wenn die App
parallel
nextvalaufruft während der Pipeline-Probe läuft, ist der gesetzte Wert veraltet. Operator-Pflicht ist heute ein Freeze-Window. Eine künftige Tranche kannLOCK TABLES/BEGIN; SELECT FOR UPDATE-Wrappers ergänzen. - CreateSequence-Pre-Probe-Gate Widening: die heutige
konservative Definition (
renameProvenance != null) deckt nur Drop+Create-Rename-Fallbacks ab; der idempotente Re-Run-gegen-bestehendes-Target-Use-Case bleibtSEQUENCE_PRESERVE_NOT_FOUND(INFO) bis ein deterministischer pre-probe-Pfad spezifiziert ist. - Multi-Sequence-Atomicity: pro-Op-Probes laufen seriell; Operator-Inserts zwischen den Probes können Inkonsistenzen erzeugen. Carve-out dokumentiert.
- SQLite-Sequence-Vollvariante: separater Plan unter
docs/planning/done/sqlite-sequence-emulation-plan.md.
Tests:
- Unit:
SequencePreserveStageTest,PlannerBlockerClassifierTest(extended),PostgresSequenceCurrentValueProbeTest,MysqlSequenceCurrentValueProbeTest,PostgresDiffSequenceOpsPreserveCurrentValueTest,MysqlDiffSequenceOpsPreserveCurrentValueTest. - Runner-E2E:
SchemaMigrateRunnerSequencePreserveTest— probe → augmented plan → MigrationDdlResult → Report (Read, NotFound auf Alter/Create, Probe-throws, SQLite-Blocker, no-probe-NOT_RUN_POLICY). - Integration (
make integration):PostgresSequenceCurrentValueProbeIntegrationTest(live PG),MysqlSequenceCurrentValueProbeIntegrationTest(live MySQL), pinnen Probe-Outcomes gegen echte DB-Bytes.
Sub-Slice-Commits:
- A — Foundations + Renderers:
29ade761. - B — PG Probe:
c93e44ef. - C — MySQL Probe:
4eb0f525. - C-Fix — Permission-Test entfernt (testcontainers MySQL-User
hat kein CREATE USER):
86f188a6. managed_by-Refactor aufSUPPORTED_MANAGED_BY: Set:12f4b812.- D — Pipeline + Planner-Emit:
257908fd.
Plan-Doc:
docs/planning/done/ImpPlan-0.9.7-sequence-preserve-current-value.md. - Neuer Port
-
0.9.7 E.3 Folge-Slice — MySQL Sequence Live-DB-Drift-Check (Sub-Slices A–F) (2026-05-20) —
schema migrate --executegegen MySQL-Targets verifiziert jetzt vor dem Render, dass die Helper-Table-Emulation der Sequence-Migration (dmg_sequences- Tabelle,dmg_nextval/dmg_setval-Routinen, betroffenedmg_sequences-Zeile, gebundenedmg_seq_…_bi-Trigger) der kanonischen Form entspricht. Drift in einem dieser Objekte blockiert die Migration vor jeder DDL-Emission statt mit unklarem JDBC-Constraint-Fehler abzubrechen.Probe & Port:
- Neuer Port
MysqlSequenceCanonicityProbe(hexagon:ports-read) mit Methoden für Support-Table- Spaltensignatur, Routine-Body-Marker, Sequence-Row Managed-Fields (increment_by/min_value/max_value/cycle_enabled/cache_size) und Support-Trigger-Body. Per-Probe-Aufruf liefert eineMysqlSequenceCanonicityDeclarationmit Status CANONICAL / DRIFT / MISSING / NOT_RUN_FILE_TARGET / NOT_RUN_POLICY / PROBE_RUNTIME_ERROR und — bei DRIFT — strukturiertem Feld-Diff (driftField/expected/actual). - JDBC-Adapter
MysqlSequenceCanonicityProbeAdapter(adapters/driven/driver-mysql) implementiert die Probes überINFORMATION_SCHEMA.COLUMNS,SHOW CREATE FUNCTIONundSHOW CREATE TRIGGER. MySQL-Fehlercodes 1305 (SP_DOES_NOT_EXIST) und 1360 (TRG_DOES_NOT_EXIST) werden zu StatusMISSINGmapped (nichtPROBE_RUNTIME_ERROR), weil der canonical Bootstrap diese Objekte erst noch erzeugen wird.
Gate-Entscheidung:
MysqlSequenceCanonicityGate.decide(declaration, intent)routet pro Status × Op-Intent (CREATE/ALTER/DROP):CANONICAL→ Proceed in jedem Intent.DRIFT→ Block mit kind-spezifischem Code (E124_MYSQL_SEQUENCE_DRIFT_TABLE/…_ROUTINE/…_ROW/…_TRIGGER) und MANUAL_ACTION_REQUIRED.MISSING+CREATE/DROP→ Proceed (Bootstrap oder idempotenter Drop).MISSING+ALTER→ Block mitE124_MYSQL_SEQUENCE_MISSING_FOR_ALTER.PROBE_RUNTIME_ERROR→ Block mitE124_MYSQL_SEQUENCE_DRIFT_PROBE_FAILED.NOT_RUN_*→ Info-Decision (kein Blocker), damit der Report den "drift-check skipped"-Grund tracked.
PlannerBlockerClassifiermapped alle sechs neuen Codes aufMigrationBlockedReason.MANUAL_ACTION_REQUIRED.
CLI-Stage + Pipeline:
- Neue
MysqlSequenceCanonicityStage(hexagon:application) mit drei Outcome-Zuständen (Succeeded / Failed / NotRun). Skip-Pfade:!execute, file-target, non-MySQL Dialekt, keineMysqlSequenceCanonicityProbeFngewired, keine Sequence-Op im Plan. Bei Exception aus der Probe-Funktion werden alle Sequence-Ops alsPROBE_RUNTIME_ERRORgestempelt und ein Top-LevelMYSQL_SEQUENCE_DRIFT_RUN_FAILED- Diagnostic emittiert. SchemaMigrateRenderPipelinebekommt einen optionalenmysqlSequenceCanonicityProbe-Parameter (rückwärtskompatibel übernulldefault);runMysqlSequenceCanonicityläuft vorbuildRenderOptions, derenmysqlSequenceCanonicity- Feld bei Succeeded die Probe-Ergebnisse trägt, sonst die Pre-Plan-Declarations.- Pre-Planning:
MigrationPreflightPlanner.plan(…)emittiert pro Sequence-Op eine SEQUENCE_ROW-Declaration mitNOT_RUN_FILE_TARGET(file-target) oderNOT_RUN_POLICY(DB-execute ohne wired Probe). Non-MySQL Dialekte und Pläne ohne Sequence-Op liefern nichts.
Renderer-Gate:
MysqlDiffSequenceOps.canonicityBlocks(op, intent, ctx)konsultiertctx.options.mysqlSequenceCanonicity, gefiltert aufop.id. Erste Block-Decision gewinnt; Info-Decisions emittieren INFO-Diagnostics und der Renderer fährt fort. Per-Op-Intent:CreateSequence→CREATE,AlterSequence→ALTER,DropSequence→DROP,RenameSequence→ALTER(UPDATE auf existierender Zeile). Eine leere Declaration-Liste für die op-id heißt "kein Probe gelaufen" → Proceed.
Report:
SchemaMigrateReport.mysqlSequenceCanonicity[]carriert ein neuesSchemaMigrateMysqlSequenceCanonicityView-DTO pro (Sequence-Op, kanonisches Objekt). JSON / human-Reports sehen Status, Kind, Object-Name, sqlHash und bei Drift / Probe-Fehler die zusätzlichen Felder.MigrationDdlResult.mysqlSequenceCanonicitylebt parallel zucheckPreflights/sqliteCastPreflights; Up- und Down-Render-Results werden überbindingKey(op-id + kind + object + hash) dedupliziert gemerged.
Live-DB-Coverage:
MysqlSequenceCanonicityProbeIntegrationTestläuft via testcontainers gegen einen echten MySQL-8-Container und pinnt jede Probe-Methode gegen kanonischen und drifted State. Aktivierung übermake integration(oder-PintegrationTests); aus der reinenmake docker-check- Suite weiterhin ausgeklammert, weil docker-in-docker benötigt wird.- PK-Drift wird vom Probe-Adapter zusätzlich zur Spalten-
Signatur geprüft (
INFORMATION_SCHEMA.STATISTICSauf denPRIMARY-Index): einedmg_sequencesohnePRIMARY KEY (name)ergibtdriftField = "primary_key". Damit ist eine Tabelle ohne PK nicht mehr CANONICAL. - Trigger-Drift-Gate in
emitSupportTriggerForColumn:AddColumn/AlterColumnDefaultmitSequenceNextVal- Default holen vor demDROP + CREATE TRIGGERdie passende SUPPORT_TRIGGER-Declaration und blocken bei DRIFT. Vorher wurde Operator-modifizierter Trigger-Body stillschweigend überschrieben. - CLI-Wiring komplettiert:
SchemaMigrateCommandübergibtMysqlSequenceCanonicityProbeRunner::probeanSchemaMigrateRunner, sodassschema migrate --executegegen MySQL den Live-Drift-Check tatsächlich ausführt statt aufNOT_RUN_POLICYzu fallen.
Out-of-scope für diesen Slice (eigene Folge-Slices):
- Auto-fix / Drift-Repair: Der Drift-Check meldet
ausschließlich. Repair-DDLs zu emittieren (z.B. die
dmg_sequences-Spalte nachträglich auf NULLABLE schalten) ist eine eigene Operatoren-Aufgabe; dieMANUAL_ACTION_REQUIRED-Diagnose nennt den betroffenen Drift-Field genau, damit der Operator das gezielt adressieren kann. --skip-sequence-drift-check-Flag: StatusNOT_RUN_POLICYist bereits vorgesehen, ein User-facing-Flag dafür gibt's noch nicht. Operatoren, die den Check temporär ausstellen wollen, müssten heute den Probe-Wire weglassen.
Plan-Doc:
docs/planning/done/ImpPlan-0.9.7-mysql-sequence-drift-check.md. - Neuer Port
-
0.9.7 E.3 — MySQL Sequence Diff-Migration (Sub-Slices A–I) (2026-05-20) —
schema migrateagainst a MySQL target now renders the four sequenceDiffOperationsubtypes (CreateSequence,AlterSequence,DropSequence,RenameSequence) into the helper-table emulation that landed in 0.9.4 (docs/planning/done/mysql-sequence-emulation-plan.md). Previously the diff generator routed sequence ops toOpCategory.UNSUPPORTED, so operators with sequence-tracking MySQL schemas had to bypassschema migrateand apply the fullschema generatescript manually.Template surface:
- New internal
MysqlSequenceEmulationTemplates(adapters/driven/driver-mysql) holds the pure helper-table SQL templates extracted fromMysqlSequenceDdlSupportso the DDL-Generator pipeline (full-schema emission) and the diff renderer share one source of truth.MysqlSequenceDdlSupportdelegates to it; the production SQL output stays byte-identical (verified via existingMysqlDdlGenerator*Testfixtures).
Diff renderer:
- New
MysqlDiffSequenceOps(internal object) implements Up/Down for all four subtypes.CreateSequenceemits the helper-table bootstrap (CREATE TABLE dmg_sequences+CREATE FUNCTION dmg_nextval+CREATE FUNCTION dmg_setval) at most once per migration direction viaMysqlSequenceMigrationContext.needsBootstrap(), followed by a row INSERT; subsequentCreateSequenceops reuse the bootstrap.AlterSequenceemits anUPDATE dmg_sequencesthat touches only the managed declarative fields that actually differ betweenop.beforeandop.after(increment_by,min_value,max_value,cycle_enabled,cache_size); the runtimenext_value/startstate is left to thepreserveCurrentValue-Cross-Dialect-Slice.DropSequenceUP drops every sequence-bound trigger (discovered via the newMysqlDiffRenderContext.triggersForSequence(name, SchemaSide)helper that walks column-levelSequenceNextValdefaults) before deleting thedmg_sequencesrow, so the catalog cannot leakdmg_nextval('<deleted>')references. The DOWN inverse re-creates the same triggers. Trigger discovery forDropSequencealways consultsSchemaSide.CURRENT(the pre-Up state); forRenameSequencethe direction-based heuristic applies. MysqlDiffDdlGenerator.categorize()routes the four subtypes to a newOpCategory.SEQUENCE.RenameSequencelands here as a defensive regression guard only — see the policy section below.- Mode gate: when
MysqlNamedSequenceMode != HELPER_TABLEevery sequence op blocks with diagnostic codeE056→MANUAL_ACTION_REQUIRED, including the actionable hint "Add --mysql-named-sequences helper_table to enable sequence emulation."; no SQL is emitted.
Rename policy + decomposition:
MysqlObjectRenamePolicy.classify(SEQUENCE, ...)lifted fromRenameSupport.BlockedtoRenameSupport.DropCreateFallback. The Mapper now decomposes sequence renames intoDropSequence(from)+CreateSequence(to)with sharedrenameProvenance, mirroring the trigger / function / procedure fallback that F.4 Sub-Slice B established. TheRenameSequenceop type still has a defensiveUPDATE dmg_sequences SET name = …+ trigger rebuild path in the renderer for planner regressions / custom plans / artefact replays.
Column-default reprojection:
SequenceDefaultReprojector(F.4 Sub-Slice D) extended to recognise the Drop-Create fallback path. Without this, a MySQL plan that combines a rename overlay (old_seq → new_seq) with aCreateTable/AddColumn/AlterColumnDefaultreferencingnextval('old_seq')would silently emit DDL that points at the dropped sequence name. The reprojector picks up theCreateSequencehalf of every fallback pair (those whoserenameProvenance.objectType == SEQUENCE) and reads itsfromPath[0]/toPath[0]into the same rewrite map the PostgreSQL-nativeRenameSequencepath uses. TheDependencyAnalyzerrequires no change — itssequenceSourceIdByNamemap already keysCreateSequence.objectRef.rootName → CreateSequence.id, so column-bearing ops with rewritten defaults get the correct dependency edge for free.
Carve-outs documented as out-of-scope (deferred to follow-up slices, named per plan §9): live-DB drift / canonical-form checks against existing
dmg_sequencesrows (analogous to F.5 E.3'sCheckPreflightProbe);preserveCurrentValuepolicy across dialects; SQLite sequence diff; cross-dialect sequence transfer; MariaDB-nativeCREATE SEQUENCE.Tests landed across the affected modules:
MysqlSequenceEmulationTemplatesTest,MysqlDiffSequenceOpsTest,ObjectRenamePolicyTest(MySQL SEQUENCE case),RenameObjectMapperTest(MySQL fallback case),MysqlDiffObjectRenameTest(Drop+Create+Provenance pin),SequenceDefaultReprojectorTest(three new MySQL fallback cases).Bootstrap idempotency + column-default rendering (Sub-Slice F):
- The helper-table bootstrap is idempotent across migration
re-runs:
MysqlSequenceEmulationTemplates.supportTableSqlemitsCREATE TABLE IF NOT EXISTS dmg_sequences, and thenextval/setvalroutines are preceded by separateDROP FUNCTION IF EXISTS …;statements (kept outside theDELIMITER //-wrapped CREATE so the DDL-Generator's rollback parser still recognises the routine name). A migration against a DB that already has the helper objects no longer fails on "table already exists" / "function already exists". SequenceNextValcolumn defaults now render correctly in the diff path.MysqlDiffSqlBuilders.columnLinedrops theDEFAULTclause for sequence defaults (mirrors the DDL- Generator'sresolveSequenceDefaultbypass), and the table-level renderers emit a per-column BEFORE INSERT trigger viaMysqlDiffSequenceOps.emitSupportTriggerForColumn.renderCreateTable/renderAddColumn/renderAlterColumnDefaultare all wired through the new mode-gated path; before Sub-Slice F a plan that combined aCreateTable/AddColumnwith aSequenceNextValdefault crashed the renderer.AlterSequencewhosebefore → afterdelta only touches the runtime-state fieldstartno longer disappears silently.renderAlterSequenceemits an INFO-severity diagnosticMYSQL_SEQUENCE_RUNTIME_STATE_NO_OPand marks the op as skipped — no blocker, but the report tracks the op. The actual runtime-state migration is thepreserveCurrentValuecross-dialect follow-up.
DELIMITER fix + scope clarification (Sub-Slice H):
- The Sub-Slice B/F templates wrapped the routine + trigger
bodies in
DELIMITER //…DELIMITER ;, which is a mysql-CLI client directive — JDBC throwsSQLException. Sub-Slice H moves the template output to a delimiterfreien BEGIN…END body; the DDL-Generator pipeline wraps the result withwrapWithDelimiterfor--output file.sqlartefacts, and the diff path passes the body directly through JDBC. Without this fixschema migrate --executewould have failed on every helper-table bootstrap or column-bound trigger emission. - The drift-/canonicity-check scope (live-DB validation of
existing
dmg_sequencesrows anddmg_*support objects) is now explicitly out of this slice with a dedicated follow-up plan-doc stub (docs/planning/done/ImpPlan-0.9.7-mysql-sequence-drift-check.md, 6 sub-slices) so "done" no longer hides the reduced scope.
Commit timeline: Sub-Slice A
edc1fb9d+ review4336284d, B28598cde+ review7c2b8bec, Cd3724a33+ review93aa3e40, D0bda4f15, E (closing iter 1)c7e92bf4+1431fb24, F3bea97e7, G (closing iter 2)aba3392f, Hd248cd11, I (closing iter 3) ⟵ this commit. Plan-Doc:docs/planning/done/ImpPlan-0.9.7-mysql-sequence-diff-migration.md. - New internal
-
0.9.7 F.5 — CHECK / EXCLUDE Constraint Vollscheibe (Sub-Slices A–G) (2026-05-20) — the conservative F.5 carve-out from the first-matrix slice (every table carrying a CHECK or EXCLUDE constraint was tagged
CONSTRAINT_NOT_DIFFABLE) is removed. CHECK and EXCLUDE constraints now flow through the standard Add/Drop/Replace pipeline with per-dialect rendering, live-data preflight in execute mode, MySQL enforcement gating, and a reversibility + replace contract.Comparison and planning:
ConstraintDiffContract.canonicalRawSqlExpression()pins raw-SQL constraint comparison to LF normalisation + surroundingtrim()— semantically different expressions remain different.OperationMapperno longer skips CHECK / EXCLUDE; they emitAddConstraint/DropConstraint(and aDropConstraint + AddConstraintpair forconstraintsChanged) like UNIQUE / FOREIGN_KEY.- Both ops gain
replacePairId: String?, a deterministic Replace-group identity (SHA-256(table | name | canonical(before) | canonical(after))) that downstream reporters use to recognise the pair. Op ids stay unique; dependency-sort, artefact binding andrenderedStatements.operationIdscontinue to work unchanged. - New
ConstraintReplaceContractpost-pass classifies reversibility:AddConstraint(CHECK/EXCLUDE)→AUTOMATIC;DropConstraint(CHECK/EXCLUDE)with known expression →AUTOMATIC_WITH_DATA_RISK;DropConstraint(CHECK/EXCLUDE)without expression →NOT_REVERSIBLE. UNIQUE / FOREIGN_KEY ops pass through unchanged. PlannerBlockerClassifieradds seven new diagnostic codes:CHECK_PREFLIGHT_VIOLATIONS,CHECK_PREFLIGHT_RUNTIME_ERROR,EXCLUDE_NOT_SUPPORTED_BY_DIALECT,MYSQL_CHECK_NOT_ENFORCED_BEFORE_8_0_16,MYSQL_CHECK_ENFORCEMENT_UNKNOWN,CHECK_EXPRESSION_CROSS_TABLE_UNSUPPORTED,EXCLUDE_OPERATOR_CLASS_NOT_SUPPORTED. All but the dialect-incapability case map toMANUAL_ACTION_REQUIRED(operator can rewrite the expression, clean up data, or upgrade the server); EXCLUDE on MySQL/SQLite maps toDIALECT_UNSUPPORTED_OPERATION. No newMigrationBlockedReasonenum values were required.
Per-dialect rendering matrix:
- PostgreSQL renders CHECK and EXCLUDE via
ALTER TABLE … ADD CONSTRAINT … CHECK (…)/… EXCLUDE USING gist (…)and… DROP CONSTRAINT …on the inverse side; inlineCREATE TABLEkeeps the constraint clause in the body. NewExcludeOperatorClassGatewhitelists element heads (bare or quoted identifier, balanced parenthesised expression) beforeWITH; custom operator classes,COLLATE,ASC/DESCandNULLS …tokens block withEXCLUDE_OPERATOR_CLASS_NOT_SUPPORTED→MANUAL_ACTION_REQUIREDon Add (UP), Drop (DOWN) and inlineCREATE TABLE. - MySQL ≥ 8.0.16 / MariaDB ≥ 10.2.1 render CHECK via
ADD CONSTRAINT … CHECK (…)/DROP CHECK ….MysqlCheckEnforcementResolver(hexagon:ports-read) mapsmysqlServerVersionto a(known, enforced)capability: pre-8.0.16 (or MariaDB < 10.2.1) blocksMYSQL_CHECK_NOT_ENFORCED_BEFORE_8_0_16on logical-add paths (silent enforcement-skip is treated as a contract violation); missingmysqlServerVersionblocksMYSQL_CHECK_ENFORCEMENT_UNKNOWNon both add and drop. EXCLUDE is short-circuited withEXCLUDE_NOT_SUPPORTED_BY_DIALECT. - SQLite absorbs CHECK Add/Drop/Replace into the rebuild
pipeline (the temporary
CREATE TABLEembeds the new constraint set inline). EXCLUDE blocks the bucket withEXCLUDE_NOT_SUPPORTED_BY_DIALECT— including the schema-level safety net that catches a pre-existing EXCLUDE on a column-reshape rebuild that no op would otherwise mention.
Live-data preflight (execute mode):
- New
CheckPreflightProbeport + per-dialect adapter (Postgres/Mysql/SqliteCheckPreflightProbe) runsSELECT count(*) FROM <t> WHERE NOT (<expr>)against the target. A non-zero count surfacesCHECK_PREFLIGHT_VIOLATIONS; a thrown SQL error surfacesCHECK_PREFLIGHT_RUNTIME_ERRORwith the message embedded in the report. The execute pipeline declares one preflight perAddConstraint(CHECK)viaCheckPreflightPlanner+CheckPreflightStage, and the renderer gate (CheckPreflightGate, consumed identically by all three dialects) decides Proceed vs. Block. File-to-file mode emitsNOT_RUN_FILE_TARGETdeclarations so the report can tell the operator the gate did not run; the renderer keeps emitting. - Reports surface a new
MigrationDdlResult.checkPreflightslist andSchemaMigrateCheckPreflightViewfield (Sub-Slice E.4); the CLI status command renders the per-op verdict alongside existing per-op diagnostics.
Reversibility + Replace contract (Sub-Slice F):
- PostgreSQL and MySQL renderers route a
DropConstraintDOWN-pass for CHECK/EXCLUDE without expression toROLLBACK_NOT_POSSIBLE(the inverseADD CONSTRAINT … <expr>cannot be reconstructed) instead of the genericDIALECT_UNSUPPORTED_OPERATION. SQLite is already covered via the existing NOT_REVERSIBLE rebuild-bucket gate. - The Replace pair (Drop+Add for
constraintsChanged) round-trips through Up and Down — Up emits Drop-then-Add of the new expression; Down emits Add-of-old followed by Drop of new. Op ids stay unique; the sharedreplacePairIdties them together for reporters.
Carve-outs documented as out-of-scope (deferred to follow-up slices, named per plan §9): semantic SQL parser for CHECK expressions; PostgreSQL
NOT VALID+VALIDATEtwo-step workflow; EXCLUDE with custom operator classes beyond the Whitelist; MySQLCHECKNOT ENFORCEDoverride; preflight sampling for very large tables; cross-table CHECK references via trigger emulation.Tests landed across the affected modules:
ConstraintReplaceContractTest,OperationMapperReplacePairTest,CheckPreflightPlannerTest,MysqlCheckEnforcementResolverTest,PlannerBlockerClassifierTest,PostgresDiffDdlGeneratorCheckExcludeTest/MysqlDiffDdlGeneratorCheckExcludeTest/SqliteDiffDdlGeneratorCheckExcludeTest,PostgresDiffCheckPreflightGateTest/MysqlDiffCheckPreflightGateTest,Postgres/Mysql/SqliteCheckPreflightProbeTest,ExcludeOperatorClassGateTest,CheckPreflightStageTest,MergeCheckPreflightsTest.Commit timeline: Sub-Slice A
2c784d8b, Bd60939c5, C99c9528c, Dfe0cc35e, E.1+E.2cde9d39f, E.38a47a640, E.4fc02d621+a2afe0c9, F172c9b82. Plan-Doc:docs/planning/done/ImpPlan-0.9.7-F.5-check-exclude-vollscheibe.md. -
0.9.7 F.4 Follow-up —
migration-plan.v1Artefact Producer Wiring (2026-05-19, Sub-Slice G) — closes the audit gap raised after F.4's closing slice: theMigrationPlanArtifactcontract that landed under F.4 Sub-Slice E was modelled, encoded and validated inhexagon:core, but never constructed in the production CLI flow.schema migratenow emits a signedmigration-plan.v1JSON to disk when the new--plan-artefact <path>flag is set.Producer surface:
- New
MigrationPlanArtifactBuilder(hexagon:application) — pure projection(DiffResult, MigrationDdlResult, dialect, clock, dMigrateVersion) → MigrationPlanArtifact. Maps everyDiffOperationto a public DTO (kind = subtype simpleName, objectType / phase / reversibility as enum names, risk projection includingdataTransformationMode+ optional model-version / model-id);DiffDiagnostic→ public DTO with enum-name severity; reversibility summary aggregated from per-opReversibility(MANUAL_REQUIRED/NOT_REVERSIBLEop-id lists,fullyReversibletrue iff all ops AUTOMATIC or AUTOMATIC_WITH_DATA_RISK); rendered statements with stablestmt-Nids + canonicalRoutineBodyScrubber.previewhashesTransactionScope.namestrings;DiffResult.renameProjectionsround-tripped into the public DTO. Builder tail-callswithRenameProjectionExtension()(auto-addsrename-projections.v1tosemanticExtensionswhen the list is non-empty) +withComputedHash().
- New
SchemaMigrateArtefactSink.writePlanArtefact(path, artifact)— atomic write of the canonical JSON via the existingatomicWriter. Returnsnullon success,7on local I/O failure (matches--reportwrite semantics). - New
SchemaMigrateRequest.planArtefact: Path? = nullfield.SchemaMigrateRunnerinvokesmaybeWritePlanArtefact(...)between the report build and the rollback compose, so the artefact lands even on Exit 8 (blocker) and--plan-onlypaths. - New
--plan-artefact <path>Clikt option onschema migrate.
Contract documentation:
spec/cli-spec.md§6.1 gains a newmigration-plan.v1-Artefakt section listing every top-level field, the semantic-extension gates (rename-projections.v1), and the eleven validator codes (PLAN_ARTIFACT_*) consumers may see. Plan-doc:docs/planning/done/ImpPlan-0.9.7-F.4-G-artefact-producer-wiring.md.Tests:
MigrationPlanArtifactBuilderTest(7 cases) pins per-field projection, reversibility-summary aggregation, stablestmt-Nids, diagnostic severity round-trip,renameProjectionsplus semantic-extension auto-gate, fingerprint-missing fail-fast.SchemaMigrateRunnerTestgets a--plan-artefactend-to-end case mirroring the--reportintegration test. - New
-
0.9.7 F.4 Follow-up —
transactionScopeenum drift fix in plan-artefact contract test (2026-05-19, Sub-Slice G.1) —MigrationPlanArtifactContractTestpinned"transactionScope": "SINGLE_STATEMENT"since the artefact was introduced in 2026-05-13. That string never matched any value in the runtimeTransactionScopeenum (RUNNER_OWNED/STREAM_OWNED/NO_TRANSACTION). Both occurrences are updated to"RUNNER_OWNED"— the enum's default and the scope every PostgreSQL diff stream emits for ordinary single-statement DDL.MigrationPlanRenderedStatement.transactionScopegains a KDoc block documenting the canonical contract: the field carriesTransactionScope.nameas aString(not the enum) so a future scope value surfaces as an unknown string for validators / consumers rather than a deserialisation failure. -
0.9.7 F.4 routine-trigger-view-renames Vollscheibe — overlay-bound renames for views, triggers, functions, procedures and sequences land alongside the existing table/column rename pipeline. The mapper consults a per-dialect
ObjectRenamePolicyfor each rename candidate and emits one of three outcomes: a nativeRename*operation (RenameView/RenameTrigger/RenameFunction/RenameProcedure/RenameSequence); aDrop*+Create*pair tagged with aRenameProvenancemarker; or anOBJECT_RENAME_UNSUPPORTEDBLOCKER diagnostic. Mapper / renderer / dependency-analyzer / plan-artifact contracts close together in this slice; the carve-out from earlier slices (rename-mapping overlay whitelisted only{table, column}) is gone.Per-dialect contract:
- PostgreSQL renders native rename SQL for every kind —
ALTER VIEW name RENAME TO new,ALTER TRIGGER name ON table RENAME TO new,ALTER FUNCTION name(types) RENAME TO new,ALTER PROCEDURE name(types) RENAME TO new,ALTER SEQUENCE name RENAME TO new. Function / procedure signatures follow PG's drop / alter convention: OUT parameters excluded, INOUT keeps the keyword, names omitted. Materialized-view rename remains blocked (D.3b carve-out). Body-drift on view / trigger / routine rename blocks withOBJECT_RENAME_UNSUPPORTED. - MySQL renders
RENAME TABLE old TO newfor view-rename (views share the table namespace). Trigger / function / procedure renames lower toDrop*+Create*with aRenameProvenancemarker (no nativeALTER … RENAMEgrammar); missing bodies or body drift block. Sequence rename stays blocked until the E.3 MySQL-sequence rendering contract lands. - SQLite lowers view and trigger renames to Drop+Create
with a
RenameProvenancemarker (no native rename grammar); function / procedure rename blocks (SQLite has no routine model); sequence rename stays blocked until an E.3 SQLite-sequence contract lands.
Pre-plan blockers for malformed overlay payloads:
RENAME_OVERLAY_TRIGGER_KEY_INVALID(trigger entry without thetable::namecanonical form),RENAME_OVERLAY_TRIGGER_CROSS_TABLE_REJECTED(cross-table moves are not renames),RENAME_OVERLAY_ROUTINE_KEY_INVALID(function / procedure entry withoutname(direction:type,...)),RENAME_OVERLAY_ROUTINE_SIGNATURE_MISMATCH(from / to signatures differ). TheMigrationOverlayValidatorobjectTypewhitelist is widened to{table, column, view, trigger, function, procedure, sequence};materialized_viewstays blocked.Sequence-default reprojection: when a plan combines
RenameSequence(old → new)withCreateTable/AddColumn/AlterColumnDefaultops that carryDefaultValue.SequenceNextVal("old"), the newSequenceDefaultReprojectorrewrites the column-default references to"new"and theDependencyAnalyzerrecognisesRenameSequenceas a sequence-provider so the topological sort places the rename strictly before the column-bearing ops. The reprojection is order-independent — the mapper may emitCreateTablebeforeRenameSequenceand the reprojector still walks the full list once.Plan-artifact contract:
migration-plan.v1gains an optional versionedrenameProjections[]field carryingcandidateId,objectType,fromPath/toPath, overlay provenance,renameOperationId(null → fallback) andfallbackOperationIds+fallbackReason. The payload is gated behind a new semantic extensionMigrationPlanArtifactFeatures.RENAME_PROJECTIONS_V1 = "rename-projections.v1". Producers MUST callMigrationPlanArtifact.withRenameProjectionExtension()before signing (the validator surfacesPLAN_ARTIFACT_RENAME_PROJECTIONS_REQUIRE_EXTENSIONotherwise); consumers that do not list the extension in their supported set reject the artifact via the existingPLAN_ARTIFACT_UNKNOWN_SEMANTIC_EXTENSIONblocker. Old consumers are therefore forced to reject artifacts they cannot read correctly rather than running the Drop+Create fallback as an ordinary destructive change.New code carriers (hexagon:core):
DiffOperation.RenameView/RenameTrigger/RenameFunction/RenameProcedure/RenameSequence(withbodyHash/signaturepayloads where required);RenameProvenance(now public — it is exposed via the publicDiffOperationdata classes);RenameSupportsealed interface;ObjectRenamePolicyObjectRenamePolicyRegistry;ObjectRenameCandidate;RenameObjectMapper;SequenceDefaultReprojector;MigrationPlanArtifactRenameProjection;MigrationPlanArtifactFeatures. All tenCreate*/Drop*ops for view / trigger / function / procedure / sequence gain an optionalrenameProvenance: RenameProvenance? = nullfield (internal metadata; the public artifact projection is viarenameProjections[]). NewMigrationBlockedReasonenum valueOBJECT_RENAME_UNSUPPORTED(ordinal 10, appended at the tail — earlier ordinals stay pinned).
CLI surface: no new
--rename-{view,trigger,…}inline shortcuts in this slice. The canonical keys (triggertable::name, routinename(direction:type,...)) are handled more cleanly in--migration-overlay <file>payloads than on the command line; inline shortcuts may follow if operator demand justifies the argument-parser complexity.Plan-Doc:
docs/planning/done/ImpPlan-0.9.7-F.4-routine-trigger-view-renames.md. - PostgreSQL renders native rename SQL for every kind —
-
0.9.7 E.2 Trigger-Rendering Vollscheibe —
CreateTrigger,ReplaceTriggerandDropTriggerleaveOpCategory.UNSUPPORTEDin all three dialects. PostgreSQL renders strictCREATE TRIGGER ... EXECUTE FUNCTION <ref>with a[schema.]identifier(args)body validator (inline PL/pgSQL blocks withTRIGGER_BODY_NOT_FUNCTION_REFERENCE); Replace uses nativeCREATE OR REPLACE TRIGGERon PG-14+, Drop+Create otherwise. MySQL renders inline-body triggers without aDELIMITERwrapper and a bare-name DROP (the<table>.<name>form is a MySQL syntax error that the renderer never produces); WHEN, statement-level and INSTEAD-OF triggers block with dialect-specificMYSQL_TRIGGER_*_UNSUPPORTEDdiagnostics. SQLite reuses the existing rebuild-pipelinecreateTriggerSqlbuilder for a bit-identicalBEGIN ... ENDoutput across standalone and rebuild-absorbed paths; the rebuild planner is the authoritative filter for trigger ops whose table is being rebuilt. Replace is always Drop+Create on MySQL and SQLite.Gap contract: every Drop+Create-Replace carries
OperationRisk.hasGap = true(set by the Mapper through a new core-localTriggerPlanningContext) and emits aW_TRIGGER_REPLACE_GAPwarning on the report. The new--strict-gap-operationsflag onschema migratelifts gap-bearing operations toMANUAL_ACTION_REQUIRED(Exit 8); the strict path emits zero statements and anOPERATION_HAS_GAP_STRICT_BLOCKEDblocker diagnostic.Identity safety: a new
TriggerNameCollisionDetectorruns on the rawList<NamedTrigger>before the trigger map is materialised, so two triggers sharing a name across different tables surface asTRIGGER_NAME_COLLISIONinstead of silently collapsing. The file path relies on Jackson's existingFAIL_ON_READING_DUP_TREE_KEY(already enabled inYamlSchemaCodec) for duplicate-map-key detection.New code carriers:
TriggerPlanningContext+TriggerReplaceMode(hexagon:core, dependency-free);TriggerCapability+TriggerCapabilityDefaults+resolve(postgresMajorVersion)(hexagon:ports-read);TriggerPlanningContextFactory(application).OperationRiskgains ahasGap: Boolean = falsefield consumed by the dialect-neutral strict-gap lift in each render context. NewMigrationBlockedReasoncodes appended at the enum tail:TRIGGER_NAME_COLLISION,TRIGGER_BODY_NOT_FUNCTION_REFERENCE.Carve-outs deferred to follow-up slices: DEFINER rendering for MySQL, schema-qualified DROP, SQLite trigger reverse-read from
sqlite_master, and theTriggerDefinitionmodel widening (eventsplural with column lists,enabledState). The structuralMap<TriggerKey, TriggerDefinition>migration that would let the model hold genuine(name, table)ambiguity remains an F.4 RenameTrigger precondition. -
0.9.7 Materialized-View-Migrationsvertrag Sub-Slice C — closes the D.3b plan: a new
MaterializedViewDependencyDetectorwalks both schemas to find MVs whosedependencies.tables/views/functionspoint at an object being dropped or replaced in the same plan without a matchingDrop/Replacefor the MV itself. Each(materialized-view, dropping-op)orphan-pair becomes aBLOCKED_DEPENDENCY_UNRESOLVEDplanner blocker (BLOCKER severity) on the dropping op AND a structuredMaterializedViewDependencyBlockeronDiffResult. Cross-MV dependency (MV-A references MV-B) follows the same rule because MVs share the view name-space.RoutineDependencyAnalyzerrecognises MV ops in its drop-side index and create-side edges, so the topological sorter placesDropMaterializedViewbefore the matchingDropTable(PG-without-CASCADE order).Report contract:
SchemaMigrateMaterializedViewContractViewgains adependencyBlockerslist with(droppingOperationId, droppingPath, droppingKind)per orphan. When the MV has no in-plan operation (purely orphaned), the report builder synthesises a contract row withaction=ORPHANandoperationIdset to the first dropping op so the operator sees the orphan inmaterializedViews[].primaryBlockedReasonbecomesMATERIALIZED_VIEW_DEPENDENCY_UNRESOLVED. The precedence table appends the new code at position 10 (lowest priority — only fires on otherwise-renderable PG paths). JSON and YAML report renderers emit the newdependencyBlockerssubfield.No new
DiffOperationsubclasses in this slice; the wiring is pure analyzer-and-planner work. -
0.9.7 Materialized-View-Migrationsvertrag Sub-Slice B — dedicated
DiffOperation.ReplaceMaterializedViewclass closes out theOperationMapper.mapViewsrouting: a body / columns change on a view where both sides stay materialized now routes to the new op instead of the legacyReplaceViewplaceholder. PostgreSQL emits the replace as two statements —DROP MATERIALIZED VIEW <name>;followed byCREATE MATERIALIZED VIEW <name> AS <after.query>;— sharing the sameoperationIdso Workstream-G'sexecutionStatementGroupstreats them as one atomic unit under PG's transactional DDL. Down emits the symmetric inverse usingbefore.query; absentbefore.queryblocks with the newMATERIALIZED_VIEW_REPLACE_DOWN_BODY_UNKNOWNcode (Up still renders fine — only the rollback contract is affected). MySQL and SQLite block the replace via the existingMATERIALIZED_VIEW_NOT_SUPPORTED_BY_DIALECTpath. ThematerializedViews[]report surfaces aREADYrow withstalenessAfterUp=FRESH_AFTER_REPLACE_REFRESH,refreshSteps=[DROP_CREATE_INITIAL_REFRESH]andlocking=ACCESS_EXCLUSIVEfor renderable PG replaces; the legacyBLOCKED_UNTIL_REFRESH_STALENESS_CONTRACTplaceholder is now fully retired.Internal API change:
DiffOperationgains one more sealed subclass (ReplaceMaterializedView). Embedders / extension code that pattern-matches againstDiffOperationmust triage it or the Kotlin compiler rejects thewhenas non-exhaustive. -
0.9.7 Materialized-View-Migrationsvertrag Sub-Slice A — dedicated
DiffOperation.CreateMaterializedView/DropMaterializedViewclasses plus a newDiffObjectType.MATERIALIZED_VIEWenum value. PostgreSQL rendersCREATE MATERIALIZED VIEW <name> AS <query>andDROP MATERIALIZED VIEW <name>(Down inverses each other; Drop-Down requires the originalquerybody, otherwise it blocks withMATERIALIZED_VIEW_DOWN_QUERY_UNKNOWN). MySQL and SQLite emit a dialect-specificMATERIALIZED_VIEW_NOT_SUPPORTED_BY_DIALECTblocker instead of the previous generic D.3a diagnostic. ThematerializedViews[]report carrier now lists concretestatus/stalenessAfterUp/refreshSteps/locking/rollbackvalues (READY+FRESH_AFTER_INITIAL_REFRESHfor Create,READY+NOT_APPLICABLE_DROPfor Drop on PostgreSQL;BLOCKED_*codes plus the newprimaryBlockedReasonfield elsewhere). Operator-visibleView↔MaterializedViewconversions are now deterministically blocked withBLOCKED_CONVERSION_UNSUPPORTED.Internal API change:
DiffOperationis a sealed interface; embedders / extension code that pattern-matches against it must triage the two new subclasses or the Kotlin compiler will reject thewhenas non-exhaustive. The non-MV renderer default is a block withMATERIALIZED_VIEW_NOT_SUPPORTED_BY_DIALECT.Replace-style materialized-view changes still fall through to the legacyReplaceViewpath with the existing D.3a guard (MATERIALIZED_VIEW_DIFF_UNSUPPORTED); the dedicatedReplaceMaterializedViewop lands in Sub-Slice B. -
0.9.7 Routine-Capability-Configurable-Source — operator override for the per-routine-kind capability of stored Functions/Procedures. Adds the repeatable
--routine-capability=<kind>:<key>=<value>[,...]CLI flag and aroutineCapability:section in.d-migrate.yaml. Precedence per routine kind: CLI > YAML > dialect/server-version defaults. Allowed kinds:function,procedure. Allowed keys:enabled(true/false),minServerVersion(major.minor.patch, MySQL/MariaDB only — must remain a quoted string in YAML to avoid SnakeYAML's float coercion of8.0). Structurally broken YAML raisesConfigResolveExceptionbefore the pipeline runs; semantically invalid input (unknown kind/key, unparsable bool or version, duplicate per-kind CLI flag, floatminServerVersion) flows through asEffectiveRoutineCapability.Invalid(reason), which the MySQL renderer surfaces asROUTINE_CAPABILITY_CONFIG_INVALID+MANUAL_ACTION_REQUIRED(Exit8) with the operator's reason string appended to the diagnostic body. PostgreSQL ignores the capability today; itsCREATE OR REPLACEsupport is unconditional.Internal API change (no user-facing migration):
DdlGenerationOptions.routineCapabilityis now of typeEffectiveRoutineCapability(a sealed interface withValid/Invalidvariants) instead of the previousRoutineCapabilitydata class. The previous data class is available asEffectiveRoutineCapability.Validwith identical fields; affects only embedders / extension code that constructedDdlGenerationOptionsdirectly (the CLI surface stays unchanged). -
E.1 Routine-Migration Slice E — closes the Slice-A reverse-read carve-out: the PostgreSQL schema reader now populates
security/definer/searchPathfor routines directly frompg_proc. NewPostgresProgrammabilityMetadataQueries.listRoutineIdentityAttributesprojectsprosecdef,pg_roles.rolname(joined viaproowner, usingpg_rolesinstead ofpg_authidso the query works without superuser), andproconfig— the latter is parsed to extract thesearch_pathsegment. The projection is keyed byRoutineKey(name, oid)so same-name overloads stay distinct.readPostgresFunctions/readPostgresProceduresconsume the projection and assemble the final identity. With the carve-out closed,--generate-rollbackDown-render for PG routines now works against a live DB whose routines declareSECURITY DEFINER+SET search_path: the prior body comes fromroutine_definition, identity attrs frompg_proc, and the diff no longer emits a spuriousReplaceFunctionpurely because the reverse side was missing the identity attrs.sqlModestays null on the PG side — it's a MySQL-only concept. The MySQL routine reader is unchanged (routine bodies / identity are an MySQL-flavoured carve-out that a later slice can revisit).This is the final slice of E.1: workstreams A → B → C → D → E are complete. MySQL routine rendering, dependency sorting across all five object classes, and the engine-verified down-body recovery now ship together.
-
E.1 Routine-Migration Slice D.3 — MySQL trigger reader now surfaces the owning-table edge into
DependencyInfoso the Slice-D.1RoutineDependencyAnalyzercan build theDropTrigger → DropTablereverse-topology edge for MySQL schemas.MysqlRoutineReader.readTriggerssetsdependencies = DependencyInfo(tables = listOf(table))frominformation_schema.triggers.event_object_table— the same value already used forTriggerDefinition.table. No new queries were added: the trigger ↔ table edge is read from the existinglistTriggersprojection. View ↔ table / view ↔ routine edges were already projected viaVIEW_TABLE_USAGE/VIEW_ROUTINE_USAGE. Routine ↔ table / view / sequence edges stay manifest-based by design — routine bodies are opaque to MySQL's information schema, so the operator must declare them independencies.functions/dependencies.tables/dependencies.sequenceson the routine's schema entry. -
E.1 Routine-Migration Slice D.2 — PostgreSQL reverse-read now projects routine and trigger dependency edges from
pg_depend/pg_triggerdirectly into the neutralDependencyInfocarrier consumed byRoutineDependencyAnalyzer(Slice D.1). New queries inPostgresProgrammabilityMetadataQueries:listRoutineRelationDependenciesjoinspg_proc→pg_depend→pg_classand discriminates bypg_class.relkindto populateDependencyInfo.tables/views/sequencesfor functions and procedures. Materialized views land inviews; sequences (relkind = 'S') populate the Slice-D.1-introducedsequenceslist.listTriggerFunctionDependenciesjoinspg_trigger.tgfoid→pg_proc.oidand populatesDependencyInfo.functionson the trigger, so aDropTriggercorrectly precedes the matchingDropFunctionin the reverse-topology sort.readPostgresFunctions/readPostgresProcedures/readPostgresTriggersconsume the new queries and attachDependencyInfowhen projection rows exist. The existing reverse-read carve-out for identity attributes (security/definer/searchPath/sqlMode) is unaffected; D.2 ships only the dependency-edge data. MySQL adapter behaviour is unchanged.
-
E.1 Routine-Migration Slice D.1 — manifest-driven cross- object dependency edges for the file-to-file path. New
RoutineDependencyAnalyzer(hexagon:core/.../diff/migration/,internal object) runs as a second pass after the existingDependencyAnalyzer. It adds Create/Replace-side edges from routines / views / triggers onto their referenced tables / views / functions / procedures / sequences (via each definition'sDependencyInfo), plus reverse-topology Drop edges so aDropViewruns before the matchingDropTable, aDropTriggerbefore theDropFunctionit referenced, etc. Triggers also pick up an unconditional edge onto their owning table viatrigger.table.DependencyInfogains asequences: List<String>field; the codec only emits the YAML key when non-empty so older schema-file goldens stay byte-identical.DiffPlannerintegrates the analyzer between the FK pass and the topological sorter. Two routines that co-exist in a plan without a manifest edge in either direction surface asUNSAFE_DEPENDENCY_PAIRWARNING diagnostics (the D.1 follow-up downgraded from BLOCKER so file-only multi- routine plans aren't locked out by default; the D.4 topology evaluator handles the actual routing decision — see the Slice D.4 entry). The existing genericDEPENDENCY_CYCLEcode stays as the cycle marker; the plan's earlierROUTINE_DEPENDENCY_CYCLEproposal collapses into it because the cycle detector is class-agnostic anyway.DependencyAnalyzerfile KDoc updated: the Phase-D carve-outs for Drop-side ordering, Replace-body deps, and Trigger ordering are now closed; materialized-view refresh remains out of scope. PostgreSQL and MySQL renderers stay byte-identical (no Slice A/B/C test churn). -
E.1 Routine-Migration Slice C.3 — Dependency-Guard stub +
DROP + CREATEfallback for the MySQL renderer. NewDependencyGuardenum (SAFE/UNSAFE/UNKNOWN) andDependencyGuardEvaluatorlive inhexagon:ports-read. The evaluator uses an explicitly conservative stub heuristic: a routine operation isSAFEiff it is the only op in the plan; any co-resident op flips the guard toUNSAFE. Slice D will replace the body with a real topology evaluator; the public contract stays stable. The MySQL routine renderer'sDisabled-capability path now consults the guard:SAFEemitsDROP+CREATE(two statements, noOR REPLACE);UNSAFEorUNKNOWNkeep the previousROUTINE_CAPABILITY_DISABLED+MANUAL_ACTION_REQUIREDblocker. Every guard consultation emits aDEPENDENCY_GUARD_HEURISTICINFO diagnostic so reports show that the routing came from the stub, not a topology proof.MysqlDiffRenderContextgains aplan: DiffResult?field so the routine renderer can ask the evaluator without changing the public renderer API. -
E.1 Routine-Migration Slice C.2 — MySQL-family function and procedure renderer joins PostgreSQL in the diff/render pipeline. New
MysqlDiffRoutineOpscoversCreate/Replace/DropofFUNCTIONandPROCEDUREin both Up and Down direction. The body is emitted verbatim — noDELIMITERwrapper — so the canonical plan artefact remains a single structured statement; display variants may add aDELIMITERwrapper in a later slice.MysqlDiffDdlGeneratorgains anOpCategory.ROUTINEdispatch. Capability-gatedCREATE OR REPLACE: the renderer consultsDdlGenerationOptions.routineCapabilityand the livemysqlServerVersionfromMysqlSchemaReader. Post-F.11, Oracle MySQL defaults toDisabled, while live MariaDB targets resolve toActive.Active⇒CREATE OR REPLACE;Disabled(Oracle MySQL, capability off, orminServerVersionfloor unmet) routes through the dependency guard and either emitsDROP+CREATEor blocks; the reserved defensiveInvalidConfigbranch yieldsROUTINE_CAPABILITY_CONFIG_INVALID+MANUAL_ACTION_REQUIREDonce a configurable capability source can make that state reachable. Down-render blocks withROUTINE_DOWN_BODY_UNKNOWNwhen the prior body is missing.SchemaReadResultcarries a newmysqlServerVersionfield populated byMysqlSchemaReaderviaMysqlMetadataQueries.readServerVersion(); the probe is best- effort (a privilege error onVERSION()falls back to null instead of failing the read).ResolvedSchemaOperandand the render pipeline thread the version through toDdlGenerationOptions. PostgreSQL renderers and tests stay byte-identical. -
E.1 Routine-Migration Slice C.1.a — Capability + Debug-Body infrastructure for the upcoming MySQL routine renderer (Slice C.2). New types
RoutineCapability,RoutineKindCapability,RoutineCapabilityResolution(Active/Disabled/InvalidConfig),RoutineCapabilityDefaults,RoutineBodyDisplayandMysqlServerVersion(with parser for8.0.36-log/5.7.44/10.11.6-MariaDB/8.4.0) live inhexagon:ports-read; the layering choice keepshexagon:core's zero-dep contract intact.MysqlMetadataQueries.readServerVersion(JdbcOperations)exposes the live MySQL version.SchemaMigrateRequestgains adebugBody: Boolean = falsefield;SchemaMigrateReportgainsbodyDisplay: RoutineBodyDisplay = SCRUBBED_ONLY.schema migrate --debug-bodyflips the report's display plane toRAW_DEBUG.schema rollbackdoes NOT carry a--debug-bodyflag — the rollback runner always redacts itsexecutionErroroutput viaRoutineBodyLogRedactor(see Slice F.1). Execution-Plane (the in-memory statements that the--executepath runs against the DB) is unaffected and always carries raw bodies. No renderer consumes the capability in C.1.a; PostgreSQL Slice A/B output stays byte-identical. Slice C.2 wires the MySQL renderer; Slice C.3 adds the Dependency-Guard. -
E.1 Routine-Migration Slice B — PostgreSQL procedures join functions in the diff/render pipeline.
ProcedureDiffgains the samesecurity/definer/searchPath/sqlModevalue-change fields that Slice A added toFunctionDiff; the comparator now hash-compares procedure bodies throughRoutineBodyNormalizerso cosmetic CRLF / trailing-semicolon / line-ending changes no longer trigger a spuriousReplaceProcedure. A newPostgresDiffProcedureOpsrenderer emitsCREATE PROCEDURE/CREATE OR REPLACE PROCEDURE/DROP PROCEDUREwith dollar-quoted bodies, parameter lists, optionalLANGUAGE/SECURITY/SET search_pathclauses, and PostgreSQL's signature contract for DROP (OUT params dropped, INOUT prefixed).--generate-rollbackblocksReplaceProcedureDown withROUTINE_REPLACE_DOWN_BODY_UNKNOWN+ROLLBACK_NOT_POSSIBLEwhen the prior body is unknown; with a known prior body it rendersCREATE OR REPLACE PROCEDUREreverting to the before-body.ROUTINE_BODY_DOLLAR_TAG_COLLISIONguards against bodies that contain the renderer's$body$tag. Trigger ops remain blocked withDIALECT_UNSUPPORTED_OPERATIONuntil Slice E.2; MySQL routine rendering and routine dependency sorting stay scheduled for Slice C/D in the same ImpPlan. -
E.1 Routine-Migration Slice A — PostgreSQL functions are now diffable and renderable.
FunctionDefinition/ProcedureDefinitiongain optionalsecurity/definer/searchPath/sqlModefields; the newRoutineSecurityenum (INVOKER/DEFINER) is part of routine identity. Body equality is decided throughRoutineBodyNormalizer(LF collapse, line-trailing whitespace strip, single trailing semicolon removal) plus a SHA-256 hash — cross-platform schema files no longer trip spuriousReplaceFunctionops on CRLF / trailing-newline differences. The PostgreSQL renderer (PostgresDiffFunctionOps) emitsCREATE FUNCTION/CREATE OR REPLACE FUNCTION/DROP FUNCTIONwith dollar-quoted bodies ($body$), parameter lists, return types, language, and optionalSECURITY/SET search_pathclauses.--generate-rollbackblocksReplaceFunctionDown withROUTINE_REPLACE_DOWN_BODY_UNKNOWN+ROLLBACK_NOT_POSSIBLEwhen the prior body is unknown (file-to-file with body omitted); when the prior body is known (file-to-DB reverse read or schema file with full before-body), Down rendersCREATE OR REPLACE FUNCTIONreverting to the prior body. Procedure / trigger ops remain blocked withDIALECT_UNSUPPORTED_OPERATIONuntil Slice B / E.2. Reports surface bodies throughRoutineBodyScrubber.preview(...)— a{hash, length, scrubbedPreview, scrubbingApplied}shape that masks password / token / api-key / JDBC-URL credentials before any preview lands in a diagnostic or artefact. MySQL routine rendering, dependency sorting between routines / views / triggers / tables, and Slice E down-render improvements stay scheduled for later slices in the same ImpPlan. -
F.4 cli-inline-overlay —
schema migrateaccepts two new repeatable flags for inline rename mappings:--rename-table <from>:<to>--rename-column <table>.<from>:<table>.<to>The CLI builds a syntheticmigration-overlay.v1document withsource = "cli-inline"and a stable sentinelcreatedAt = "cli-inline"so two identical invocations produce a bit-identicaloverlayHash(and therefore identicalRename*operation ids and statement stream) regardless of wall-clock. The synthetic document runs through the exact same validator/preflight/mapper pipeline as a file-loaded overlay. Cross-document conflicts (file vs inline or file vs file) block beforeDiffPlanner.plan(...)via a new Pre-Plan cross-document uniqueness gate inMigrationOverlayPreflight.validateBeforePlan, emittingOVERLAY_RENAME_MAPPING_DUPLICATE/OVERLAY_RENAME_MAPPING_AMBIGUOUSwith every conflictingsource/entryIdpair; the reason-classifier folds them intoRENAME_MAPPING_INVALID. Parse-time CLI errors (bad syntax, duplicatefromwithin the same invocation, mismatched table prefix, forbidden SQL-quoting chars) exit 2 before any DB / I/O happens. Inline overlays are deliberately NOT artefact-stable — they're a runtime-only operator shortcut and must NOT be serialised into a publicmigration-plan.v1artefact; use--migration-overlaywith a file for long-lived plans.MigrationOverlayDiagnosticcarries the validator's own fact-bearing message text downstream (no more synthesised "failed F.0 contract validation"), andOVERLAY_ACCEPTEDINFO provenance rows surface inoverlays[]for entries that pass without blockers so report consumers can attribute each rename back to its flag slot or file entry.spec/cli-spec.md§6.1 lists the new flags; Plan-2 §10 F.4 carve-out updated.
-
F.4 rename-mapping-invalid-enum — new
MigrationBlockedReason.RENAME_MAPPING_INVALID(last enum value; existing ordinals unchanged). The application-layer reason-classifier inMigrationOverlayPreflight.buildFailureResult(...)now groups blocker findings by reason using structured overlay context (MigrationOverlayDiagnostic.entryKind/renameObjectType) — no free-form message or entry-ID parsing. All fiveOVERLAY_RENAME_MAPPING_*blocker codes plusOVERLAY_UNKNOWN_ENTRY_KINDtagged with a rename-mappingobjectTypeoutside the current{table, column}whitelist surface asRENAME_MAPPING_INVALID; mixed preflights with a rename-bound and a generic overlay blocker emit twoMigrationBlockers withprimaryBlockedReason = RENAME_MAPPING_INVALID.MigrationOverlayValidationContextgainssupportedRenameObjectTypes(default{"table","column"});MigrationOverlayPreflight.validateBeforePlan(...)exposes the same set as a parameter so the later View-/Trigger-/Routine-Rename slice can widen the whitelist additively. Backward-Compat: bestehende Reports mitMANUAL_ACTION_REQUIREDfuer Rename-Codes bleiben semantisch blockiert; nur die Klassifikation hat sich verfeinert.migration-plan.v1traegt keineMigrationBlockedReasonund ist daher unveraendert.spec/cli-spec.md§6.1 dokumentiert den neuen Exit-8-Fall. -
F.4 dependency-projection (T1–T6) — overlay-bound rename candidates now fold to native
RenameTable/RenameColumnoperations plus synthesised intra-object delta operations (AlterColumn*,AddColumn,AddIndex, …) AND explicit view-reprojection (DropView+CreateViewfrom desired body). Per-dialectRenameDependencyPolicy(Postgres / MySQL / SQLite) gates engine-automatic vs explicit vs blocked dependencies. Migrate report gains arenameProjections[]section (one entry per candidate, success or drop+add fallback) — seespec/cli-spec.md§6.1. Plan-2 §10 F.4 has the status update. Artefakt-Gate decision:renameProjectionsis report-only; it is NOT serialised intomigration-plan.v1. Today's rollback artefact is Down-SQL — the renderers produce natural inverses for every synthesised intra-object delta (AlterColumn*,AddColumn,AddIndex, …) and for the explicit view reprojection (DropView+CreateViewreverse toDropView+CreateViewof the previous body), so--generate-rollbackworks without a versioned plan artefact. Adding aROLLBACK_NOT_POSSIBLEgate for persisted Mischfall-Plaene stays a future tranche — oncemigration-plan.v1gains therenameProjectionsfield, the gate fires for plans that lack it. -
adapters:driven:text-icumodule withIcuUnicodeTextService— production ICU4J implementation behind the newdev.dmigrate.text.UnicodeTextServiceport inhexagon:ports-common. CLI and MCP composition roots inject this service;hexagon:applicationno longer depends oncom.ibm.icu:icu4jdirectly. -
FakeUnicodeTextServicetest fixture inhexagon:ports-common(JDK-only, backed byjava.text.NormalizerandBreakIterator) so application-side tests stay free of the ICU4J runtime.
-
0.9.7 Port-Refactor —
DdlDialectContext(2026-05-27) —DdlGenerationOptionstrug bislang nullablemysql*/sqlite*-Felder parallel am Top-Level (mysqlNamedSequenceMode,mysqlServerVersion,mysqlSequenceCanonicity,routineCapabilitymit MySQL-flavored Default,liveSqliteCatalog,sqliteCastPreflights,catalogProbeMode). Diese sind jetzt in einen sealedDdlDialectContextmit VariantenNone,MySql(...),Sqlite(...)gewandert. Renderer/Tests greifen über die neuen Extension-Propertiesoptions.mysqlContext/options.sqliteContextzu. Dialekt-neutrale Felder (spatialProfile,executionMode,checkPreflights,extension*,strictGapOperations,generatedAt,deterministic,deferForeignKeys) bleiben am Top-Level. 30 Files migriert, semantisch identisch.Plan-Doc:
docs/planning/done/sqlite-sequence-emulation-plan.mdPhase B.0; Memory-Pin:feedback_hexagon_dialect_context. -
0.9.7 E.2 Folge-Slice — SQLite Trigger Reverse-Read (Sub-Slices A–E) (2026-05-22) — neuer token-basierter
SqliteTriggerSqlParserersetzt den regex-/substring-basiertenSqliteTypeMapping.parseTriggerSql. Reverse-Read aussqlite_master.sqlpopuliert jetztforEach,condition(WHEN-Klausel) und einen Round-Trip-symmetrischenbody(genau ein trailing;vorENDwird gestrippt, weil der Renderer unconditional;\nEND;anhängt — sonst driften Bodies nach ein paar Cycles zu...;;).Diagnostics:
R210/R211werden zuACTION_REQUIRED(vorherWARNING), wenn dieCREATE TRIGGER-Grammatik nicht parsebar ist.- Neu:
R212(ACTION_REQUIRED) für schema-qualifizierte Trigger-Namen oder Target-Tabellen (main.trg,aux.t). Betroffene Trigger werden aus dem Reverse-Read-Ergebnis ausgeschlossen, keinTriggerDefinitionwird gebaut. - Neu:
R213(WARNING) fürUPDATE OF <cols>-Spaltenliste — das neutrale Modell behandelt UPDATE als whole-row.
Auswirkung:
schema comparezwischen zwei SQLite-Live-DBs mit identischen WHEN-getragenen Triggern emittiert keine spurious False-Positive-Diffs mehr;schema migrate(file-to-DB) gegen SQLite triggert keinenDrop+Create-Replace mehr, wenn das Source-File und das Live-DB-Trigger semantisch übereinstimmen.Breaking-Change-Note: alte Live-DBs mit unparseable Trigger-DDL surfen jetzt als
ACTION_REQUIREDstattWARNING. Tooling, dasnotes.severityfiltert, sollte beide Severities akzeptieren, bevor 0.9.7 ausgerollt wird.Plan-Doc:
docs/planning/done/ImpPlan-0.9.7-sqlite-trigger-reverse-read.md. -
0.9.7 E.2 Trigger-Rendering — data-class extensions on published types:
OperationRisk(hexagon:core) gains a newhasGap: Boolean = falsefield;MigrationBlockedReason(hexagon:ports-read) appends two enum values at the tail (TRIGGER_NAME_COLLISION,TRIGGER_BODY_NOT_FUNCTION_REFERENCE). Source-compatible for callers that use named arguments; callers that use positionalOperationRisk(...)constructors or positional.copy(...)must migrate to named arguments because thehasGapfield sits before the trailingnotes: List<DiffDiagnostic>field. Existing enum ordinals stay stable per the documented append-at-end convention; theMigrationDdlResultTestenum-order pin (RENAME_MAPPING_INVALIDordinal = 7) was extended to assert the new entries at indices 8 and 9. TheSqliteDiffSqlBuilders.createTriggerSql(...)builder — shared between the new standalone trigger renderer and the existing Phase H.3a rebuild-pipeline trigger-recreation path — now normalises a trailing;out of the body before wrapping inBEGIN..END;. Rebuild-pipeline output stays bit-identical to the standalone renderer; goldenness fixtures that asserted viastartsWith("CREATE TRIGGER ...")are not affected, but any fixture that pinned an embedded trailing-;body will see one less terminator. -
E.1 Routine-Migration Slice F.11 — MySQL-family routine capability is now vendor-aware. The neutral
MYSQLdialect defaults to Oracle MySQL semantics, where stored routines do not supportCREATE OR REPLACE; file-to-file MySQL routine replaces therefore use the existing dependency-guardedDROP+CREATEfallback instead of emitting invalid Oracle MySQL SQL. Live MariaDB targets are detected throughMysqlServerVersion.vendor(SELECT VERSION()values such as10.11.6-MariaDB) and keepCREATE OR REPLACEenabled.DdlGenerationOptionsdefaults to the conservative Oracle MySQL capability so direct renderer calls are safe unless tests or future config explicitly opt into the MariaDB capability. -
E.1 Routine-Migration Slice D.4 —
DependencyGuardEvaluatorbody now drives the SAFE / UNSAFE decision from the real dependency-edge graph populated by Slices D.1 / D.2 / D.3 instead of the Slice-C.3 "any co-resident op == UNSAFE" stub heuristic. An op isSAFEwhen no other op in the plan has a declared incoming or outgoing edge to it; any declared edge in either direction flips it toUNSAFE. Cross-plan edge ids (already dropped by the topological sorter as unresolvable) are ignored. The public signatureevaluate(plan, op)is unchanged, so the MySQL renderer's consult sites compile without edits.The MySQL renderer's INFO-severity guard annotation switches from
DEPENDENCY_GUARD_HEURISTICtoDEPENDENCY_GUARD_TOPOLOGYto reflect that the bewertung is no longer a stub.MYSQL_ROUTINE_DROP_CREATE_NON_ATOMICstays active — implicit-commit atomicity is orthogonal to the evaluator change.Practical consequence: the Slice-C.3 test that pinned "ReplaceFunction blocks when ANY co-resident op is in the plan" no longer makes sense — the stub was the only reason that path blocked. It is replaced by two D.4 tests:
- one constructs a true
dependencies.tablesedge from the routine to a co-residentCreateTableand pins that the evaluator finds the edge, returns UNSAFE, and the renderer blocks withROUTINE_CAPABILITY_DISABLED; - the other keeps the same plan shape but removes the
declared edge, pins that the evaluator returns SAFE, and
the renderer falls back to
DROP + CREATEwith the non-atomicity warning.
DependencyGuardEvaluatorTestis rewritten end-to-end: isolated → SAFE, co-resident-without-edges → SAFE, outgoing-edge → UNSAFE, incoming-edge → UNSAFE, cross-plan-edge-id → ignored, mixed-routine-kind without edges → SAFE. Plan-2 §9 E.1 status updated to mark D.4 landed. - one constructs a true
-
E.1 Routine-Migration Slice C.1.b — PostgreSQL function and procedure renderers emit the canonical
ROUTINE_DOWN_BODY_UNKNOWNdiagnostic code instead of the older Replace-specificROUTINE_REPLACE_DOWN_BODY_UNKNOWNwhen--generate-rollbackblocks because the prior routine body is unknown. The change is diagnostic-only: blocker reason (ROLLBACK_NOT_POSSIBLE), message text, and SQL output stay identical. Test pins inPostgresDiffFunctionOpsTest/PostgresDiffProcedureOpsTestand the--generate-rollbackrow inspec/cli-spec.md§6.1 follow. The Replace-Up variantROUTINE_REPLACE_UP_BODY_UNKNOWNstays as-is — a missing after-body on Replace is structurally different from a missing rollback target. Plan §1 sees the generic code as authoritative; MySQL slices (C.2+) will emit only the generic code from the start. -
JsonCanonicalizer: converted frominternal objecttointernal class JsonCanonicalizer(unicodeText: UnicodeTextService)so JCS NFC normalization runs through the port instead of a static ICU4J facade. -
DefaultPayloadFingerprintServiceandOutputFormattertakeUnicodeTextServiceas a required constructor argument. -
spec/architecture.md: ICU4J now belongs toadapters/driven/text-icu, notapplication/cli.
-
0.9.7 F.4 Renderer-Blocker-Bridge — preserve
OBJECT_RENAME_UNSUPPORTEDasprimaryBlockedReason(2026-05-19) —PostgresDiffRenderContext,MysqlDiffRenderContextandSqliteDiffRenderContextused to wrap every planner-emitted BLOCKER diagnostic into a singleMigrationBlocker(reason = DIALECT_UNSUPPORTED_OPERATION), collapsing the F.4-specificMigrationBlockedReason.OBJECT_RENAME_UNSUPPORTEDreason that the Mapper / Planner had set per F.4 plan-doc §5.2.The fix introduces a
PlannerBlockerClassifierinhexagon:ports-readthat mapsDiffDiagnostic.code → MigrationBlockedReasonwith the initial entry"OBJECT_RENAME_UNSUPPORTED" → MigrationBlockedReason.OBJECT_RENAME_UNSUPPORTEDand a conservative default toDIALECT_UNSUPPORTED_OPERATIONso legacy codes (CONSTRAINT_NOT_DIFFABLE,MATERIALIZED_VIEW_DIFF_UNSUPPORTED, etc.) keep their pre-F.4 contract. The three render contexts now group planner-blockers by classified reason and emit oneMigrationBlockerper reason, so a Materialized-View-Rename / body-drift / missing-body rename candidate surfacesprimaryBlockedReason = OBJECT_RENAME_UNSUPPORTEDend-to-end while a co-existingCONSTRAINT_NOT_DIFFABLEkeeps its legacyDIALECT_UNSUPPORTED_OPERATIONreason.Breaking-change-note for downstream consumers: the report field
summary.primaryBlockedReasonfor F.4 materialized-view-rename and body-drift routine/trigger scenarios now readsOBJECT_RENAME_UNSUPPORTEDinstead ofDIALECT_UNSUPPORTED_OPERATION. Existing tooling that pinned the old reason as primary for these scenarios needs to widen its expectation. The H.3 audit found no in-tree consumer that did so, and the enum valueMigrationBlockedReason.OBJECT_RENAME_UNSUPPORTEDhas existed since F.4 A.1 (2026-05-18) — only the wiring through the renderer wrap was missing.New tests:
PlannerBlockerClassifierTest(hexagon:ports-read) pins the mapping + the publicOBJECT_RENAME_UNSUPPORTED_CODEconstant.PostgresDiffObjectRenameTest/MysqlDiffObjectRenameTest/SqliteDiffObjectRenameTesteach get a sibling case that drives the fullplanner.plan(...) → gen.generateUp(...)pipeline through a mapper-only-block scenario (materialized-view rename on PG, trigger-body-drift on MySQL / SQLite — both surfaceprimaryBlockedReason = OBJECT_RENAME_UNSUPPORTED).SchemaMigrateRunnerTestgets an end-to-end pin that the report-emit pipeline carries the new primary reason through to stdout.
§11 DoD Box (b) Carve-out (committed in
ffc6970e) is resolved. Plan-Doc:docs/planning/done/ImpPlan-0.9.7-F.4-renderer-blocker-bridge.md. -
0.9.7 E.3 Folge-Slice — MySQL Sequence Drift-Check Plan-Compliance-Fixes (2026-05-20) — zwei Verhaltensabwei- chungen zwischen Plan-Doc und Implementierung des Drift-Checks korrigiert:
MysqlSequenceCanonicityGateroutet jetztMISSING + DROPfürSEQUENCE_ROWundSUPPORT_TABLEals Block (E124_MYSQL_SEQUENCE_MISSING_FOR_DROP→MANUAL_ACTION_REQUIRED). Plan-Doc §3.1 hatte das schon immer als "Missing → UPDATE/DELETE-Path: Block" deklariert; Renderer war aufOpIntent.DROPschon korrekt verdrahtet, das Gate hat es aber durchgewunken. Routinen und Column-Trigger fallen weiterhin auf Proceed durch, weil dasDELETE FROM dmg_sequencessie nicht braucht und das column-trigger-Drop ohnehin idempotent ist.MysqlSequenceCanonicityProbeAdapterverifiziert Routine- und Trigger-Bodies jetzt jenseits des Marker-Substrings: der Body zwischenBEGINund letztemENDwird normalisiert (backticks raus, lowercase, whitespace kollabiert) und gegen die kanonische Form ausMysqlSequenceEmulationTemplatesverglichen. Driftet ein operator-bearbeiteter Routine-/Trigger-Body bei intaktem Marker →body_signature-Drift mit Preview im Report. Für Trigger zusätzlichsequence_reference-Check: der tatsächlichedmg_nextval('…')-Call muss den Sequence- Namen referenzieren, den der Plan erwartet — ein operator-verschobener Trigger (Marker stehen lassen, Call auf andere Sequence umbiegen) bricht jetzt sichtbar.
-
0.9.7 E.3 Folge-Slice — Drift-Check Trigger-Gate für reine Column-Default-Migrationen + Naming-Lift (2026-05-20) — Lücke geschlossen, in der
AddColumn/AlterColumnDefaultmitSequenceNextVal-Default ihrenDROP + CREATE TRIGGER-Block ohne Probe-Konsultation ausführten, weilMysqlSequenceCanonicityStageauf!hasSequenceOps(plan)short-circuitete: ein operator-modifizierter Trigger wurde stillschweigend überschrieben.hasSequenceOps→hasSequenceRelatedOpszählt jetzt auch Column-Ops mitSequenceNextVal-Default;MigrationPreflightPlanneremittiert pro solcher Column-Op eine SUPPORT_TRIGGERNOT_RUN_*-Declaration; der Stage-Failure-Stamping pflegt SUPPORT_TRIGGER-Declarations mit kanonischem Trigger-Namen.MysqlSequenceSupportNamingwurde nachhexagon/ports-readgehoben, sodass Stage (application), Renderer (driver-mysql) und Probe-Runner (driving/cli) dieselbe Naming-Quelle verwenden; das driver-sideMysqlSequenceNamingist nun Facade darüber. -
0.9.7 E.3 Folge-Slice — Report-Renderer + BLOCKER- Diagnostic-Symmetrie (2026-05-20) —
SchemaMigrateReportRendereremittiertemysqlSequenceCanonicityweder in JSON noch in YAML, obwohl das DTO befüllt wurde — Operatoren sahen den Plan-dokumentierten Feldnamen, aber nie die tatsächlichen Declarations. Beide Renderer geben das Feld jetzt aus (JSON-Projection in eigenemSchemaMigratePreflightRenderers-Object, damit der Main-Renderer unter Detekt'sTooManyFunctions-Budget bleibt). Zusätzlich trägt der Trigger-Drift-Block imAddColumn/AlterColumnDefault-Pfad jetzt einen BLOCKER-severityDiffDiagnostic(vorher WARNING ohne Blocker-Attach), der direkt amMigrationBlocker.diagnosticshängt — gleiche Report-Semantik wie die Sequence-Op-Pfade.MysqlDiffRenderContext.addBlocker(reason, opIds, diagnostics)undrecordDiagnostic(...)neu (letzteres schreibt in den Diagnostic-Stream ohnerendered/skippedzu berühren, sodass dierendered ∩ skipped = ∅-Invariante desMigrationDdlResultbei vorab emittierter Column-DDL nicht verletzt wird).
- MCP server (
mcp serve): production-ready Model Context Protocol v1 server over bothstdioand Streamable HTTP, with initialize/capability negotiation, principal context and per-run tenant scoping. The full MCP-Server milestone (Phases A–G) lands in this release; the surface followsspec/ki-mcp.mdandspec/mcp-server.md. - MCP read-only schema tools:
schema_validate,schema_generateandschema_compareroute through the existing validator, DDL generators and comparator. Schema sources can be inline payloads, file paths or tenant-scopedschemaRefartifacts;schema_compareemits column-level findings with structureddetails.before/afterand falls back to a scrubbeddiffArtifactRefwhen the inline budget is exceeded. - MCP capability discovery:
capabilities_listexposes dialects, formats, limits andexecutionMeta.tools/listis a stable registry; HMAC-sealed cursors paginate the list-tool surface (job_list,artifact_list,schema_list,profile_list,diff_list). - MCP resource discovery: standard
resources/list,resources/templates/listandresources/readare wired against typed resource URIs (sealed ADT) with tenant-scope enforcement and a no-oracle error policy.artifact_chunk_getreads tenant-scoped artifacts via 4-segment URIs with HMACnextChunkCursorpaging. - MCP write tools (Phase F):
data_import_startanddata_transfer_startaccept connection- and schema-refs, run structural pre-idempotency validation, perform schema-ref import preflight and join the Phase-E job pipeline. The policy-pflichtigeartifact_upload_initvariant carries session metadata, drives an approval flow, terminally fails sessions on oversize segments and releases init quotas on expiry / finalisation failure. - MCP AI tools (Phase G):
procedure_transform_plan,procedure_transform_executeandtestdata_planproduce signed AI artifacts via the newAiProviderPort/NoOpAiProvider,AiToolOrchestrator,AiToolOutcomeStoreandAiArtifactMetadataStore.testdata_executematerialises an importable testdata artifact (path A) that is then written viadata_import_start. - MCP prompts:
prompts/listandprompts/getexpose a static prompt registry with prompt-hygiene secret scrubbing. - MCP authorization and audit: HTTP and stdio authorization
contracts (capability-based scope tables, principal binding),
centralised audit wiring with secret scrubbing on every
tools/call, structured error envelope with runtime scrubbing for read-only handlers, andexecutionMeta.requestIdplumbed through every handler. - MCP async jobs and idempotency (Phase E):
schema_reverse_start,data_profile_start,schema_compare_start,data_import_start,data_transfer_startandjob_cancel. Includes a typedIdempotencyStateautomaton withFAILED/markFailed,JobStartOrchestrator,JobStartTransaction, durable approval challenges,ApprovedRetryService, per-workerJobWorkerports and dispatcher withOperationCancelSourcemapping (cancel reasons includingEXECUTOR_SATURATEDand the standard Exit-130 mapping). - MCP policy and approval grants:
ConfiguredPolicyService,GrantIssuerwith fail-closed and demo modes, file-backed approval grants formcp serveand amcp approval-grant issueCLI subcommand that mints either--idempotency-keyor--approval-keygrants.POLICY_REQUIREDenvelopes carryapprovalRequestId,correlationKind,correlationKey,requiredScopes,reasonsandpayloadFingerprint(uniformly across job-, upload- and AI-tool paths). - MCP quotas: typed quota dimensions including
STORED_ARTIFACT_BYTES, operation-key dimension,parallelSegmentWritesdefault, retry-after and owner-tracking, COMPLETED-quota swap on artifact materialisation, AI provider quota with audit metadata. - MCP persistent state (Phase E2): JDBC/Postgres adapters land in a
new
persistence-jdbcmodule —JdbcTransactionRunner,JdbcIdempotencyStore(regular andreserveInitResumepaths),JdbcJobStore,JdbcJobStartTransaction, JDBC quota stack (JdbcQuotaService/JdbcQuotaReservationOwnerStore), Flyway V1 initial migration, contract test suites against Testcontainers Postgres. The MCP bootstrap wires server-state persistence end-to-end. - MCP async executor (Phase E3):
BoundedAsyncJobExecutorwithJobExecutorLifecycle,JobDispatchAdmissiongate, backpressure (RateLimitedwith reasonEXECUTOR_SATURATED), cancel-while-queued inJobDispatcherand graceful shutdown. The defaultSyncExecutoris preserved; async is opt-in viaserver.jobs.executor.mode. - MCP file-backed byte stores:
mcp serveuses a file-backed artifact byte store with stateDir locks, per-key locks, atomicFiles.move/Files.createLinkvisibility, sidecar publish before data, content verification and a startup sweep over<stateDir>/assembly/.... Streaming finalisation viaAssembledUploadPayload+SchemaStagingFinalizerkeeps memory bounded. - MCP upload pipeline:
UploadInitOrchestrator, single-writerUploadInitClaimStorewith claim-CAS, intent-based scope check, dispatch-by-uploadIntent (job_inputvs schema),JobInputFinalizerfor policy-pflichtige uploads, idempotent final-segment replay, finalisation timeout sweeper mapping toOPERATION_TIMEOUT,AbortOutcomeStoreand administrative abort path with policy. - MCP bundle import (data_import_start AP-2): multi-table bundle
import contract — wire schema accepts
tables, the worker extracts the bundle and runs per-table import. - DDL determinism: new
--deterministicflag onschema generateandDdlGenerationOptions.generatedAt.AbstractDdlGeneratoremits a fixed timestamp or omits theGenerated:header line.SchemaGenerateRunnerhonoursSOURCE_DATE_EPOCHand exits 2 on an invalid value.TransformationReportWriter(sidecar reports) shares the same policy. - PostgreSQL bigint identity: forward generation and reverse
engineering of
GENERATED { ALWAYS | BY DEFAULT } AS IDENTITYbigint columns, including an explicit decision to retire thebigserialshorthand in favour of identity columns. - MySQL bigint identity: reverse engineering preserves
BIGINT AUTO_INCREMENTidentity semantics in the neutral schema. - Neutral column generation model: shared core type for identity / default-generation metadata across PostgreSQL, MySQL and SQLite generators and readers.
- Index features:
IndexDefinitionnow supports partial-index predicates and per-column sort directions; the corresponding DDL generators and reverse readers round-trip both. schema reverseflags:--nameand--versionset the neutral schema name/version directly (e.g.--name bess-ems --version 1.0.0).- Foreign-key reverse output:
schema reversealways emits named-constraint entries inconstraints[], including for single-column FKs, so identifiers likeoptimization_objective_breakdowns_run_id_fkeysurvive a reverse/generate round-trip. Column-levelreferencesremains supported for hand-written schemas. - PostgreSQL composite PK/UNIQUE:
schema reversereads composite primary keys and unique constraints viapg_constraint.conkey; primary-key columns are restored torequired: true.
AbstractDdlGenerator.getVersion()returns0.9.6.schema reverse(PostgreSQL) no longer emits column-levelreferencesfrom the reverse path — FKs are always named entries inconstraints[].--split=pre-postFK handling is now dialect-aware:deferForeignKeysis enabled only for PostgreSQL. MySQL and SQLite no longer lose circular FKs to the deferred-FK fence; circular explicit/composite FKs carry full constraint metadata and are rendered as completeALTER TABLE ... ADD CONSTRAINT. Deferred FKs are emitted only for tables that actually exist in the output, and FKs to blocked schema tables are no longer written into post-data.- PostgreSQL
CREATE TABLEordering considers composite-FK constraints when computing dependency order. --split=pre-postPostgreSQL FKs are stripped from pre-data and added back asALTER TABLE ... ADD CONSTRAINTin post-data; output parent directories are created on demand.- MySQL view dependency contract: column-level view dependencies
no longer assume
INFORMATION_SCHEMA.VIEW_COLUMN_USAGE; without introspection privileges or explicit dependencies the affected column-level view cases are blocked instead of being silently approximated. - MCP Schema-Source resolver: tenant policy aligned with the rest
of Phase B/C;
mcp serveconsistently routes inline payloads, file paths and tenant-scopedschemaRefartifacts through one resolver. - Driver timeout layer (Phase E0.7):
PoolSettingsgainsstatementTimeoutMs/networkTimeoutMs, drivers carry a driver-specificconnectionInitSql, and a commonTimeoutDecoratedConnectionlayer plussetNetworkTimeoutis applied uniformly across PostgreSQL, MySQL and SQLite Hikari factories. - MCP plan drift cleanup:
data_export_startremoved from the registry, the HTTPAcceptheader is enforced more strictly, and artifact-upload scope is now checked by intent. schema generate --output --generate-rollbackemits a rollback-DDL header that uses the same deterministic timestamp policy as the forward DDL.- Cancel-Reason scrubbing runs end-to-end across reverse, profile, import, transfer and compare workers; cancel reasons reaching audit/error envelopes are scrubbed before persistence.
schema generate --split=pre-post --generate-rollbacknow exits 2 with a clear stderr message instead of producing inconsistent rollback files.- PostgreSQL primary-key columns from
schema reverseare emitted withrequired: trueagain (regression introduced when the column reader was refactored). mcp serveshutdown hook is unregistered on a normal return so multiple lifecycle entries no longer accumulate.- MCP upload session races: transition-mapping race outcomes map
to typed exceptions; finalize gates
COMPLETEDon artifact materialisation;validateFinalizeis enforced onUploadSessionService.finalize; segment offsets persist in the sidecar (rehash-recovery dropped); divergent artifact-write hashes emit aConflict. - MCP idempotency:
IdempotencyStore.claimApprovedis now atomic before side effects; in-memoryJobStartTransactioncommits idempotency before saving the job; quota releases are race-free (no double release);ApprovedRetryServiceintegrates with quotas. - MCP
job_status_getwire payload aligned with the JSON-Schema, pinned to a no-oracle store property;artifact_chunk_getaligned with spec §5.5 including the empty-artefact branch. - MCP audit pipeline:
AuditFields.resourceRefspopulated in upload handlers;AuditFieldsplumbed through Phase-E tools; HTTP readiness and per-run principal asserted in the integration harness. isAdmincorrectly bypassesScopeChecker.isSatisfiedfor administrative tools.E07PostgresTimeoutBenchdecouplesnetworkTimeoutfromstatement_timeoutto avoid driver-quirk-driven flakes.release-homebrew.ymlpasses--no-build-cacheto the release-asset Gradle build (mirroringbuild.yml), so the workflow does not hit stale Gradle task-output state on a fresh runner. Without the flag,:hexagon:core:koverVerifyflaked at ~75.5% (vs ≥90% required) on the tag run while the parallelbuild.ymlwas green.MysqlSchemaReaderIntegrationTestis aligned with the new reverse contract: single-column FKs are preserved as named entries inTableDefinition.constraints(notColumnDefinition.references) — matching the PostgreSQL-equivalent test and theschema reverseoutput.
- MCP secret scrubbing is applied at every layer that can serialise
user input: tool-error envelopes, schema sinks (
SchemaSecretGuardwith normalised forbidden-key detection), prompt registry (PromptHygieneService), AI-provider audit metadata, response byte-limit fallback artefacts and cancel-reason text. - MCP cursors (
resources/list, list tools, artifact chunks) are HMAC-sealed and rejected on tenant or chunk-key mismatch. - MCP request/response byte limits are enforced centrally with a
generic envelope fallback for unbounded payloads, and an
inline-vs-
artifactRefbyte cap onresources/read. - Approval-grant CLI (
mcp approval-grant issue) requires exactly one of--idempotency-keyor--approval-key, so a grant cannot ambiguously match both correlation kinds.
data_export_startremoved from the MCP tool registry — exports remain CLI-only for 0.9.6 and were never wired in the server path.
- MySQL profiling schema support:
data profilenow accepts--schemafor MySQL end-to-end. Schema introspection and profiling queries use the requested database name instead of silently falling back to the current database. - Profiling integration coverage: the MySQL profiling integration suite now exercises cross-database schema selection against a real Testcontainers MySQL instance, including a second database created at runtime.
- Shared profiling SQL helpers now centralize identifier and schema-qualified table rendering for PostgreSQL, MySQL, and SQLite profiling adapters.
ProfileDatabaseServicepreserves schema information discovered by introspection when profiling tables without an explicit--schemaoverride.- Core quality refactors split
FilterDslTokenizerfrom filter parsing, switched driver discovery toServiceLoader, and separatedValueDeserializer,DialectCapabilities, andSchemaSyncconcerns for maintainability. - Build hygiene: detekt is enforced before test tasks, compiler warnings were cleaned up, and the release/roadmap docs now reflect the 0.9.5 quality milestone.
AbstractDdlGenerator.getVersion()returns0.9.5.- Metadata and profiling precedence fixes ensure explicit connection/schema context is preferred consistently when discovering and profiling tables.
- MySQL sequence DDL escaping now quotes string literals correctly for sequence names, avoiding broken SQL and identifier-injection regressions.
- Import/transfer robustness tightened DDL injection handling, row-by-row fallback behavior, and cleanup paths in the transfer/import stack.
- Detekt enforcement now runs before test tasks so style issues are caught locally before the GitHub Actions workflows.
- ProfileTableService classifies optional profiling failures and reports unexpected errors with operation context instead of hiding them silently.
- MySQL sequence reverse-engineering (
schema reverse): the MySQLSchemaReadernow reconstructsSequenceDefinitionobjects fromdmg_sequenceshelper tables,dmg_nextval/dmg_setvalsupport routines, and canonicalBEFORE INSERTtriggers generated by the--mysql-named-sequences helper_tablemode introduced in 0.9.3. The reverse pipeline runs in three stages: D1 (support-table scan and shape verification), D2 (sequence row materialization with numeric overflow and ambiguity detection), D3 (trigger-basedSequenceNextValdefault assignment with marker cross-validation and guard verification) MysqlMetadataQueries: seven new metadata query methods for support-table existence, shape verification, sequence row listing, routine lookup (withSHOW CREATE FUNCTIONfallback wheninformation_schema.routines.routine_definitionis NULL), and trigger scanning with backtick-safe scope resolutionMysqlSequenceReverseSupport: internal D1 types —ReverseScope,SupportTableState,SupportRoutineState,SupportTriggerState, and sealedDiagnosticKeyinterface (SequenceDiagnosticKey/ColumnDiagnosticKey) for structured reverse diagnostics- Sequence support in
schema compare: JSON and YAML renderers now includesequences_added,sequences_removed,sequences_changedin summary and diff output; operand-level diagnostics (notes, skipped objects) are surfaced per source/target - Warning code W116: emitted when reverse-engineering encounters
degraded sequence support — missing or inaccessible
dmg_sequencestable, invalid table shape, invalid sequence rows, non-canonical support routines or triggers. W116 is deduplicated per diagnostic key and bound to the owning sequence or column - Warn-code ledger
ledger/warn-code-ledger-0.9.4.yaml: documents W116 variants with test paths and evidence sources
MysqlSchemaReader.read()integrates the three-stage reverse pipeline (D1→D2→D3) alongside existing table/view/function/procedure/ trigger reading; support objects (dmg_sequences,dmg_nextval,dmg_setval, canonical triggers) are auto-filtered from user schema output- Sealed
DiagnosticKeyinterface replaces the previousAny-typed diagnostic key, providing type-safe sequence-bound and column-bound diagnostic scoping - Routine and trigger detection uses
SHOW CREATE FUNCTION/SHOW CREATE TRIGGERas fallback wheninformation_schemastrips d-migrate marker comments - Backtick-safe identifier parsing: scope resolution handles
schema-qualified sequence names (
`db`.`seq`vs. unqualifiedseq) correctly AbstractDdlGenerator.getVersion()returns0.9.4
- Safe numeric conversions (Long/Int) reject lossy fallbacks
(Double/Float) and detect overflow in
dmg_sequencesrow validation cycle_enabledcolumn accepts both Integer and Boolean values from MySQL JDBC driver (BIT(1)shape)- Marker strictness enforced:
d-migrate:mysql-sequence-v1version tag,object=nextval/setvalin routines,object=sequence-triggerin triggers - Trigger guard validation requires
NEW.<column> IS NULLmatching the assignment column before accepting a trigger as canonical - Cross-schema sequence references rejected as
NON_CANONICAL SequenceNextValconflict detection prevents duplicate default assignments to the same column- W116 deduplication prevents repeated diagnostics for the same sequence or column across reverse stages
- MySQL trigger scope parsing handles backtick-quoted identifiers robustly
- Safe
--filterDSL fordata export: the--filterflag now accepts a closed, parameterized filter language instead of raw SQL. Supports comparisons,IN (...),IS NULL/IS NOT NULL,AND,OR,NOT, parentheses, function calls (allowlist), and arithmetic. All literals are bound as JDBC parameters. Canonicalized fingerprints ensure stable checkpoint resume across whitespace and case variations default.sequence_nextvalschema form: columns can now declaredefault: { sequence_nextval: <sequence-name> }to express that the column's default value comes from a named sequence. This replaces the legacynextval(...)function-call notation--mysql-named-sequencesCLI option forschema generate:action_required(default — sequences skipped with E056) orhelper_table(opt-in — emulates named sequences viadmg_sequencessupport table,dmg_nextval/dmg_setvalroutines, and canonicalBEFORE INSERTtriggers)- MySQL
helper_tablesequence emulation: generatesdmg_sequencestable,dmg_nextval/dmg_setvalsupport routines, and per-columnBEFORE INSERTtriggers for sequence-based defaults - Warning codes W114, W115, W117: W114 — cache stored but not emulated in MySQL helper-table mode; W115 — lossy trigger semantics (explicit NULL treated like omitted value); W117 — sequence values are transaction-bound in MySQL (rollback retracts increments)
- Error codes E122, E123, E124: E122 — legacy
nextval(...)notation rejected; E123 —sequence_nextvalreferences non-existent sequence; E124 — support object name collision with user schema objects
- Breaking:
--filteris now a closed DSL — raw SQL fragments, dialect-specific operators (LIKE,BETWEEN,ILIKE), and subqueries are no longer accepted. Existing checkpoints with pre-0.9.3 raw-SQL fingerprints are rejected with Exit 2 and a migration hint - Breaking:
nextval(...)function-call notation removed — columns that previously useddefault: "nextval(invoice_seq)"must migrate to the object formdefault: { sequence_nextval: invoice_seq }. This affects all dialects. Search-and-replace heuristic: replacedefault: "nextval(<name>)"withdefault: { sequence_nextval: <name> }in all schema YAML files. The parser now rejects the legacy form with error E122 AbstractDdlGenerator.getVersion()returns0.9.3
- Phase-aware DDL model (
DdlPhase.PRE_DATA/POST_DATA): everyDdlStatementcarries a phase tag;DdlResultprovidesstatementsForPhase(),renderPhase(),notesForPhase()andskippedObjectsForPhase()filter methods --split single|pre-postforschema generate: optional DDL output split into*.pre-data.sql(tables, constraints, sequences, simple views) and*.post-data.sql(triggers, functions, procedures, views with routine dependencies); JSON output usesddl_parts.pre_data/ddl_parts.post_datawithsplit_mode: "pre-post"ViewPhaseClassifier: classifies views intoPRE_DATAorPOST_DATAby declareddependencies.functions, inferred function calls in query text, and transitive propagation through view dependenciesDependencyInfo.functionsand codec support: schema YAML views can declare function dependencies for reliable phase assignment- SchemaReader catalog queries for function/procedure metadata (AP 6.3 Steps F+G)
- Split Golden Master tests, JSON/Report contract tests, CLI split tests and ledger validation (AP 6.7)
- E2E round-trip test: DB→Export→Format→Import→DB→Schema Compare with strict DDL and NULL verification
- Error and warning code ledgers (
ledger/error-codes.yaml,ledger/warn-codes.yaml) with systematic validation against E006–E121 - REST, gRPC, and MCP service specifications for future server interface
- Runner decomposition:
DataExportRunner.executeWithPool(446→26 LOC) andDataImportRunner.executeWithPool(477→24 LOC) decomposed into named step functions (AP 6.6) - Executor parameter grouping:
ExportExecutor(16→4 params) andImportExecutor(14→4 params) use context DTOs instead of flat parameter lists (AP 6.6) - DDL interpolation hardened: CHECK constraints, partition expressions, trigger conditions, and SpatiaLite function calls systematically reviewed; remaining 4 MySQL
-- TODOplaceholders replaced withManualActionRequiredentries (AP 6.5) - Split SQL, JSON and report output implemented for
schema generate(AP 6.4): phase attribution in notes andskipped_objects(kebab-case"pre-data"/"post-data") - Quality refactoring:
ViewPhaseClassifierandStatementInverterextracted;ColumnConstraintHelperextracted per dialect;SchemaCompareprojection DTOs extracted; Runner DTOs and MySQL column helpers refactored;Preflight/Checkpointhelpers extracted for both Runners - Type mappings stabilized: tabular geometry and document type fallbacks consolidated
AbstractDdlGenerator.getVersion()returns0.9.2- Documentation updated:
guide.md(split examples and round-trip workflow),architecture.md(§3.8 phase-aware DDL model),ddl-generation-rules.md(§17 phase-based ordering),quality.mdreview findings refreshed
--since/--since-columnvalidation indata transferaligned withdata export(isNullOrBlankinstead ofisNull)- E060 diagnostic fires only when
dependenciesis null, not when empty - E2E test allowlist replaced with verifiable assertions (no comment-based suppression)
- W120
test_pathand evidence source corrected in warn-code ledger - Sequence diff assertions completed with shared JSON/Report anchor
- Central SQL identifier quoting (
SqlIdentifiers): dialect-awarequoteIdentifier(),quoteQualifiedIdentifier()andquoteStringLiteral()utility; all profiling and introspection adapters route through this single quoting layer, closing the injection surface documented indocs/user/quality.md - Security tests: 17 injection tests with malicious table/column names (
"; DROP TABLE …, Unicode homoglyphs, reserved words) across profiling and DDL paths ManualActionRequiredmodel: structured replacement for-- TODO: …SQL comment placeholders in DDL output; renders as-- TODO: …for backward compatibility in 0.9.1DialectCapabilitiesmodel: declares which schema objects each dialect supports natively (views, functions, procedures, triggers, sequences, custom types, partitioning); used by DDL generators for consistent generate/skip/rewrite decisionsTableDependencySort(hexagon:core): single Kahn's-algorithm-based FK topo-sort utility replacing three prior duplicates (AbstractDdlGenerator,ImportDirectoryResolver,DataTransferRunner); returns sorted tables plus circular edgesconsumer-read-probetest module (test/consumer-read-probe): compile-time verification that read-only consumers can use d-migrate's ports without importing write, CLI, or profiling modules- CI: Docker coverage report stages in
build.yml
hexagon:portssplit into three modules:ports-common(shared driver abstractions:DatabaseDialect,SqlIdentifiers,DialectCapabilities,ConnectionPool),ports-read(read-only contracts:SchemaReader,DdlGenerator,DataReader,DataChunkReader),ports-write(write-oriented contracts:DataWriter,TableImportSession,ImportOptions,ExportOptions,DataChunkWriter, checkpoint storage)- Profiling adapters extracted into optional modules:
driver-postgresql-profiling,driver-mysql-profiling,driver-sqlite-profiling— JDBC driver cores no longer depend onhexagon:profiling FormatReadOptionsextracted fromImportOptions: carries read-oriented fields only (encoding, CSV no-header, CSV null-string); lives inports-read- Runner decomposition:
DataImportRunner→ImportResumeCoordinator+DirectoryImportScanner;DataExportRunner→ExportResumeCoordinator;StreamingImporter→TableImporter+InputResolver;StreamingExporter→TableExporter SchemaComparatordecomposition:TableComparatorextracted (~320 LOC) for isolated table-level diff logic (columns, constraints, indexes, primary keys)- DDL generator decomposition: routine DDL helpers extracted per object type; dialect-specific generators compose through these helpers with per-object rewrite/capability rules
- Plan/milestone comments removed from production code; only
whycomments and invariants remain AbstractDdlGenerator.getVersion()returns0.9.1- Documentation updated:
docs/user/releasing.mdhardened (accuracy, consistency, Umlaut normalization, SHA source fix, Homebrew rollback step)
- Checkpoint/Resume for
data export: file-based exports can be interrupted and resumed via--resume <checkpoint-id>and--checkpoint-dir <path>. Table-granular resume (Phase C.1) skips already-completed tables; mid-table resume (Phase C.2) continues from the last confirmed composite marker when--since-columnis set and the table has a primary key. Single-file targets use staging with atomic rename - Checkpoint/Resume for
data import: file- and directory-based imports can be interrupted and resumed at committed-chunk boundaries. Already-completed tables are skipped; partially committed tables restart at the next open chunk.--truncateis automatically suppressed for resumed tables to protect committed rows. Directory imports validate stabletable → inputFilebindings --langas productive CLI override (de/en): strict validation against bundled product languages with Exit 2 for unsupported values; priority:--lang>D_MIGRATE_LANG>LC_ALL/LANG> configi18n.default_locale> system locale > English fallback- CheckpointStore port and file-based adapter: versioned
CheckpointManifestwith schema version, operation type, table slices, options fingerprint and resume positions; atomic temp-to-rename write strategy PipelineConfigextended withCheckpointConfig:pipeline.checkpoint.enabled,interval(row-based),max_interval(time-based, ISO-8601 duration),directory; merge priority CLI > Config > Runtime-DefaultoperationIdin progress and result types: stable UUID per run, emitted inProgressEvent.RunStarted(with Starting/Resuming label) and inExportResult/ImportResult- Exit code 3 for resume incompatibility: fingerprint mismatch (format, encoding, CSV options, filter,
--since-*, tables, output mode/path, PK signature, directory topology), operation type mismatch, or table list divergence - CI:
verify-homebrewJob in.github/workflows/release-homebrew.yml— macOS-Runner installiert nach jedem Tag-Push den veröffentlichten Tap (pt9912/homebrew-d-migrate) und verifiziertd-migrate --version+--helpend-to-end - CI: neuer Workflow
.github/workflows/verify-homebrew-formula.yml— macOS-Verifikation der repo-lokalen Formula (packaging/homebrew/d-migrate.rb) via Ephemeral-Tap bei jeder Änderung der Formula-Datei, PRs gegendevelop/mainsowie manuellemworkflow_dispatch
AbstractDdlGenerator.getVersion()returns0.9.0--langCLI flag is now productive (was rejected with exit 7 in 0.8.0)- Normative documentation (
cli-spec.md,connection-config-spec.md,roadmap.md,guide.md,design.md,architecture.md) synchronized to the implemented 0.9.0 contract; stale phase placeholders removed - Homebrew install block (
packaging/homebrew/d-migrate.rbundrelease-homebrew.ymlinstall:-Key) verwendetDir["*"]stattDir["d-migrate-*"].fetch(0): Homebrew strippt das einzelne Top-Level-Verzeichnis beim Entpacken, das alte Pattern lieferte daher immer einen leeren Treffer und einenIndexErrorbei der Installation docs/user/releasing.md§4.7:brew install --formula <path.rb>durch den modern-homebrew-konformen Ephemeral-Tap-Weg ersetzt; Alternativpfad über den veröffentlichten Tap dokumentiertdocs/user/releasing.md§4.7 / §7: ZIP-SHA wird aus dem publizierten Release-Asset (curl/gh release download) gelesen, nicht ausrelease-assets/*.sha256— die beiden Workflows bauen den ZIP unabhängig und produzieren unterschiedliche Hashesdocs/user/releasing.md§3.5 / §7 / §8: neue Workflows in der Vorbereitungs-, Veröffentlichungs- und Referenzsektion eingetragen
- I18n-Runtime and config resolution (Phase B):
ResolvedI18nSettingswithlocale,timezone,normalizationplusI18nSettingsResolverthat resolves viaD_MIGRATE_LANG>LC_ALL>LANG>i18n.default_locale> system locale > English fallback; timezone chaini18n.default_timezone>ZoneId.systemDefault()> UTC (error fallback only) - ResourceBundles and localized CLI output (Phase C): English root bundle (
messages.properties) and German bundle (messages_de.properties);MessageResolverwith parent-chain fallback;OutputFormatterandProgressRendereremit localized plain-text while JSON/YAML payloads stay language-stable English - ICU4J-based Unicode utilities (Phase D):
UnicodeNormalizer(NFC/NFD/NFKC/NFKD) as explicit utility, not silent payload mutation;GraphemeCounterwith ICUBreakIteratorfor grapheme-aware length on combining marks, emoji, ZWJ sequences and CJK - TemporalFormatPolicy (Phase E): stateless policy object in
hexagon:applicationcentralizing ISO-8601 formatters, parse helpers (parseSinceLiteral,parseOffsetDateTime,parseLocalDateTime,parseLocalDate),hasOffsetOrZoneheuristic andtoZoned(local, zone)as the single explicit-zoning API --csv-bomcontract consolidated (Phase F / D1): writes a BOM matching--encoding—EF BB BFfor UTF-8,FE FFfor UTF-16 BE,FF FEfor UTF-16 LE; no-op for non-UTF encodings (ISO-8859-1, Windows-1252, …)- Unicode-aware BOM/encoding tests across
EncodingDetectorTest,CsvChunkReaderTest,JsonChunkReaderTest,YamlChunkReaderTest,CsvChunkWriterTestwith Cyrillic, CJK and emoji payloads - TemporalFormatPolicyTest covering §4.1–§4.6 of the Phase-E contract including minute-precision read tolerance lock-in
- DE-bundle fallback test in
MessageResolverTestusing a dedicated test-resource pair (test-messages-phase-g/phasegmsg[_de].properties) to prove ResourceBundle parent-chain fallback - Phase plans
docs/planning/ImpPlan-0.8.0-A.mdthroughdocs/planning/ImpPlan-0.8.0-G.mddocumenting the milestone in full
AbstractDdlGenerator.getVersion()returns0.8.0ValueSerializerandValueDeserializertemporal branches annotated with Phase-E contract references; behavior unchanged but ISO-8601 handling documented inline (TIMESTAMPstays local,TIMESTAMP WITH TIME ZONEstays offset-bearing,ZonedDateTimeserialized offset-based —ZoneIdregion intentionally not part of the 0.8.0 contract)DataExportHelpers.parseSinceLiteraldelegates toTemporalFormatPolicy.parseSinceLiteral, removing the duplicate parser logic and ensuring CLI and format layer share the same contract--langCLI flag is declared but actively rejected in 0.8.0 with exit 7 — the resolution chain runs throughD_MIGRATE_LANG,LC_ALL/LANGandi18n.default_locale; the final CLI override contract lands in 0.9.0- Documentation aligned across
spec/cli-spec.md,docs/user/guide.md,spec/design.md,spec/architecture.md,spec/connection-config-spec.mdandspec/lastenheft-d-migrate.mdon the Phase-E and Phase-F contracts CsvChunkWriter.writeBomBytes()now carries the D1 rationale inline; behavior (UTF-8/UTF-16 BE/LE BOM + non-UTF no-op) unchanged
- Master plan
docs/planning/implementation-plan-0.8.0.mdcleaned of stale "UTF-8-only" and "CLI > ENV > Config > System > Fallback" claims that contradicted the Phase-E/F/G contracts
d-migrate data profileCLI command — profiles a database with column statistics, quality warnings, and target type compatibility as JSON/YAML report- New Gradle module
hexagon:profilingwith domain model, type system, rule engine, and outbound ports DatabaseProfile,TableProfile,ColumnProfiledomain model withLogicalType(data-oriented, separate fromNeutralType),TargetLogicalType,ProfileWarning,TargetTypeCompatibilityWarningEvaluatorwith 8 migration-relevant rules: high null ratio, empty/blank strings, high/low cardinality, duplicate values, invalid target type values, placeholder valuesSchemaIntrospectionPort,ProfilingDataPort,LogicalTypeResolverPort— profiling-specific outbound ports (not extendingDatabaseDriver)- Dialect-specific profiling adapters for PostgreSQL, MySQL, and SQLite with schema-qualified queries, full-scan type compatibility, and deterministic example values
ProfileTableServiceandProfileDatabaseServicefor profiling orchestrationDataProfileRunnerwith exit codes 0/2/4/5/7 and injectedProfilingAdapterSetlookupProfileReportWriterwith JSON (default) and YAML output, identical information content--source,--tables,--schema(PostgreSQL only),--top-n,--format,--outputflags- PostgreSQL
--schemasupport end-to-end (introspection + data queries) - Determinism contract: stable table/column order, stable topValues, no runtime-variable
generatedAt - Architecture section 3.7, CLI-spec
data profilesection, design section 3.6 updated
AbstractDdlGenerator.getVersion()returns0.7.5- Kotest upgraded from 5.9.1 to 6.1.11
- Kover upgraded from 0.9.1 to 0.9.8
- Gradle JVM heap raised from 512 MB to 4 GB
- Dockerfile:
gradle:8.12-jdk21base image (no more wrapper download)
d-migrate export flywayCLI command — generates Flyway SQL migration files (V<version>__<slug>.sql, optionalU<version>__<slug>.sqlundo)d-migrate export liquibaseCLI command — generates a versioned XML changelog with deterministicchangeSet(id from version+slug+dialect, authord-migrate, optional<rollback>block)d-migrate export djangoCLI command — generates a minimal DjangoRunSQLmigration with optionalreverse_sqld-migrate export knexCLI command — generates a Knex.js CommonJS migration with sequentialknex.raw()calls and optionalexports.down- Tool-neutral migration export contract in
hexagon:ports(MigrationBundle,MigrationIdentity,MigrationDdlPayload,MigrationRollback,ArtifactRelativePath,ToolMigrationExporter,ToolExportResult) - Adapter-free application helpers:
MigrationIdentityResolver,MigrationVersionValidator,MigrationSlugNormalizer,DdlNormalizer,ArtifactCollisionChecker ToolExportRunnerorchestrator in application layer with 12-step pipeline (schema read, validate, identity resolve, DDL generate, bundle build, exporter delegation, collision check, file write, report sidecar)- New Gradle module
adapters:driven:integrationswith fourToolMigrationExporterimplementations (side-effect-free, no tool-runtime dependencies) - Collision detection: in-run artifact duplicates, existing file collisions (recursive), and report/artifact path overlap — all checked before first write
- YAML report sidecar via
--reportwith notes, skippedObjects, rollbackNotes, rollbackSkippedObjects, exportNotes, and artifact paths - Dockerfile
integration-teststage with JDK + Python + Django + Node.js for runtime validation - Runtime validation tests: Flyway→PostgreSQL, Liquibase→PostgreSQL (with rollback), Django→SQLite (with reverse), Knex→SQLite (with rollback)
- Tool export smoke tests in
docs/user/releasing.md - Architecture section 3.6 documenting the full hexagonal export pipeline
- Extensibility section 8.4 for adding new tool exporters
AbstractDdlGenerator.getVersion()returns0.7.0test-integration-docker.shbuilds from Dockerfileintegration-teststage (JDK + Python + Django + Node.js) instead of plain JDK image.github/workflows/integration.ymlprovisions Python, Django, Node.js, and pnpm for CI- Django/Knex exporters filter comment-only statements to prevent tool runtime errors
d-migrate schema reverseCLI command — extracts the schema of a live database (PostgreSQL, MySQL, SQLite) into the neutral YAML format with structured notes and optional YAML sidecar reportSchemaReaderport interface withSchemaReadResultenvelope (schema + notes + skipped objects) andSchemaReadOptionsfor object-type filtering (views, functions, procedures, triggers)- PostgreSQL
SchemaReader: tables, columns, PKs, FKs, indices, CHECK constraints, sequences, ENUM/DOMAIN/COMPOSITE custom types, views, functions, procedures, triggers, partitioning, extension notes - MySQL
SchemaReader: tables with engine metadata, columns, PKs, FKs, indices, CHECK constraints, ENUM columns, views, functions, procedures, triggers,lower_case_table_names-aware lookups - SQLite
SchemaReader: PRAGMA-based metadata extraction,WITHOUT ROWIDdetection, CHECK constraint regex parser, views, triggers ObjectKeyCodecfor canonical routine keysname(direction:type,...)and trigger keystable::namewith percent-encodingReverseScopeCodecfor__dmigrate_reverse__:prefixed schema names with dialect/database/schema components- DB-based
schema compareoperands:file:<path>anddb:<url-or-alias>prefix disambiguation withCompareOperandParserandCompareOperandNormalizerfor reverse-marker normalization d-migrate data transferCLI command — direct DB-to-DB streaming without intermediate format, with target-authoritative preflight, FK topological sort, and per-chunk commitCustomTypeDiff,SequenceDiff,FunctionDiff,ProcedureDiff,TriggerDiffin core diff engineSchemaComparatorextended for DOMAIN, COMPOSITE, sequences, functions, procedures, triggersTableMetadatadata class withengine(MySQL) andwithoutRowid(SQLite) propertiesSchemaReadNoteandSchemaReadSeverityfor reverse-specific diagnostic notes (R300, R310, R320, R330, R400)ReverseReportWriterfor structured YAML sidecar reportsSchemaNodeParserandSchemaNodeBuilderfor format-agnostic JSON/YAML codec extractionJdbcMetadataSessionand typed metadata projections (TableRef, Column, PK, FK, Index, Constraint) in driver-common- PostgresTypeMapping, MysqlTypeMapping, SqliteTypeMapping as pure testable type mapping objects
- Release smoke paths for Reverse, Compare (file/db, db/db), and Transfer in
docs/user/releasing.md CliDataTransferTestwith flag parsing, validation, and error path coverage
SchemaDiff:enumTypes*fields renamed tocustomTypes*to support ENUM, DOMAIN, and COMPOSITE types uniformlySchemaValidator: E008 (missing primary key) downgraded from error to warningDatabaseDriverinterface:schemaReader()method added (implemented for all three dialects)MysqlJdbcUrlBuilder: removed deprecateduseUnicode/characterEncodingproperties (Connector/J 9.x), addedallowPublicKeyRetrieval=trueAbstractDdlGenerator.getVersion()returns0.6.0- Dependency upgrades: PostgreSQL JDBC 42.7.10, MySQL Connector/J 9.6.0, SQLite JDBC 3.51.3.0, Jackson 2.21.2
- PostgreSQL
listSequences:cache_sizecolumn referenced frominformation_schema.sequences(does not exist); fixed via LEFT JOIN topg_sequences - PostgreSQL
readSequences:information_schema.sequencesreturns varchar values, not numbers; fixed withtoLongOrNull()helper for mixed Number/String parsing - PostgreSQL
listForeignKeys: cartesian product on composite FKs fromconstraint_column_usage; fixed withpg_constraint+unnest(conkey, confkey) WITH ORDINALITY
- Spatial geometry type (
type: geometry) in the neutral schema model withgeometry_type(8 canonical values: geometry, point, linestring, polygon, multipoint, multilinestring, multipolygon, geometrycollection) andsrid(spatial reference system identifier) - DDL generation for spatial columns across all three dialects: PostgreSQL/PostGIS (
geometry(Point, 4326)), MySQL (native spatial types with SRID constraint), SQLite/SpatiaLite (AddGeometryColumn()/DiscardGeometryColumn()strategy) --spatial-profileCLI flag forschema generatewith profilespostgis,native,spatialite,noneand dialect-appropriate defaults- Generator options architecture:
DdlGenerationOptionsdata class with typedSpatialProfileenum,SpatialProfilePolicyfor central defaults and validation - Schema validation rules E120 (unknown geometry type) and E121 (invalid SRID); generation-time codes E052 (spatial column cannot be generated with chosen profile) and W120 (SRID transfer limitation)
- YAML codec support for
type: geometrywith lossless parsing of spatial attributes (geometry_type,srid) - Spatial Golden Master tests for all three dialects (
spatial.postgresql.sql,spatial.mysql.sql,spatial.sqlite.sql)
DdlGenerator.generate()andgenerateRollback()extended withoptions: DdlGenerationOptionsparameter (backward-compatible with default arguments)- TypeMapper implementations for all three dialects extended with geometry type handling
AbstractDdlGenerator.getVersion()returns0.5.5
d-migrate schema compareCLI command — file-based comparison of two neutral schema definitions with deterministic Plain/JSON/YAML output- Core-Diff-Engine (
SchemaComparator) with hierarchical before/after diff model (SchemaDiff,TableDiff,ColumnDiff,EnumTypeDiff,ViewDiff) - Single-column UNIQUE/FK normalization to avoid false positives between column-level and constraint-level representations
- Stable DiffView projection for CLI output — primitive-only types, no raw core model leakage
- Exit code contract for
schema compare: 0 (identical), 1 (different), 2 (CLI error), 3 (invalid schema), 7 (parse/IO error) - Validation of both schemas before comparison; warnings visible on stderr (plain) or in validation block (JSON/YAML) without changing exit code
--outputsupport forschema comparewith automatic parent directory creation and input collision detection- Line-oriented stderr progress display for
data exportanddata importwith event-based reporting (RunStarted, TableStarted, ChunkProcessed, TableFinished) ProgressEventsealed interface andProgressReporterfun interface in hexagon:ports for cross-module progress contractProgressRendererin CLI adapter with Locale.US number formatting- Kover XML-to-JSON coverage report conversion in Docker build stage (via yq)
- GitHub Actions workflow for automated Homebrew tap releases (
release-homebrew.yml) - Release packaging with Fat JAR, ZIP, TAR distributions and SHA256 checksums
cli-spec.mdsection 7 updated to reflect actual MVP progress display: line-oriented, sequential single-table, no >2s thresholdExportExecutorandImportExecutorinterfaces extended withProgressReporterparameter--quietsuppresses progress events and final summary;--no-progresssuppresses progress events and summary but keeps non-progress stderr output (e.g., export warnings)
d-migrate data importCLI command — transactional import of JSON, YAML, or CSV data into PostgreSQL, MySQL, and SQLite databasesDataWriter/TableImportSessionport interfaces for hexagonal import pipeline- PostgreSQL, MySQL, and SQLite data writers with batch INSERT, UPSERT (
--on-conflict update), and TRUNCATE support - Streaming import pipeline (chunk-based, transactional, no full-table buffering)
- Schema preflight validation on import (target schema is authoritative)
- Sequence / Identity / AUTO_INCREMENT reseeding after import for all three dialects
- Dialect-specific trigger handling during import (
--trigger-mode disable) --truncateflag for clearing target tables before import--on-conflict updateflag for idempotent UPSERT imports--trigger-modeflag for controlling trigger behavior during import- Format read path:
DataChunkReader,DataChunkReaderFactory,ImportOptionswithJsonChunkReader,YamlChunkReader,CsvChunkReaderfor streaming deserialization EncodingDetectorwith BOM sniffing for UTF-8 / UTF-16 BE / UTF-16 LEValueDeserializerfor JDBC-type-hint based conversion on importDefaultDataChunkReaderFactoryimplementationSchemaSynccontract for pre-import schema matching and validationNamedConnectionResolverextended withresolveSource/resolveTarget- Incremental export flags
--since-columnand--sinceond-migrate data export(LF-013), including typed parameter binding and composition with--filter DataFilter.ParameterizedClauseandSelectQuery(sql, params)for safely bound WHERE fragments- Testcontainers E2E import tests for PostgreSQL and MySQL
- Driver-level truncate integration tests
- E2E CLI data import tests: JSON/YAML/CSV round-trips,
--truncate,--on-conflict update,--trigger-mode disable - Round-trip tests (export → import → comparison) and incremental round-trip tests (initial export → delta export → UPSERT import → comparison)
- Golden-Master round-trip and null-row property tests
- Hexagonal architecture restructuring: project reorganized from flat modules into
hexagon/core,hexagon/ports,hexagon/application,adapters/driven/*,adapters/driving/cli— all module paths, imports, and CI workflows updated accordingly DatabaseDriverRegistryreplaces legacy per-dialect registriesSchemaGenerateRunnerandDataExportRunnerextracted from CLI command classes intohexagon:applicationDataExportCommandextracted into its own fileMain.ktbootstrap split for testability; Help and edge-gap tests addedAbstractDdlGenerator.getVersion()returns0.4.0- CLI Kover coverage gate raised from 60% to 90%
d-migrate data exportnow rejects literal?inside--filterwhen combined with--since, returning exit code 2 in the CLI preflight
ImportOptions.encodingdefaults to auto-detect (null) instead of hard-coded UTF-8EncodingDetector.UnsupportedEncodingExceptionrenamed toUnsupportedFileEncodingExceptionto avoid JDK collisionValueDeserializerdistinguishesNUMERIC/DECIMALvia precision, scale, and token shape instead of forcing every value ontoBigDecimal- Large-fixture cache invalidation uses generator source hash instead of manual fingerprint
- Enum type check no longer rejects custom PG enums without
refType Types.OTHERcross-contamination in schema type compatibility check- Fragile
message.startsWithcheck replaced with typed exception - Schema-qualified table names no longer break
--schemavalidation - Directory import now includes
.ymlfiles for YAML format - Schema target validation decoupled from application layer for cleaner error handling
autoCommitguard before PostgreSQLTRUNCATEinopenTable- Writer wiring and cleanup-failure reporting hardened
d-migrate data exportCLI command — streaming export of database tables to JSON, YAML, or CSV- 2 new Gradle modules:
d-migrate-driver-api(connection layer + data ports),d-migrate-streaming(pull-based StreamingExporter) - Connection layer:
ConnectionConfig,ConnectionUrlParser,JdbcUrlBuilderper dialect, HikariCP-backedHikariConnectionPoolFactory,LogScrubber(password-safe URL masking) - Hexagonal data ports in
d-migrate-driver-api:DataReader,TableLister,ChunkSequence(single-use, AutoCloseable, transaction lifecycle inclose()) - JDBC
DataReader+TableListeradapters for PostgreSQL (cursor streaming viasetFetchSize+autoCommit=false), MySQL (useCursorFetch=true), and SQLite (lazy file read) - Driver bootstrap objects (
PostgresDriver,MysqlDriver,SqliteDriver) for explicit registry registration inMain.kt - Pull-based
StreamingExporter(chunk-based, no full-table buffering) withExportOutputresolution (Stdout/SingleFile/FilePerTable) - Format writers in
d-migrate-formats/data/:JsonChunkWriter(DSL-JSON),YamlChunkWriter(SnakeYAML Engine),CsvChunkWriter(uniVocity-parsers) — chosen over Jackson for streaming throughput ValueSerializermapping table (Plan §6.4.1) covering 18+ JDBC types (Numeric, BigInteger/BigDecimal, Date/Time/UUID, BLOB/CLOB,java.sql.Arrayas recursiveSerializedValue.Sequence)NamedConnectionResolver(Plan §6.14): minimal.d-migrate.yamlloader with CLI > ENV > default-path priority,${ENV_VAR}substitution,$${VAR}escape, literal substitution (no auto URL-encoding)- CLI flags for
data export:--source(URL or named connection),--format(required),--output,-o,--tables,--filter,--encoding,--chunk-size,--split-files,--csv-delimiter,--csv-bom,--csv-no-header,--null-string --tablesstrict identifier validation ([A-Za-z_][A-Za-z0-9_]*optionally schema-qualified) — rejects whitespace, hyphens, SQL injection attempts (Plan §6.7)- §6.17 empty-table contract: every reader emits at least one chunk with
columnseven whenrows.isEmpty(); pre-format outputs are[](JSON),[](YAML), header line (CSV), or empty file (--csv-no-header) - Exit-code matrix for
data export:0success,2CLI/usage error,4connection error,5export error,7config error - Testcontainers-based integration tests for PostgreSQL 16 and MySQL 8.0 — both at the driver level and end-to-end via the CLI
- New
.github/workflows/integration.ymlrunning./gradlew test koverVerify -PintegrationTests; defaultbuild.ymlexcludes theintegrationKotest tag and stays under the 5-min CI budget scripts/test-integration-docker.shfor running integration tests locally in a disposable Docker containerdocs/planning/implementation-plan-0.3.0.md(1400+ lines),docs/user/releasing.mddata exportsection inspec/cli-spec.md§6.2 covering all flags, output resolution, exit codes, and 6 example invocations- 600+ tests across all modules (was 374 in 0.2.0)
- Kover coverage gates: ≥ 90% for all production modules, ≥ 60% for
d-migrate-cli(per Plan §11)
AbstractDdlGenerator.getVersion()now returns0.3.0- Bumped Testcontainers from 1.20.4 to 2.0.4 — module artifacts renamed (
org.testcontainers:postgresql→org.testcontainers:testcontainers-postgresql) and classes relocated (org.testcontainers.containers.PostgreSQLContainer→org.testcontainers.postgresql.PostgreSQLContainer); resolves Docker Engine ≥ 25.x API negotiation failures d-migrate-cliKover gate raised from 50% to 60% (Plan §11) now that CLI integration tests are in place- LF-013 (incremental export/import) moved from milestone 0.9.0 forward to 0.4.0 in
docs/planning/roadmap.md— paired withdata import, no longer requiresSchemaReader - Roadmap milestone 0.9.0 split into 0.9.0 (Beta core: Checkpoint/Resume +
--lang) and 0.9.5 (Beta docs and pilot QA) — different cadences for code vs. documentation work cli-spec.md§6.2data exportupdated to reflect the actual 0.3.0 surface (named connections, all CSV flags, exit codes 2/7, 6 example invocations)--quietnow also suppressesValueSerializerwarnings on stderr (percli-spec.md§1.3 — "Nur Fehler"), not just theProgressSummary--no-progressis now honored bydata export(previously only--quietsuppressed theProgressSummary)
- SQLite
:memory:URLs are now preserved as in-memory databases instead of being interpreted as filesystem paths - MySQL
characterEncodingparameter: use Java charset nameUTF-8instead of MySQL identifierutf8mb4, which Connector/J rejects withUnsupportedEncodingException java.sql.Arrayis now serialized recursively as a JSON array / YAML sequence per Plan §6.4.1; CSV producesnullplus a single deduplicatedW201warning per(table, column)tupleBigIntegerandBigDecimalare now mapped to separateSerializedValuevariants —BigIntegerrenders as a YAML number (no precision loss),BigDecimalas a quoted string for both JSON and YAML--csv-delimitervalidation now produces a clean exit code 2 with a stderr message instead of a rawIllegalArgumentExceptionstack traceStreamingExporter.end()is no longer called whenbegin()failed, preventing format writers from emitting unmatched closing tokens (e.g. JSON]without[)JsonChunkWriterhonorsExportOptions.encodingvia aCharsetReencodingOutputStreamwrapper when the target encoding is not UTF-8CsvChunkWriterwarnings now carry the real column name instead ofcol0/col1Stdoutexports no longer closeSystem.out— aNonClosingOutputStreamwrapper guards against the writer'sclose()propagating to the process-wide stream
- DDL generation for PostgreSQL, MySQL, and SQLite via
d-migrate schema generate --target <dialect> - 4 new Gradle modules:
d-migrate-driver-api,d-migrate-driver-postgresql,d-migrate-driver-mysql,d-migrate-driver-sqlite TypeMapperinterface with full 18-type mapping per dialect (SERIAL, AUTO_INCREMENT, AUTOINCREMENT, JSONB, TINYINT(1), etc.)AbstractDdlGeneratorbase class with topological sort (Kahn's algorithm) and rollback generationDdlStatementmodel pairing SQL with inlineTransformationNotesViewQueryTransformerwith 17 regex-based SQL function transformations between dialectsTransformationReportWritergenerating YAML sidecar reports (<output>.report.yaml)--output,--target,--generate-rollback,--reportflags onschema generate--output-format jsonsupport forschema generatewith ddl, notes, skipped_objects- Circular FK handling: PostgreSQL/MySQL via ALTER TABLE ADD CONSTRAINT, SQLite via E019 error
- MySQL DELIMITER wrapping for triggers, functions, procedures
- 12 golden master test files (4 schemas x 3 dialects)
- 374 tests across all modules (was 83 in 0.1.0)
- Kover coverage enforcement >= 90% for all driver modules
- SQL injection prevention: identifier quoting escapes embedded quote characters
- Enum values with single quotes are properly escaped (
'')
- Gradle multi-module project structure with three modules:
d-migrate-core,d-migrate-formats,d-migrate-cli NeutralTypesealed class with 18 database-agnostic types (identifier, text, char, integer, smallint, biginteger, float, decimal, boolean, datetime, date, time, uuid, json, xml, binary, email, enum, array)SchemaDefinitionmodel for representing database schemas in a neutral formatSchemaValidatorwith 18 validation error codes (E001 -- E018) covering structural and semantic checksYamlSchemaCodecfor parsing and writing schema definitions in YAML format- CLI command
schema validatefor validating schema files from the command line - CLI output format support: plain text, JSON, and YAML (
--output-format) - 83 unit and integration tests across all modules
- Kover code coverage enforcement: >= 90% for core and formats modules, >= 50% for CLI module
- Kotlin 2.1.20 with JVM 21 target
- Gradle 8.12 build configuration
- MIT License