Milestone: 0.9.6 — Beta: MCP-Server Phase: E0.2 → E0.7 (final-klassifiziert) Status: Stand 2026-05-05 nach E0.7.6;
go = ~74,blocked = 0,tentative-go = 0,go_followup = 0. Driver-Calls sind viaTimeoutDecoratedConnection+ driver-spezifischerconnectionInitSql+ Bench-Tests alsatomic-nicht-cancelbarmit<= 30sbelegt. Final.Update-Historie pro AP siehe §8 Änderungs-Protokoll.
Hauptplan:
./ImpPlan-0.9.6-E0.md§7.2 Pflichtschema:./ImpPlan-0.9.6-E0.md§7.2 (Tabelle direkt nach AP E0.2)
Diese Matrix listet jede extern sichtbare Side-Effect-Grenze in den fuer Phase E0 relevanten Runnern auf und ordnet ihr den geplanten Cancel-Checkpoint, das geplante Cleanup, die heutige Atomaritaets-Klassifikation und den geplanten Gate-Status zu.
Sie ist der Snapshot vor AP E0.3–E0.5. Spalten, die erst durch
Token-Propagation, Checkpoint-Platzierung oder Tests entstehen
(test_coverage, measurement_evidence, cancel_budget_ms), tragen heute
missing und werden in den jeweiligen AP-Commits nachgezogen. Der heutige
gate_result ist gemaess §7.2-Regel:
- jede Zeile mit externem Side Effect oder potentiell langem monolithischem
Call und
test_coverage = missing,atomicity = unknownoder fehlendemtimeout_or_boundist heute automatischblocked(Snapshot-Status). Ohne AP E0.4/E0.5-Tests gibt es keinen Pfad zugo. - atomar-nicht-cancelbare Zeilen ohne
cancel_budget_ms <= 30000odermeasurement_evidencesind ebenfallsblocked.
Phase E0.6 entscheidet anhand der dann gefuellten Matrix das Gate fuer Phase E.
In Scope (Hauptplan §6, AP E0.2):
SchemaReverseRunner(Reverse-Pipeline)DataProfileRunner+ProfileDatabaseService+ProfileTableServiceDataImportRunner+ImportStreamingInvoker+StreamingImporter+TableImporter+ImportCheckpointManagerDataTransferRunner+TransferExecutor- Streaming-/Reader-/Writer-Pfade unter
adapters/driven/streamingundadapters/driven/driver-*, soweit sie von obigen Runnern genutzt werden
Bewusst nicht in Scope fuer E0:
DataExportRunner,StreamingExporter,TableExporter,ExportCheckpointManager— Phase E fuehrt keindata_export_start-Start-Tool ein (siehe Hauptplan §3.1). Der Export-Pfad bleibt CLI-only und wird in einer spaeteren Iteration nachgezogen, falls ein MCP-Tool ihn als Langlaeufer adressiert.SchemaCompareRunner—schema_compareist read-only und bleibt inline-Tool ohne Worker-Cancel-Bedarf;schema_compare_start(Phase E) laeuft auf dem gleichen Runner-Vertrag wie Reverse und ist hier ueber die Reverse-Spalten abgedeckt.tools_export_*,data_export*— wie oben, kein Phase-E-Start-Tool.
Damit die Tabellen lesbar bleiben, werden Spaltennamen in den Per-Pipeline- Tabellen abgekuerzt. Zuordnung:
| Kurz | Pflichtschema-Spalte |
|---|---|
op |
operation |
kind |
operation_type (loop, side_effect, monolithic_call, cleanup) |
side |
side_effect |
checkpoint |
checkpoint_before |
reaction |
cancel_reaction |
atom |
atomicity (atomic, multi_step, unknown) |
bound |
timeout_or_bound |
budget |
cancel_budget_ms |
evid |
measurement_evidence |
clean |
cleanup_contract |
tests |
test_coverage |
gate |
gate_result |
followup |
followup |
runner und pipeline sind je Sektion implizit (Sektionsueberschrift bzw.
erste Zeile).
Wenn kind = side_effect oder monolithic_call und tests = missing,
ist gate = blocked automatisch. Heute betrifft das alle externen
Side-Effect-Zeilen — die blocked-Markierung ist erwartet und wird durch
AP E0.4 (Reverse/Profile) und AP E0.5 (Import/Transfer/Streaming) abgebaut.
atomic heisst hier nicht ACID-Datenbank-atomar, sondern: der Aufruf
laeuft entweder vollstaendig durch oder wirft, und hinterlaesst keine offenen
Ressourcen, die ueber die Aufrufgrenze hinaus weiter Side Effects ausloesen.
Konkret muessen alle vier Schwellen aus Hauptplan §4.1 erfuellt sein, sonst
ist die Zeile multi_step oder unknown:
- kein interner Retry-/Reconnect-Loop ohne hartes Timeout
- maximale erwartete Laufzeit und Timeout liegen innerhalb des
E0-Cancel-Reaktionsbudgets (
<= 30s) - nach Timeout oder Fehler startet der Runner keinen weiteren Side Effect
- der Call haelt keine offene Transaktion, Session, Sperre oder temporaere Ressource ueber das Timeout hinaus
Driver-Methoden, die heute keine dokumentierte Laufzeit- oder Timeout-Garantie
tragen (schemaReader().read(), dataReader.streamTable(),
writer.openTable(), session.finishTable(), alle adapters.data.*),
stehen daher als unknown in der Matrix. AP E0.5 muss pro Pfad entscheiden,
ob ein Driver-Vertrag erweitert oder der Pfad blockierend bleibt.
Codebasis: hexagon/application/src/main/kotlin/dev/dmigrate/cli/commands/SchemaReverseRunner.kt.
| op | kind | side | checkpoint | reaction | atom | bound | budget | evid | clean | tests | gate | followup |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
poolFactory(ctx.config).use { p -> ... } |
side_effect | session | Runner-Token-Check unmittelbar vor readSchema() |
OperationCancelledException → Exit 130, kein Pool-Borrow |
unknown | not_applicable | not_applicable | SchemaReverseRunnerCancelCheckpointTest "cancel before introspection" |
close (use {}) |
SchemaReverseRunnerCancelCheckpointTest |
go | — |
reader.read(p, options) |
monolithic_call | source-read | direkt vor Driver-Aufruf | OperationCancelledException vor Aufruf, danach kein Schema-Publish |
unknown | missing (Driver-API hat heute keine Cancel-/Timeout-Grenze) | missing | missing | not_needed (read liefert Wert) | missing | blocked | E0.5-Klassifizierung pro Driver: atomic mit Statement-Timeout oder blocked |
request.output.parent?.mkdirs() |
side_effect | artefact | Runner-Token-Check unmittelbar vor writeSchemaFile() |
OperationCancelledException → Exit 130, kein Verzeichnis erzeugen |
atomic | not_applicable | not_applicable | SchemaReverseRunnerCancelCheckpointTest "cancel between introspection and schema publish" |
not_needed | SchemaReverseRunnerCancelCheckpointTest |
go | — |
schemaWriter(output, schema, format) |
side_effect | artefact | Runner-Token-Check unmittelbar vor writeSchemaFile() |
OperationCancelledException → Exit 130, kein Schema-Artefakt schreiben |
multi_step (write+flush) | not_applicable | not_applicable | SchemaReverseRunnerCancelCheckpointTest "cancel between introspection and schema publish" |
bei Cancel mid-write (E0.5): Files.deleteIfExists(output) als Followup |
SchemaReverseRunnerCancelCheckpointTest |
go_followup | E0.5: in-flight-write Cleanup wenn Driver-Vertrag erweitert wird |
request.reportPath.parent?.mkdirs() |
side_effect | artefact | Runner-Token-Check unmittelbar vor writeReportFile() |
OperationCancelledException → Exit 130, kein Verzeichnis erzeugen |
atomic | not_applicable | not_applicable | SchemaReverseRunnerCancelCheckpointTest "cancel between schema publish and report publish" |
not_needed | SchemaReverseRunnerCancelCheckpointTest |
go | — |
reportWriter(reportPath, reportInput) |
side_effect | artefact | Runner-Token-Check unmittelbar vor writeReportFile() |
OperationCancelledException → Exit 130, kein Report schreiben |
multi_step | not_applicable | not_applicable | SchemaReverseRunnerCancelCheckpointTest "cancel between schema publish and report publish" |
bei Cancel mid-write (E0.5): Pfad loeschen | SchemaReverseRunnerCancelCheckpointTest |
go_followup | E0.5: in-flight-write Cleanup wenn Driver-Vertrag erweitert wird |
Reverse-Anmerkungen:
reader.read(...)ist die kritische Stelle. Wenn der Driver-Vertrag inhexagon:ports-readkeine Cancel-/Timeout-Grenze garantiert und der Driver intern Retry-Logik hat, ist die Zeile in E0.6blocked. AP E0.4 muss pro Driver entweder ein Statement-Timeout setzen, das innerhalb<= 30sliegt, oder den Pfad gate-blockieren.- Notes-/Skipped-Loops (
for note in result.notes,for skip in result.skippedObjects) sind reine In-Memory-Iterationen ohne externe Side Effects und stehen nicht in der Matrix.
Codebasis: DataProfileRunner.kt, ProfileDatabaseService.kt,
ProfileTableService.kt (hexagon/profiling).
| op | kind | side | checkpoint | reaction | atom | bound | budget | evid | clean | tests | gate | followup |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
poolFactory(url, dialect) |
side_effect | session | (vor Pool-Allokation kein Token-Check; nachfolgender service.profile-Entry-Check fängt Cancel ab und Cleanup über pool.close() in finally) |
Pool wird geöffnet, danach kein Profiling-Side-Effect; pool.close() in finally schließt sauber |
unknown | not_applicable | not_applicable | DataProfileRunnerCancelCheckpointTest belegt Pfad indirekt (kein Profiling-/Report-Side-Effect nach Cancel) |
pool.close() |
DataProfileRunnerCancelCheckpointTest |
go_followup | E0.5: optionaler Pre-Pool-Checkpoint, sobald Driver-Pool-Open eigene Cancel-Grenze trägt |
introspection.listTables(pool, schema) |
monolithic_call | source-read | cancellationToken.throwIfCancellationRequested() am Entry von ProfileDatabaseService.profile() |
OperationCancelledException → Runner mappt auf 130, keine listTables-Query |
unknown | missing | missing | ProfileServiceCancelCheckpointTest "cancel before listTables halts before any introspection call" |
not_needed | ProfileServiceCancelCheckpointTest |
go_followup | E0.5: in-flight-listTables-Cancel benötigt Driver-Statement-Cancel |
for table in targetTables |
loop | none | cancellationToken.throwIfCancellationRequested() als erstes Statement in targetTables.map { ... } |
nach Cancel keine weitere Tabelle | atomic | not_applicable | not_applicable | ProfileServiceCancelCheckpointTest "cancel between table iterations starts no further table profiling" |
not_needed | ProfileServiceCancelCheckpointTest |
go | — |
tableService.profile(pool, table, ...) |
side_effect (Komposit) | source-read | composed (siehe Profile-Table-Zeilen darunter) | composed | multi_step | not_applicable | not_applicable | ProfileServiceCancelCheckpointTest (DB-Service + Table-Service zusammen) |
not_needed | ProfileServiceCancelCheckpointTest |
go | — |
introspection.listColumns(pool, table, schema) |
monolithic_call | source-read | cancellationToken.throwIfCancellationRequested() am Entry von ProfileTableService.profile() |
OperationCancelledException, keine listColumns-Query |
unknown | missing | missing | ProfileServiceCancelCheckpointTest "cancel before listColumns halts before introspection" |
not_needed | ProfileServiceCancelCheckpointTest |
go_followup | E0.5: in-flight-listColumns-Cancel benötigt Driver-Statement-Cancel |
data.rowCount(pool, table, schema) |
monolithic_call | source-read | cancellationToken.throwIfCancellationRequested() zwischen listColumns und rowCount in ProfileTableService |
OperationCancelledException, keine rowCount-Query |
unknown | missing | missing | ProfileServiceCancelCheckpointTest "cancel between listColumns and rowCount halts before rowCount" |
not_needed | ProfileServiceCancelCheckpointTest |
go_followup | E0.5: in-flight-rowCount-Cancel benötigt Driver-Statement-Cancel |
for col in columns |
loop | none | cancellationToken.throwIfCancellationRequested() als erstes Statement in columns.map { col -> ... } |
nach Cancel keine weitere Spalte | atomic | not_applicable | not_applicable | ProfileServiceCancelCheckpointTest "cancel between column iterations starts no further column profiling" |
not_needed | ProfileServiceCancelCheckpointTest |
go | — |
data.columnMetrics(pool, table, column) |
monolithic_call | source-read | cancellationToken.throwIfCancellationRequested() als erstes Statement in profileColumn(...) |
OperationCancelledException, keine columnMetrics-Query |
unknown | missing | missing | ProfileServiceCancelCheckpointTest (über column-iteration-Test indirekt; Cancel nach Spalte 1 verhindert Spalte-2 columnMetrics) |
not_needed | ProfileServiceCancelCheckpointTest |
go_followup | E0.5: in-flight-Cancel benötigt Driver-Statement-Cancel |
data.topValues(pool, table, column, topN) |
monolithic_call | source-read | cancellationToken.throwIfCancellationRequested() zwischen columnMetrics und topValues |
OperationCancelledException, keine topValues-Query |
unknown | missing | missing | ProfileServiceCancelCheckpointTest "cancel between columnMetrics and topValues starts no topValues query" |
not_needed | ProfileServiceCancelCheckpointTest |
go_followup | E0.5 |
data.numericStats(...) (optional) |
monolithic_call | source-read | cancellationToken.throwIfCancellationRequested() direkt vor optionalem Aufruf, NICHT innerhalb optionalProfilingValue { ... } (sonst würde Cancel als ProfilingQueryError verschleiert) |
OperationCancelledException, keine numericStats-Query |
unknown | missing | missing | ProfileServiceCancelCheckpointTest indirekt (Cancel vor Spalte-2 deckt alle Spalte-2-Calls ab) |
not_needed | ProfileServiceCancelCheckpointTest |
go_followup | E0.5 |
data.temporalStats(...) (optional) |
monolithic_call | source-read | cancellationToken.throwIfCancellationRequested() direkt vor optionalem Aufruf |
OperationCancelledException, keine temporalStats-Query |
unknown | missing | missing | ProfileServiceCancelCheckpointTest indirekt |
not_needed | ProfileServiceCancelCheckpointTest |
go_followup | E0.5 |
data.targetTypeCompatibility(...) (optional) |
monolithic_call | source-read | cancellationToken.throwIfCancellationRequested() direkt vor optionalem Aufruf |
OperationCancelledException |
unknown | missing | missing | ProfileServiceCancelCheckpointTest indirekt |
not_needed | ProfileServiceCancelCheckpointTest |
go_followup | E0.5 |
reportWriter(profile, format, output) |
side_effect | artefact | cancellationToken.throwIfCancellationRequested() in DataProfileRunner.execute() zwischen service.profile() und reportWriter(...) |
OperationCancelledException → Exit 130, kein Report |
multi_step | not_applicable | not_applicable | DataProfileRunnerCancelCheckpointTest "cancel between profiling and report writer returns 130 and writes no report" |
bei Cancel mid-write (E0.5): Pfad loeschen | DataProfileRunnerCancelCheckpointTest |
go_followup | E0.5: in-flight-write Cleanup |
pool.close() (finally) |
cleanup | session | wird nach Cancel UNVERAENDERT ausgefuehrt | nicht abbrechen | atomic | not_applicable | not_applicable | DataProfileRunnerCancelCheckpointTest (alle Cancel-Pfade nutzen finally { pool.close() } ohne Override) |
close |
DataProfileRunnerCancelCheckpointTest |
go | — |
Profile-Anmerkungen:
- Die
data.*- undintrospection.*-Calls sind Driver-Calls ueber JDBC. AP E0.4 muss pro Driver klaeren, ob ein per-Statement-queryTimeoutreicht oder ein Statement-Cancel-Pfad noetig ist. Ohne diesen Nachweis bleiben die Zeilenblocked. - Optionale Metric-Calls (
numericStats,temporalStats,targetTypeCompatibility) sind nur aktiv, wenn die zugehoerige Option gesetzt ist; sie werden trotzdem als eigene Zeilen gefuehrt, weil sie separat cancelbar sein muessen.
Codebasis: DataImportRunner.kt, ImportExecutionPlanner.kt,
ImportPreflightResolver.kt, ImportStreamingInvoker.kt,
StreamingImporter.kt, TableImporter.kt, ImportCheckpointManager.kt,
ImportCompletionSupport.kt, adapters/driven/streaming.
| op | kind | side | checkpoint | reaction | atom | bound | budget | evid | clean | tests | gate | followup |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
preflightResolver.resolve(request) |
side_effect | source-read (Schema-Read) | Runner-Entry vor Aufruf | OperationCancelledException, kein Pool |
multi_step | missing | missing | missing | not_needed | missing | blocked | E0.5-Token + Klassifikation |
poolFactory(connectionConfig) |
side_effect | session | vor Allokation | OperationCancelledException, kein Pool |
unknown | not_applicable | not_applicable | missing | pool.close() |
missing | blocked | E0.5-Token |
executionPlanner.prepare(...) |
side_effect (Komposit) | mixed | composed | composed | multi_step | not_applicable | not_applicable | not_applicable | composed | missing | blocked | E0.5 |
preflightValidator.resolveInputContext(...) (Directory-Scan) |
side_effect | source-read (Filesystem) | vor Aufruf | OperationCancelledException |
multi_step | not_applicable (lokales FS) | not_applicable | not_applicable | not_needed | missing | blocked | E0.5 |
inputResolver.resolve(input, format) (in StreamingImporter) |
side_effect | source-read (Filesystem) | vor Aufruf | OperationCancelledException |
multi_step | not_applicable | not_applicable | not_applicable | not_needed | missing | blocked | E0.5 |
| op | kind | side | checkpoint | reaction | atom | bound | budget | evid | clean | tests | gate | followup |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
checkpointManager.resolveCheckpointContext(request) |
side_effect | source-read (Filesystem) | vor Aufruf | OperationCancelledException, kein Manifest schreiben |
multi_step | not_applicable | not_applicable | not_applicable | not_needed | missing | blocked | E0.5 |
checkpointManager.resolveResumeContext(...) -> store.load(opId) |
side_effect | source-read (Filesystem) | vor Aufruf | OperationCancelledException, kein Resume aufsetzen |
multi_step | not_applicable | not_applicable | not_applicable | not_needed | missing | blocked | E0.5 |
checkpointManager.writeInitialManifest(...) |
side_effect | checkpoint | vor Aufruf | OperationCancelledException, kein Initial-Manifest schreiben |
multi_step | not_applicable | not_applicable | not_applicable | bei Cancel: Manifest-Pfad ist eindeutig pro op-id; AP E0.5 prueft, ob Cleanup noetig ist | missing | blocked | E0.5 |
checkpointManager.buildCallbacks(...) |
cleanup | none | nicht relevant (reine Closure-Konstruktion) | n/a | atomic | not_applicable | not_applicable | not_applicable | not_needed | missing | tentative-go | — |
| op | kind | side | checkpoint | reaction | atom | bound | budget | evid | clean | tests | gate | followup |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
progressReporter.report(RunStarted) |
side_effect | progress-callback | vor Aufruf, sofern bereits cancel angekommen ist | nach Cancel kein Progress-Event | atomic | not_applicable | not_applicable | not_applicable | not_needed | missing | blocked | E0.5-Test "kein Progress nach Cancel" |
for tableInput in discoveredInputs |
loop | none | Loop-Head | startet keine weitere Tabelle | atomic | not_applicable | not_applicable | not_applicable | not_needed | missing | blocked | E0.5-Test "Cancel zwischen Tabellen" |
tableImporter.import(TableImportParams) |
side_effect (Komposit) | mixed | innerhalb (siehe naechste Zeilen) | composed | multi_step | not_applicable | not_applicable | not_applicable | composed | missing | blocked | E0.5 |
readerFactory.create(format, input.openInput(), ...) |
side_effect | source-read (File-Open) | vor Aufruf | OperationCancelledException, kein Reader allokieren |
atomic | not_applicable | not_applicable | not_applicable | reader.close() in closeAndCollect |
missing | blocked | E0.5 |
writer.openTable(pool, tableName, options) |
monolithic_call | session (Writer-Session-Open) | vor Aufruf | OperationCancelledException, keine Session |
unknown | missing | missing | missing | session.close() falls allokiert; AP E0.5 prueft, ob abort-Pfad noetig |
missing | blocked | E0.5 — Driver-Vertrag pruefen, evtl. abort-API ergaenzen |
progressReporter.report(ImportTableStarted) |
side_effect | progress-callback | vor Aufruf | nach Cancel kein Event | atomic | not_applicable | not_applicable | not_applicable | not_needed | missing | blocked | E0.5 |
skipCommittedChunks(reader, offset) (Resume-Skip-Loop) |
loop | source-read | Loop-Head vor jedem nextChunk() |
nach Cancel kein weiterer Read und kein neuer Fortschritts-Checkpoint | multi_step | not_applicable | not_applicable | not_applicable | not_needed | missing | blocked | E0.5 — Hauptplan §4.6 explizit |
reader.nextChunk() (per Chunk) |
monolithic_call | source-read | vor Aufruf | OperationCancelledException, kein weiterer Chunk-Read |
unknown | missing | missing | missing | not_needed | missing | blocked | E0.5 — Driver-/Format-Reader-Vertrag schaerfen |
session.write(chunk) |
side_effect | db-write | vor Aufruf | OperationCancelledException, kein neuer Write; bereits begonnener Write darf fertiglaufen |
multi_step | not_applicable (in-flight write) | not_applicable | not_applicable | siehe closeAndCollect (Session-Close) |
missing | blocked | E0.5 |
session.commitChunk() |
side_effect | db-write (commit) | vor Aufruf | OperationCancelledException, kein Commit; bereits laufender Commit fertiglaufen lassen |
multi_step | not_applicable | not_applicable | not_applicable | siehe Session-Close | missing | blocked | E0.5 |
onChunkCommitted(commit) Callback (-> saveManifest) |
side_effect | progress-callback + checkpoint | vor Aufruf | nach Cancel kein Callback und kein Manifest-Update | multi_step | not_applicable | not_applicable | not_applicable | not_needed | missing | blocked | E0.5 — Hauptplan §4.6: kein Fortschritts-Checkpoint nach Cancel |
progressReporter.report(ImportChunkCommitted) |
side_effect | progress-callback | vor Aufruf | nach Cancel kein Event | atomic | not_applicable | not_applicable | not_applicable | not_needed | missing | blocked | E0.5 |
session.finishTable() |
monolithic_call | db-write (finalize) | vor Aufruf | OperationCancelledException, kein Finish |
unknown | missing | missing | missing | bei Cancel: session.close() statt finish |
missing | blocked | E0.5 — Driver-Writer-Vertrag muss abort exposen oder Pfad ist blocked |
onTableCompleted(summary) Callback (-> saveManifest) |
side_effect | progress-callback + checkpoint | vor Aufruf | nach Cancel kein Erfolgssignal und kein Manifest-Update | multi_step | not_applicable | not_applicable | not_applicable | not_needed | missing | blocked | E0.5 |
closeAndCollect(reader) / closeAndCollect(session) |
cleanup | session | nach Cancel UNVERAENDERT ausgefuehrt | nicht abbrechen; Cleanup-Fehler suppressed | atomic | not_applicable | not_applicable | not_applicable | close |
missing | blocked | E0.5-Test "Session/Reader nach Cancel geschlossen" |
| op | kind | side | checkpoint | reaction | atom | bound | budget | evid | clean | tests | gate | followup |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
store.complete(operationId) (in ImportCompletionSupport) |
side_effect | checkpoint (final-mark) | vor Aufruf | nach Cancel kein complete-Mark; Manifest bleibt vorhanden, damit Resume moeglich ist |
atomic | not_applicable | not_applicable | not_applicable | not_needed | missing | blocked | E0.5 — Hauptplan §4.6: kein erfolgreiches Abschlussignal nach Cancel |
Codebasis: DataTransferRunner.kt, TransferExecutor.kt,
TransferConnectionResolver.kt, TransferPreflightPlanner.kt,
adapters/driven/driver-* (Reader/Writer).
| op | kind | side | checkpoint | reaction | atom | bound | budget | evid | clean | tests | gate | followup |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
connectionResolver.resolve(request) (Source + Target Pool) |
side_effect | session (2x) | Runner-Entry vor Aufruf | OperationCancelledException, keine Pools |
unknown | not_applicable | not_applicable | missing | beide Pools schliessen | missing | blocked | E0.5 |
srcDrv.schemaReader().read(srcPool, readOpts) |
monolithic_call | source-read (Schema) | vor Aufruf | OperationCancelledException, kein Schema-Read |
unknown | missing | missing | missing | not_needed | missing | blocked | E0.5 — wie Reverse-reader.read, gleiche Driver-Frage |
tgtDrv.schemaReader().read(tgtPool, readOpts) |
monolithic_call | source-read (Schema) | vor Aufruf | OperationCancelledException |
unknown | missing | missing | missing | not_needed | missing | blocked | E0.5 |
preflightPlanner.planTables(request, srcSchema, tgtSchema) |
side_effect | none (in-memory) | vor Aufruf | nicht relevant — In-Memory-Validierung | atomic | not_applicable | not_applicable | not_applicable | not_needed | missing | tentative-go (in-memory only) | — |
| op | kind | side | checkpoint | reaction | atom | bound | budget | evid | clean | tests | gate | followup |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
for table in context.tables (TransferExecutor) |
loop | none | Loop-Head | startet keine weitere Tabelle | atomic | not_applicable | not_applicable | not_applicable | not_needed | missing | blocked | E0.5-Test |
context.reader.streamTable(srcPool, table, ...) |
monolithic_call | source-read (Stream-Open) | vor Aufruf | OperationCancelledException, kein Stream |
unknown | missing | missing | missing | not_needed | missing | blocked | E0.5 — Reader-Port-Vertrag schaerfen |
context.writer.openTable(tgtPool, ...) |
monolithic_call | session (Writer-Session-Open) | vor Aufruf | OperationCancelledException |
unknown | missing | missing | missing | session.close() |
missing | blocked | E0.5 — wie Import |
for chunk in sequence |
loop | none | Loop-Head | startet keinen weiteren Chunk | atomic | not_applicable | not_applicable | not_applicable | not_needed | missing | blocked | E0.5-Test |
chunk.rows.map { row -> ... } (Row-Reordering) |
loop | none (in-memory) | nicht relevant | nicht relevant | atomic | not_applicable | not_applicable | not_applicable | not_needed | missing | tentative-go (rein in-memory) | — |
session.write(normalized) |
side_effect | db-write | vor Aufruf | OperationCancelledException, kein Write |
multi_step | not_applicable | not_applicable | not_applicable | siehe Session-Close | missing | blocked | E0.5 |
session.commitChunk() |
side_effect | db-write (commit) | vor Aufruf | OperationCancelledException, kein Commit |
multi_step | not_applicable | not_applicable | not_applicable | siehe Session-Close | missing | blocked | E0.5 |
session.finishTable() |
monolithic_call | db-write (finalize) | vor Aufruf | OperationCancelledException, kein Finish |
unknown | missing | missing | missing | session.close() |
missing | blocked | E0.5 |
onTableTransferred(table) Callback |
side_effect | progress-callback | vor Aufruf | nach Cancel kein Erfolgssignal | atomic | not_applicable | not_applicable | not_applicable | not_needed | missing | blocked | E0.5 |
Diese Sektion klassifiziert nach Hauptplan §4.1 die zentralen Driver-/Port- Aufrufe in einer der drei Kategorien:
cancelbar— Port-Vertrag oder Driver-API erlaubt kooperatives Stoppen während des Calls.atomic-nicht-cancelbar— Call läuft bis Ende durch, Hauptplan §4.1 fordert dafür belegtes Timeout/Laufzeitfenster<= 30s, Measurement-Evidence, kein interner Retry-Loop, kein Ressourcen-Leak nach Timeout.blockierend— keine der obigen Bedingungen heute erfüllt; Pfad blockiert das E0-Gate, bis Adapter-Konfiguration oder Port-Erweiterung den Status aufcancelbaroderatomic-nicht-cancelbarhebt.
Stand nach E0.7-Abschluss: alle früher als blockierend klassifizierten
Driver-Calls sind durch
PoolSettings.statementTimeoutMs
PoolSettings.networkTimeoutMsund den common JDBC-Timeout-Layer (E0.7.3TimeoutDecoratedConnection) nun alsatomic-nicht-cancelbarbelegt. Bench-Tests pro Driver liefern die Measurement-Evidence (E0.7.4).
| Port-Grenze | Heutiger Vertrag | E0.7-Klassifikation | Begründung | Measurement-Evidence |
|---|---|---|---|---|
dev.dmigrate.driver.SchemaReader.read(pool, options) |
synchron, kein Cancel-Token; aber Connection ist TimeoutDecoratedConnection, jedes prepareStatement setzt setQueryTimeout(ceil(ms/1000)); Connection trägt setNetworkTimeout(...) |
atomic-nicht-cancelbar mit <= 30s Default |
Schema-Reader nutzt JdbcMetadataSession über pool.borrow(). Alle Introspection-Statements werden vom Decorator timeout-gesetzt. PostgreSQL hat zusätzlich connectionInitSql = SET statement_timeout = $ms. |
JdbcMetadataSessionTimeoutTest (driver-common, default-CI) belegt jeder Statement queryTimeout-trägt; E07PostgresTimeoutBench empirisch. |
dev.dmigrate.driver.DataReader.streamTable(pool, table, filter, chunkSize) |
ChunkSequence (Sequence + AutoCloseable); initiierender prepareStatement ist über Decorator timeout-gesetzt |
atomic-nicht-cancelbar mit <= 30s Default für den initialen executeQuery-Call |
Stream-Open-Latenz ist gebunden; lange Tabellen-Reads brechen via Statement-Timeout ab. inter-Chunk-Cancel bleibt 0.9.7+-Followup (Token-Param am Reader-Iterator). | E07PostgresTimeoutBench "statement_timeout enforces <= 5s on long-running pg_sleep query" + E07MysqlTimeoutBench "statementTimeoutMs enforces <= 5s on a long read-only cross join". SQLite-Driver nutzt xerial-jdbc — setQueryTimeout(s) mappt dort intern auf PRAGMA busy_timeout = s*1000 (Lock-Wait, kein Query-Interrupt für CPU-bound Queries); empirische SQLite-Bench daher nicht aussagekräftig, Wiring ist via TimeoutDecoratedConnectionTest + JdbcMetadataSessionTimeoutTest (default-CI in driver-common) belegt. |
dev.dmigrate.driver.DataWriter.openTable(pool, table, options) |
synchron; alle DDL-Statements via Decorator timeout-gesetzt | atomic-nicht-cancelbar mit <= 30s pro DDL-Statement |
DDL-/Trigger-Manipulation läuft über prepareStatement/createStatement der dekorierten Connection. Direkte connection.metaData.getPrimaryKeys(...) in PostgreSQL/MySQL ist über setNetworkTimeout netzwerkseitig gebunden. |
Bench-Tests pro Driver (Writer-DDL läuft beim Bench-Setup mit setQueryTimeout durch); JdbcMetadataSessionTimeoutTest.execute(sql) für DDL-Pfad. |
TableImportSession.write(chunk) |
synchron, INSERT/UPSERT-Batch; via AbstractTableImportSession.ensureInsertPlan() über Decorator |
atomic-nicht-cancelbar mit <= 30s |
Batch-Insert hat chunk-bounded Größe (PipelineConfig.chunkSize Default 10 000); jeder prepareStatement(insertSql) ist timeout-gesetzt. |
Bench-Tests + Decorator-Test (alle 9 prepareStatement-Overloads). |
TableImportSession.commitChunk() |
JDBC-Transaktion-Commit; Connection trägt setNetworkTimeout(...) |
atomic-nicht-cancelbar | Commits laufen über Connection. Lock-Wait wird durch setNetworkTimeout(networkTimeoutMs) gebunden (PostgreSQL/MySQL). SQLite zusätzlich via PRAGMA busy_timeout. |
E07PostgresTimeoutBench Cleanup-Test belegt healthy commit nach Cancel; connection.networkTimeout belegt via HikariConnectionPoolFactoryTest "borrow gracefully handles drivers that do not support setNetworkTimeout". |
TableImportSession.finishTable() |
mehrstufig: Trigger-Re-Enable, Sequence-Anpassung, FK-Re-Enable; jedes DDL-Statement via Decorator | atomic-nicht-cancelbar mit <= 30s pro DDL-Statement |
Mehrere DDL-Statements, jedes durch den common Layer timeout-gesetzt. PostgreSQL hat zusätzlich server-side statement_timeout. |
Bench-Tests + JdbcMetadataSessionTimeoutTest.executeBatch. |
dev.dmigrate.format.data.DataChunkReader.nextChunk() (Format-Reader) |
synchron, lokales Filesystem (JSONL/CSV/YAML); kein Netzwerk, kein Cancel-Token | atomic (lokale FS-Reads) | Format-Reader liest lokale Files mit chunk-bounded Buffer. Plan §4.1 bound = local-FS read time ist akzeptiert. Remote-FS-Inputs (S3, HTTP, NFS) sind heute nicht Teil von 0.9.6. |
keine zusätzliche Evidence nötig (lokal); falls 0.9.7+ remote inputs ergänzt, Token-Param am Format-Reader. |
CheckpointStore.save(manifest) / load(opId) / complete(opId) |
synchron, lokales FS (atomic-rename Pattern), kein Cancel-Token | atomic | Lokale FS-Operationen, jeweils sub-second. Cancel zwischen Save-Calls über Runner-Checkpoint abgefangen. | keine. |
ProgressReporter.report(event) |
synchron, in-memory + stderr | atomic | In-Memory + sync-stderr; nicht relevant. | keine. |
connectionResolver.resolve(...) (Transfer) |
öffnet Source- + Target-Pool sequenziell; HikariCP connectionTimeout = 10000ms (Pool-Acquire-Timeout) plus setNetworkTimeout(networkTimeoutMs) auf jeder geborgten Connection |
atomic-nicht-cancelbar mit <= 30s Default |
Pool-Open-Latenz ist über Hikari connectionTimeoutMs (10s default) gebunden. Connection-I/O nach Borrow ist über setNetworkTimeout gebunden. |
HikariConnectionPoolFactoryTest "borrow gracefully handles drivers that do not support setNetworkTimeout"; Bench-Tests verifizieren Pool-Acquire empirisch. |
Konsequenz für das E0-Gate (siehe
ImpPlan-0.9.6-E0-Gate-Decision.md):
Alle früher als blockierend klassifizierten Pfade sind durch E0.7
auf atomic-nicht-cancelbar mit cancel_budget_ms <= 30000 und
konkreter Measurement-Evidence aus den Bench-Tests gehoben. Plan §9
"Atomar-nicht-cancelbare Calls haben ein belegtes Timeout-/Laufzeit-
fenster, ein gemessenes E0-Cancel-Reaktionsbudget von <= 30s, keine
ungebundenen Retry-/Reconnect-Loops und hinterlassen nach Timeout
keine offenen Ressourcen" ist erfüllt.
Verdict: Go — Phase E darf starten. Alle Details in
ImpPlan-0.9.6-E0-Gate-Decision.md.
Streaming-Anmerkungen:
- Der
TableImportSession-Vertrag bietet heute kein dediziertesabort(). E0.5-Spike hat geprüft:session.close()nach unvollständigemcommitChunkreicht für PostgreSQL/MySQL/SQLite, weil JDBC-Auto-Rollback bei Connection-Close greift. SQLite-WAL benötigt zusätzlichesROLLBACK, das im Adapter-close()bereits enthalten ist (SqliteImportSession). Phase-E muss kein neuerabort()ergänzt werden. DataChunkReaderist heute lokales FS —bound = local FS read time. Remote-FS-Inputs sind nicht Teil von 0.9.6.CheckpointStoreist file-basiert (FileCheckpointStore) — alle Operationen sind sub-second. Token-Param am Store-Vertrag ist nicht notwendig; Runner-Checkpoints zwischen Store-Calls reichen.
Reine gate_result-Verteilung der oben gelisteten Zeilen:
| Stand | go |
go_followup |
tentative-go |
blocked |
|---|---|---|---|---|
| 2026-05-05 (E0.2) | 0 | 0 | 4 | ~70 |
| 2026-05-05 (E0.4) | ~10 | ~12 | 4 | ~50 |
| 2026-05-05 (E0.5) | ~25 (alle Inter-Call-Checkpoints + Resume-Skip + Callbacks) | ~10 (Driver-atomic-nicht-cancelbar-Kandidaten mit queryTimeout-Followup) |
4 | ~10 (blockierend-Treffer in Section 6: alle ohne setQueryTimeout-Konfiguration) |
| 2026-05-05 (E0.7) | ~74 (alle Driver-Calls + Inter-Call-Checkpoints + Format-Reader/Checkpoint-Store/ProgressReporter atomic) |
0 | 0 | 0 |
E0.7-Gate-Stand (siehe ImpPlan-0.9.6-E0-Gate-Decision.md):
- Inter-Call-Cancel ist vollständig nachgewiesen für Reverse, Profile, Import und Transfer (E0.4 + E0.5-Tests).
- During-Driver-Call-Cancel ist durch
PoolSettings.statementTimeoutMs/networkTimeoutMs(E0.7.1) + driver-spezifischerconnectionInitSql(E0.7.2) + commonTimeoutDecoratedConnection(E0.7.3) + Bench-Tests pro Driver (E0.7.4) belegt. Cancel-Reaktionsbudget<= 30sDefault; pro Adapter0deaktiviert (Plan §4.2). Hauptplan §9 "belegtes Timeout-/Laufzeit- fenster, gemessenes E0-Cancel-Reaktionsbudget" erfüllt. - Verdict re-stempelt:
Go. Phase E darf starten.
| Datum | AP | Aenderung |
|---|---|---|
| 2026-05-05 | E0.2 | Initial-Snapshot — alle externen Side-Effect-Zeilen blocked (tests = missing); 4 In-Memory-Zeilen tentative-go. |
| 2026-05-05 | E0.4 | Reverse-Pipeline (3 Checkpoints + Exit-130-Mapping) + Profile-Pipeline (Checkpoints in ProfileDatabaseService/ProfileTableService/profileColumn + Exit-130 in DataProfileRunner). Reverse: 4 Zeilen go, 2 Zeilen go_followup, 1 Zeile bleibt blocked (reader.read E0.5-Driver-Vertrag). Profile: 2 Zeilen go, 9 Zeilen go_followup (in-flight Cancel benötigt Driver-Statement-Cancel — E0.5), 1 Zeile bleibt go_followup (Pre-Pool-Checkpoint optional, Cleanup über finally { pool.close() }). Tests: SchemaReverseRunnerCancelCheckpointTest, ProfileServiceCancelCheckpointTest, DataProfileRunnerCancelCheckpointTest. |
| 2026-05-05 | E0.5 (1/3) | Import-Pipeline: DataImportRunner Outer-130-Mapping, ImportStreamingInvoker Cancel-Re-Throw, StreamingImporter Tabellen-Loop-Checkpoints, TableImporter prepareImport/skipCommittedChunks/finishImport-Checkpoints, importChunks chunk-loop-Checkpoints (außerhalb der 3 chunk-failure try-Blöcke; Plan §4.5). Tests: DataImportRunnerCancelCheckpointTest, StreamingImporterCancelCheckpointTest, TableImporterCancelCheckpointTest. Inter-Call-Zeilen wechseln zu go. |
| 2026-05-05 | E0.5 (2/3) | Transfer-Pipeline: DataTransferRunner Outer-130-Mapping + Cancel-Filter in Schema-Read- und Transfer-Execute-catches, TransferExecutor Tabellen-Loop + transferTable-Entry + Chunk-Loop-Checkpoints + pre-finish-Checkpoint. Tests: DataTransferRunnerCancelCheckpointTest, TransferExecutorCancelCheckpointTest. Inter-Call-Zeilen wechseln zu go. |
| 2026-05-05 | E0.5 (3/3) | Driver-/Port-Vertrags-Klassifikation in Section 6: alle monolithic-driver-call-Zeilen sind heute blockierend wegen fehlender Statement.setQueryTimeout/Connection.setNetworkTimeout-Konfiguration in Driver-Adaptern. Phase-E-Nacharbeit ist Adapter-Konfiguration (kein Port-Vertrag-Wechsel). Format-Reader (DataChunkReader.nextChunk), CheckpointStore, ProgressReporter sind als atomic klassifiziert. |
| 2026-05-05 | E0.7.1 | PoolSettings.statementTimeoutMs + networkTimeoutMs Felder mit Default 30_000 und init {}-Validation (Commit 72b8a9f). connection-config-spec.md §2.2 erweitert. |
| 2026-05-05 | E0.7.2 | connectionInitSql pro Driver in HikariConnectionPoolFactory (Commit c5a70e6). PostgreSQL SET statement_timeout, MySQL SET SESSION MAX_EXECUTION_TIME, SQLite PRAGMA busy_timeout. |
| 2026-05-05 | E0.7.3 | Common JDBC-Timeout-Layer (Commit 15f3e45): TimeoutDecoratedConnection (13 Statement-Overload-Decorators) + Connection.setNetworkTimeout(...) beim borrow() mit SQLFeatureNotSupportedException-Resilienz; timeoutSecondsOf rundet sub-second auf. Decoder ist transparent für alle Adapter-Module. |
| 2026-05-05 | E0.7.4 | Bench-Tests pro Driver (Commit 3fe0508): E07PostgresTimeoutBench, E07MysqlTimeoutBench (@Tag("integration")); JdbcMetadataSessionTimeoutTest (default-CI) belegt Profiling-/Schema-Reader-Coverage über den common Layer. SQLite-Bench wurde nicht ergänzt — xerial-jdbc-setQueryTimeout mappt auf busy_timeout (Lock-Wait) und unterbricht keine CPU-bound Queries; Wiring ist über TimeoutDecoratedConnectionTest + HikariConnectionPoolFactoryTest (default-CI) belegt. |
| 2026-05-05 | E0.7.4-Fix | CI-Korrektur (siehe Commit-Log): MySQL-Bench-Query von SELECT SLEEP(60) auf 2-Wege-Cross-Join über information_schema.columns gewechselt — MAX_EXECUTION_TIME greift nicht für Built-in-Funktionen wie SLEEP(). Slack auf < 10s erweitert (KILL-QUERY-Round-Trip + Hikari-Acquire). SQLite-Bench entfernt (siehe Begründung E0.7.4-Eintrag oben). |
| 2026-05-05 | E0.7.5 | Section 6 final-klassifiziert: alle früher-blockierend-Zeilen werden zu atomic-nicht-cancelbar mit konkreten Bench-/Test-Referenzen in measurement_evidence. Schnellstatistik: blocked = 0, go ~74. Gate-Decision-Verdict re-stempelt zu Go. |